commit 9e4ab26005452adeb499785f053f9fa4ed04939d Author: astatin3 Date: Mon Dec 9 08:01:09 2024 -0700 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..14ff7a4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,167 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9008bb0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Set default behavior to automatically normalize line endings (LF on check-in). +* text=auto + +# Force batch scripts to always use CRLF line endings so that if a repo is accessed +# in Windows via a file share from Linux, the scripts will work. +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf +*.{ics,[iI][cC][sS]} text eol=crlf + +# Force bash scripts to always use LF line endings so that if a repo is accessed +# in Unix via a file share from Windows, the scripts will work. +*.sh text eol=lf + +# Ensure Spotless does not try to use CRLF line endings on Windows in the local repo. +*.gradle text eol=lf +*.java text eol=lf +*.json text eol=lf +*.md text eol=lf +*.xml text eol=lf +*.h text eol=lf +*.hpp text eol=lf +*.inc text eol=lf +*.inl text eol=lf +*.cpp text eol=lf + +# Frontend Files +*.js text eol=lf +*.vue text eol=lf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.so binary +*.dll binary +*.webp binary diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..0447d72 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# These owners will be the default owners for everything in the repo. +* @PhotonVision/program-devs diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..5cdc15f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is and what the expected behavior should have been. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Screenshots / Videos** +If applicable, add screenshots to help explain your problem. Additionally, provide journalctl logs and settings zip export. If your issue is regarding the web dashboard, please provide screenshots and the output of the browser console. + +**Platform:** + - Hardware Platform (ex. Raspberry Pi 4, Windows x64): + - Network Configuration (Connection between the Radio and any devices in between, such as a Network Switch): + - PhotonVision Version: + - Browser (with Version) (Chrome, Edge, Firefox, etc.): + - Camera(s) Used: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..11fc491 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0335c35 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,590 @@ +name: Build + +on: + # Run on pushes to master and pushed tags, and on pull requests against master, but ignore the docs folder + push: + branches: [ master ] + tags: + - 'v*' + paths: + - '**' + - '!docs/**' + - '.github/**' + pull_request: + branches: [ master ] + paths: + - '**' + - '!docs/**' + - '.github/**' + merge_group: + +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: + + strategy: + fail-fast: false + matrix: + include: + - os: windows-2022 + architecture: x64 + - os: macos-14 + architecture: aarch64 + - os: ubuntu-22.04 + + name: "Photonlib - Build Examples - ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + + 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 + - name: Install RoboRIO Toolchain + run: ./gradlew installRoboRioToolchain + # Need to publish to maven local first, so that C++ sim can pick it up + - name: Publish photonlib to maven local + run: ./gradlew photon-targeting:publishtomavenlocal photon-lib:publishtomavenlocal -x check + - name: Build Java examples + working-directory: photonlib-java-examples + run: ./gradlew build + - name: Build C++ examples + working-directory: photonlib-cpp-examples + run: ./gradlew build + 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@v4 + 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: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check + - name: Gradle Tests + run: ./gradlew testHeadless -i --stacktrace + - name: Gradle Coverage + run: ./gradlew jacocoTestReport + - name: Publish Coverage Report + uses: codecov/codecov-action@v4 + with: + file: ./photon-server/build/reports/jacoco/test/jacocoTestReport.xml + - name: Publish Core Coverage Report + uses: codecov/codecov-action@v4 + 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 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install graphviz + run: | + sudo apt-get update + sudo apt-get -y install graphviz + - name: Install dependencies + working-directory: docs + 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 + working-directory: docs + run: | + make html + - uses: actions/upload-artifact@v4 + with: + name: built-docs + path: docs/build/html + + build-photonlib-vendorjson: + name: "Build Vendor JSON" + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Java 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + # grab all tags + - run: git fetch --tags --force + + # Generate the JSON and give it the ""standard""" name maven gives it + - run: | + ./gradlew photon-lib:generateVendorJson + export VERSION=$(git describe --tags --match=v*) + mv photon-lib/build/generated/vendordeps/photonlib.json photon-lib/build/generated/vendordeps/photonlib-$(git describe --tags --match=v*).json + + # Upload it here so it shows up in releases + - uses: actions/upload-artifact@v4 + with: + name: photonlib-vendor-json + path: photon-lib/build/generated/vendordeps/photonlib-*.json + + build-photonlib-host: + env: + MACOSX_DEPLOYMENT_TARGET: 13 + strategy: + fail-fast: false + matrix: + include: + - os: windows-2022 + artifact-name: Win64 + architecture: x64 + - os: macos-14 + artifact-name: macOS + architecture: aarch64 + - os: ubuntu-22.04 + artifact-name: Linux + + name: "Photonlib - Build Host - ${{ matrix.artifact-name }}" + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install Java 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + architecture: ${{ matrix.architecture }} + - run: git fetch --tags --force + - run: ./gradlew photon-targeting:build photon-lib:build -i + name: Build with Gradle + - run: ./gradlew photon-lib:publish photon-targeting:publish + name: Publish + env: + ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }} + if: github.event_name == 'push' && github.repository_owner == 'photonvision' + # Copy artifacts to build/outputs/maven + - run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts + - uses: actions/upload-artifact@v4 + with: + name: maven-${{ matrix.artifact-name }} + path: build/outputs + + build-photonlib-docker: + strategy: + fail-fast: false + matrix: + include: + - container: wpilib/roborio-cross-ubuntu:2024-22.04 + artifact-name: Athena + build-options: "-Ponlylinuxathena" + - container: wpilib/raspbian-cross-ubuntu:bullseye-22.04 + artifact-name: Raspbian + build-options: "-Ponlylinuxarm32" + - container: wpilib/aarch64-cross-ubuntu:bullseye-22.04 + artifact-name: Aarch64 + build-options: "-Ponlylinuxarm64" + + runs-on: ubuntu-22.04 + container: ${{ matrix.container }} + name: "Photonlib - Build Docker - ${{ matrix.artifact-name }}" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Config Git + run: | + git config --global --add safe.directory /__w/photonvision/photonvision + - name: Build PhotonLib + # We don't need to run tests, since we specify only non-native platforms + run: ./gradlew photon-targeting:build photon-lib:build ${{ matrix.build-options }} -i -x test + - name: Publish + run: ./gradlew photon-lib:publish photon-targeting:publish ${{ matrix.build-options }} + env: + ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }} + if: github.event_name == 'push' && github.repository_owner == 'photonvision' + # Copy artifacts to build/outputs/maven + - run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts ${{ matrix.build-options }} + - uses: actions/upload-artifact@v4 + with: + name: maven-${{ matrix.artifact-name }} + path: build/outputs + + combine: + name: Combine + needs: [build-photonlib-docker, build-photonlib-host] + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: git fetch --tags --force + # download all maven-* artifacts to outputs/ + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: output + pattern: maven-* + - run: find . + - run: zip -r photonlib-$(git describe --tags --match=v*).zip . + name: ZIP stuff up + working-directory: output + - run: ls output + - uses: actions/upload-artifact@v4 + with: + name: photonlib-offline + path: output/*.zip + + 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: winx64 + - os: macos-latest + artifact-name: macOS + architecture: x64 + arch-override: macx64 + - os: macos-latest + artifact-name: macOSArm + architecture: x64 + arch-override: macarm64 + - os: ubuntu-22.04 + artifact-name: Linux + architecture: x64 + arch-override: linuxx64 + - os: ubuntu-22.04 + 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 + architecture: ${{ matrix.architecture }} + - name: Install Arm64 Toolchain + run: ./gradlew installArm64Toolchain + if: ${{ (matrix.artifact-name) == 'LinuxArm64' }} + - 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: ./gradlew photon-targeting:jar photon-server:shadowJar -PArchOverride=${{ matrix.arch-override }} + if: ${{ (matrix.arch-override != 'none') }} + - run: ./gradlew photon-server:shadowJar + if: ${{ (matrix.arch-override == 'none') }} + - uses: actions/upload-artifact@v4 + with: + name: jar-${{ matrix.artifact-name }} + path: photon-server/build/libs + - uses: actions/upload-artifact@v4 + with: + name: photon-targeting_jar-${{ matrix.artifact-name }} + path: photon-targeting/build/libs + + run-smoketest-native: + needs: [build-package] + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + artifact-name: jar-Linux + extraOpts: -Djdk.lang.Process.launchMechanism=vfork + - os: windows-latest + artifact-name: jar-Win64 + extraOpts: "" + - os: macos-latest + artifact-name: jar-macOS + architecture: x64 + + runs-on: ${{ matrix.os }} + + steps: + - name: Install Java 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + - uses: actions/download-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + # On linux, install mrcal packages + - run: | + sudo apt-get update + sudo apt-get install --yes libcholmod3 liblapack3 libsuitesparseconfig5 + if: ${{ (matrix.os) == 'ubuntu-22.04' }} + # and actually run the jar + - run: java -jar ${{ matrix.extraOpts }} *.jar --smoketest + if: ${{ (matrix.os) != 'windows-latest' }} + - run: ls *.jar | %{ Write-Host "Running $($_.Name)"; Start-Process "java" -ArgumentList "-jar `"$($_.FullName)`" --smoketest" -NoNewWindow -Wait; break } + if: ${{ (matrix.os) == 'windows-latest' }} + + run-smoketest-chroot: + needs: [build-package] + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: RaspberryPi + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-4/photonvision_raspi.img.xz + cpu: cortex-a7 + image_additional_mb: 0 + extraOpts: -Djdk.lang.Process.launchMechanism=vfork + + runs-on: ${{ matrix.os }} + name: smoketest-${{ matrix.image_suffix }} + + steps: + - uses: actions/download-artifact@v4 + with: + name: jar-${{ matrix.artifact-name }} + + - uses: pguyot/arm-runner-action@v2 + name: Run photon smoketest + 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 + # our image better have java installed already + commands: | + java -jar ${{ matrix.extraOpts }} *.jar --smoketest + + build-image: + needs: [build-package] + + if: ${{ github.event_name != 'pull_request' }} + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: RaspberryPi + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_raspi.img.xz + cpu: cortex-a7 + image_additional_mb: 0 + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: limelight2 + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_limelight.img.xz + cpu: cortex-a7 + image_additional_mb: 0 + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: limelight3 + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_limelight3.img.xz + cpu: cortex-a7 + image_additional_mb: 0 + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: orangepi5 + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_opi5.img.xz + cpu: cortex-a8 + image_additional_mb: 1024 + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: orangepi5b + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_opi5b.img.xz + cpu: cortex-a8 + image_additional_mb: 1024 + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: orangepi5plus + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_opi5plus.img.xz + cpu: cortex-a8 + image_additional_mb: 1024 + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: orangepi5pro + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_opi5pro.img.xz + cpu: cortex-a8 + image_additional_mb: 1024 + - os: ubuntu-22.04 + artifact-name: LinuxArm64 + image_suffix: orangepi5max + image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_opi5max.img.xz + cpu: cortex-a8 + image_additional_mb: 1024 + + 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@HEAD + 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, combine] + runs-on: ubuntu-22.04 + steps: + # Download all fat JARs + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + pattern: jar-* + # Download offline photonlib + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + pattern: photonlib-offline + # Download vendor json + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + pattern: photonlib-vendor-json + # Download all images + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + pattern: image-* + + - 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 + **/photonlib*.zip + if: github.event_name == 'push' + # Upload all jars and xz archives + # Split into two uploads to work around max size limits in action-gh-releases + # https://github.com/softprops/action-gh-release/issues/353 + - uses: softprops/action-gh-release@v2.0.9 + with: + files: | + **/*orangepi5*.xz + if: startsWith(github.ref, 'refs/tags/v') + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: softprops/action-gh-release@v2.0.9 + with: + files: | + **/!(*orangepi5*).xz + **/*.jar + **/photonlib*.json + **/photonlib*.zip + if: startsWith(github.ref, 'refs/tags/v') + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + dispatch: + name: dispatch + needs: [build-photonlib-vendorjson, release] + runs-on: ubuntu-22.04 + steps: + - uses: peter-evans/repository-dispatch@v3 + if: | + github.repository == 'PhotonVision/photonvision' && + startsWith(github.ref, 'refs/tags/v') + with: + token: ${{ secrets.VENDOR_JSON_REPO_PUSH_TOKEN }} + repository: PhotonVision/vendor-json-repo + event-type: tag + client-payload: '{"run_id": "${{ github.run_id }}", "package_version": "${{ github.ref_name }}"}' diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml new file mode 100644 index 0000000..10af231 --- /dev/null +++ b/.github/workflows/lint-format.yml @@ -0,0 +1,97 @@ +name: Lint and Format + +on: + # Run on pushes to master and pushed tags, and on pull requests against master, but ignore the docs folder + push: + branches: [ master ] + tags: + - 'v*' + paths: + - '**' + - '!docs/**' + - '.github/**' + pull_request: + branches: [ master ] + paths: + - '**' + - '!docs/**' + - '.github/**' + merge_group: + +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@v4 + - 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.11 + - name: Install wpiformat + run: pip3 install wpiformat==2024.45 + - 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@v4 + with: + name: wpiformat fixes + path: wpiformat-fixes.patch + if: ${{ failure() }} + javaformat: + name: "Java Formatting" + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + - run: ./gradlew spotlessCheck + name: Run spotless + + client-lint-format: + name: "PhotonClient Lint and Formatting" + 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: 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@v4 + - 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 diff --git a/.github/workflows/photon-code-docs.yml b/.github/workflows/photon-code-docs.yml new file mode 100644 index 0000000..11d8704 --- /dev/null +++ b/.github/workflows/photon-code-docs.yml @@ -0,0 +1,97 @@ +name: Photon Code Documentation + +on: + # Run on pushes to master and pushed tags, and on pull requests against master, but ignore the docs folder + push: + branches: [ master ] + tags: + - 'v*' + paths: + - '**' + - '!docs/**' + - '.github/**' + pull_request: + branches: [ master ] + paths: + - '**' + - '!docs/**' + - '.github/**' + merge_group: + +# 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@v4 + with: + java-version: 17 + distribution: temurin + + - name: Build javadocs/doxygen + run: | + chmod +x gradlew + ./gradlew photon-docs:generateJavaDocs photon-docs:doxygen + + - uses: actions/upload-artifact@v4 + with: + name: built-docs + path: photon-docs/build/docs + + release: + needs: [build-client, run_docs] + + runs-on: ubuntu-22.04 + steps: + + # Download literally every single artifact. + - uses: actions/download-artifact@v4 + + - run: find . + - name: copy file via ssh password + if: github.ref == 'refs/heads/master' + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.WEBMASTER_SSH_HOST }} + username: ${{ secrets.WEBMASTER_SSH_USERNAME }} + password: ${{ secrets.WEBMASTER_SSH_KEY }} + port: ${{ secrets.WEBMASTER_SSH_PORT }} + source: "*" + target: /var/www/html/photonvision-docs/ diff --git a/.github/workflows/photonvision-docs.yml b/.github/workflows/photonvision-docs.yml new file mode 100644 index 0000000..9ef25f8 --- /dev/null +++ b/.github/workflows/photonvision-docs.yml @@ -0,0 +1,55 @@ +name: PhotonVision Sphinx Documentation Checks + +on: + push: + branches: [ master ] + paths: + - 'docs/**' + - '.github/**' + pull_request: + branches: [ master ] + paths: + - 'docs/**' + - '.github/**' + merge_group: + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install and upgrade pip + run: python -m pip install --upgrade pip + + - name: Install graphviz + run: | + sudo apt-get update + sudo apt-get -y install graphviz + + - name: Install Python dependencies + working-directory: docs + run: | + pip install sphinx sphinx_rtd_theme sphinx-tabs sphinxext-opengraph doc8 + pip install -r requirements.txt + + - name: Check links + working-directory: docs + run: make linkcheck + continue-on-error: true + + - name: Check lint + working-directory: docs + run: make lint + + - name: Compile HTML + working-directory: docs + run: make html diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..dd261cf --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,76 @@ +name: Build and Distribute PhotonLibPy + +permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + +on: + push: + branches: [ master ] + tags: + - 'v*' + paths: + - '**' + - '!docs/**' + - '.github/**' + pull_request: + branches: [ master ] + paths: + - '**' + - '!docs/**' + - '.github/**' + merge_group: + +jobs: + buildAndDeploy: + runs-on: ubuntu-22.04 + + 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 mypy + + - 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: Run mypy type checking + uses: liskin/gh-problem-matcher-wrap@v3 + with: + linters: mypy + run: | + mypy --show-column-numbers --config-file photon-lib/py/pyproject.toml photon-lib + + + - 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fcd869 --- /dev/null +++ b/.gitignore @@ -0,0 +1,152 @@ +Python/__pycache__/WebSiteHandler\.cpython-37\.pyc + +\.idea/ + +*.pyc + +Python/app/__pycache__/ + +Python/app/handlers/__pycache__/ + +\.vscode/ + +/.vs + +backend/settings/ +/.vscode/ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.xz +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# Temporary build files +**/.gradle +**/target +**/src/main/java/META-INF +**/.settings +**/.classpath +**/.project +**/dependency-reduced-pom.xml +# photon-server/photon-vision.iml + +# compile_commands +compile_commands.json + +# clang configuration and clangd cache +.clang +.clangd/ +.cache/ + +New client/photon-client/* + +*.prefs +*.jfr +.DS_Store +# *.iml +*.bin +.gradle +.gradle/* +photonvision_config +bin*/ +build*/ + +photonlib-java-examples/*/vendordeps/* +photonlib-cpp-examples/*/vendordeps/* + +*/networktables.json +*/networktables.json.bck +photonlib-cpp-examples/*/networktables.json.bck +photonlib-java-examples/*/networktables.json.bck +*.sqlite +photon-server/src/main/resources/web/* +venv +.venv/* +.venv diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..4cf9063 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,30 @@ +version: 2 + +sphinx: + builder: html + configuration: docs/source/conf.py + fail_on_warning: true + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + apt_packages: + - graphviz + jobs: + post_checkout: + # Cancel building pull requests when there aren't changed in the docs directory or YAML file. + # You can add any other files or directories that you'd like here as well, + # like your docs requirements file, or other files that will change your docs build. + # + # If there are no changes (git diff exits with 0) we force the command to return with 183. + # This is a special exit code on Read the Docs that will cancel the build immediately. + - | + if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/master -- docs/ .readthedocs.yaml; + then + exit 183; + fi + +python: + install: + - requirements: docs/requirements.txt diff --git a/.styleguide b/.styleguide new file mode 100644 index 0000000..1acec9b --- /dev/null +++ b/.styleguide @@ -0,0 +1,36 @@ +cppHeaderFileInclude { + \.h$ + \.hpp$ + \.inc$ + \.inl$ +} + +cppSrcFileInclude { + \.cpp$ +} + +modifiableFileExclude { + \.jpg$ + \.jpeg$ + \.png$ + \.gif$ + \.so$ + \.dll$ + \.webp$ + \.ico$ + \.rknn$ + gradlew + photon-lib/py/photonlibpy/generated/ + photon-targeting/src/generated/ +} + +includeProject { + ^photonLib/ +} + +includeOtherLibs { + ^frc/ + ^networktables/ + ^units/ + ^wpi/ +} diff --git a/.styleguide-license b/.styleguide-license new file mode 100644 index 0000000..bcc3fc0 --- /dev/null +++ b/.styleguide-license @@ -0,0 +1,16 @@ +/* + * 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 . + */ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3c7bd5 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# PhotonVision + +[![CI](https://github.com/PhotonVision/photonvision/workflows/CI/badge.svg)](https://github.com/PhotonVision/photonvision/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/PhotonVision/photonvision/branch/master/graph/badge.svg)](https://codecov.io/gh/PhotonVision/photonvision) [![Discord](https://img.shields.io/discord/725836368059826228?color=%23738ADB&label=Join%20our%20Discord&logo=discord&logoColor=white)](https://discord.gg/wYxTwym) + +PhotonVision is the free, fast, and easy-to-use computer vision solution for the *FIRST* Robotics Competition. You can read an overview of our features [on our website](https://photonvision.org). You can find our comprehensive documentation [here](https://docs.photonvision.org). + +The latest release of platform-specific jars and images is found [here](https://github.com/PhotonVision/photonvision/releases). + +If you are interested in contributing code or documentation to the project, please [read our getting started page for contributors](https://docs.photonvision.org/en/latest/docs/contributing/index.html) and **[join the Discord](https://discord.gg/wYxTwym) to introduce yourself!** We hope to provide a welcoming community to anyone who is interested in helping. + +## Authors + + + + + +## Documentation + +- Our main documentation page: [docs.photonvision.org](https://docs.photonvision.org) +- Photon UI demo: [demo.photonvision.org](https://demo.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-client/)) +- Javadocs: [javadocs.photonvision.org](https://javadocs.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-docs/javadoc/)) +- C++ Doxygen [cppdocs.photonvision.org](https://cppdocs.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-docs/doxygen/html/)) + +## Building + +Gradle is used for all C++ and Java code, and NPM is used for the web UI. Instructions to compile PhotonVision yourself can be found [in our docs](https://docs.photonvision.org/en/latest/docs/contributing/building-photon.html#compiling-instructions). + +You can run one of the many built in examples straight from the command line, too! They contain a fully featured robot project, and some include simulation support. The projects can be found inside the [`photonlib-java-examples`](photonlib-java-examples) and [`photonlib-cpp-examples`](photonlib-cpp-examples) subdirectories, respectively. Instructions for running these examples directly from the repo are found [in the docs](https://docs.photonvision.org/en/latest/docs/contributing/building-photon.html#running-examples). + +## Gradle Arguments + +Note that these are case sensitive! + +* `-PArchOverride=foobar`: builds for a target system other than your current architecture. [Valid overrides](https://github.com/wpilibsuite/wpilib-tool-plugin/blob/main/src/main/java/edu/wpi/first/tools/NativePlatforms.java) are: + * winx32 + * winx64 + * winarm64 + * macx64 + * macarm64 + * linuxx64 + * linuxarm64 + * linuxathena +- `-PtgtIP`: Specifies where `./gradlew deploy` should try to copy the fat JAR to +- `-Pprofile`: enables JVM profiling + +If you're cross-compiling, you'll need the wpilib toolchain installed. This can be done via Gradle: for example `./gradlew installArm64Toolchain` or `./gradlew installRoboRioToolchain` + +## Out-of-Source Dependencies + +PhotonVision uses the following additional out-of-source repositories for building code. + +- Base system images for Raspberry Pi & Orange Pi: https://github.com/PhotonVision/photon-image-modifier +- C++ driver for Raspberry Pi CSI cameras: https://github.com/PhotonVision/photon-libcamera-gl-driver +- JNI code for [mrcal](https://mrcal.secretsauce.net/): https://github.com/PhotonVision/mrcal-java +- Custom build of OpenCV with GStreamer/Protobuf/other custom flags: https://github.com/PhotonVision/thirdparty-opencv +- JNI code for aruco-nano: https://github.com/PhotonVision/aruconano-jni + +## Additional packages + +For now, using mrcal requires installing these additional packages on Linux systems: + +``` +sudo apt install libcholmod3 liblapack3 libsuitesparseconfig5 +``` + +## Acknowledgments + +PhotonVision was forked from [Chameleon Vision](https://github.com/Chameleon-Vision/chameleon-vision/). Thank you to everyone who worked on the original project. + +* [WPILib](https://github.com/wpilibsuite) - Specifically [cscore](https://github.com/wpilibsuite/allwpilib/tree/master/cscore), [CameraServer](https://github.com/wpilibsuite/allwpilib/tree/master/cameraserver), [NTCore](https://github.com/wpilibsuite/allwpilib/tree/master/ntcore), and [OpenCV](https://github.com/wpilibsuite/thirdparty-opencv). + +* [Apache Commons](https://commons.apache.org/) - Specifically [Commons Math](https://commons.apache.org/proper/commons-math/), and [Commons Lang](https://commons.apache.org/proper/commons-lang/) + +* [Javalin](https://javalin.io/) + +* [JSON](https://json.org) + +* [FasterXML](https://github.com/FasterXML) - Specifically [jackson](https://github.com/FasterXML/jackson) + +## License + +PhotonVision is licensed under the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.html). + +## Meeting Notes + +Our [meeting notes](https://github.com/PhotonVision/photonvision/wiki/PhotonVision-Meeting-Notes) can be found in the wiki section of this repository. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..9ef0e5f --- /dev/null +++ b/build.gradle @@ -0,0 +1,131 @@ +import edu.wpi.first.toolchain.* + +plugins { + id "java" + id "cpp" + id "com.diffplug.spotless" version "6.24.0" + id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2" + id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2" + id 'edu.wpi.first.WpilibTools' version '1.3.0' + id 'com.google.protobuf' version '0.9.3' apply false + id 'edu.wpi.first.GradleJni' version '1.1.0' +} + +allprojects { + repositories { + mavenCentral() + mavenLocal() + maven { url = "https://maven.photonvision.org/releases" } + maven { url = "https://maven.photonvision.org/snapshots" } + maven { url = "https://jogamp.org/deployment/maven/" } + } + wpilibRepositories.addAllReleaseRepositories(it) + wpilibRepositories.addAllDevelopmentRepositories(it) +} + +ext.localMavenURL = file("$project.buildDir/outputs/maven") +ext.allOutputsFolder = file("$project.buildDir/outputs") + +// Configure the version number. +apply from: "versioningHelper.gradle" + +ext { + wpilibVersion = "2025.1.1-beta-2" + wpimathVersion = wpilibVersion + openCVYear = "2024" + openCVversion = "4.8.0-4" + joglVersion = "2.4.0" + javalinVersion = "5.6.2" + libcameraDriverVersion = "dev-v2023.1.0-15-gc8988b3" + rknnVersion = "dev-v2024.0.1-4-g0db16ac" + frcYear = "2025" + mrcalVersion = "dev-v2024.0.0-27-g41d7868"; + + + pubVersion = versionString + isDev = pubVersion.startsWith("dev") + + // A list, for legacy reasons, with only the current platform contained + wpilibNativeName = wpilibTools.platformMapper.currentPlatform.platformName; + def nativeName = wpilibNativeName + if (wpilibNativeName == "linuxx64") nativeName = "linuxx86-64"; + if (wpilibNativeName == "winx64") nativeName = "windowsx86-64"; + if (wpilibNativeName == "macx64") nativeName = "osxx86-64"; + if (wpilibNativeName == "macarm64") nativeName = "osxarm64"; + jniPlatform = nativeName + + println("Building for platform " + jniPlatform + " wpilib: " + wpilibNativeName) + println("Using Wpilib: " + wpilibVersion) + println("Using OpenCV: " + openCVversion) + + + photonMavenURL = 'https://maven.photonvision.org/' + (isDev ? 'snapshots' : 'releases'); + println("Publishing Photonlib to " + photonMavenURL) +} + +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**', '**/src/generated/**' + } + toggleOffOn() + googleJavaFormat() + indentWithTabs(2) + indentWithSpaces(4) + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + } + groovyGradle { + target fileTree('.') { + include '**/*.gradle' + exclude '**/build/**', '**/build-*/**' + } + greclipse() + indentWithSpaces(4) + trimTrailingWhitespace() + endWithNewline() + } + format 'xml', { + target fileTree('.') { + include '**/*.xml' + exclude '**/build/**', '**/build-*/**', "**/.idea/**" + } + eclipseWtp('xml') + trimTrailingWhitespace() + indentWithSpaces(2) + endWithNewline() + } + format 'misc', { + target fileTree('.') { + include '**/*.md', '**/.gitignore' + exclude '**/build/**', '**/build-*/**' + } + trimTrailingWhitespace() + indentWithSpaces(2) + endWithNewline() + } +} + +wrapper { + gradleVersion '8.11' +} + +ext.getCurrentArch = { + return NativePlatforms.desktop +} + +subprojects { + tasks.withType(JavaCompile) { + options.compilerArgs.add '-XDstringConcat=inline' + options.encoding = 'UTF-8' + } + + // Enables UTF-8 support in Javadoc + tasks.withType(Javadoc) { + options.addStringOption("charset", "utf-8") + options.addStringOption("docencoding", "utf-8") + options.addStringOption("encoding", "utf-8") + } +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..cbefb2d --- /dev/null +++ b/codecov.yml @@ -0,0 +1,5 @@ +coverage: + # Turning off commit status to prevent failed checks if coverage decreases + status: + project: no + patch: no diff --git a/devTools/calibrationUtils.py b/devTools/calibrationUtils.py new file mode 100644 index 0000000..340a7b9 --- /dev/null +++ b/devTools/calibrationUtils.py @@ -0,0 +1,255 @@ +import argparse +import base64 +import json +import os +from dataclasses import dataclass + +import cv2 +import mrcal +import numpy as np +from wpimath.geometry import Quaternion as _Quat + + +@dataclass +class Size: + width: int + height: int + + +@dataclass +class JsonMatOfDoubles: + rows: int + cols: int + type: int + data: list[float] + + +@dataclass +class JsonMat: + rows: int + cols: int + type: int + data: str # Base64-encoded PNG data + + +@dataclass +class Point2: + x: float + y: float + + +@dataclass +class Translation3d: + x: float + y: float + z: float + + +@dataclass +class Quaternion: + X: float + Y: float + Z: float + W: float + + +@dataclass +class Rotation3d: + quaternion: Quaternion + + +@dataclass +class Pose3d: + translation: Translation3d + rotation: Rotation3d + + +@dataclass +class Point3: + x: float + y: float + z: float + + +@dataclass +class Observation: + # Expected feature 3d location in the camera frame + locationInObjectSpace: list[Point3] + # Observed location in pixel space + locationInImageSpace: list[Point2] + # (measured location in pixels) - (expected from FK) + reprojectionErrors: list[Point2] + # Solver optimized board poses + optimisedCameraToObject: Pose3d + # If we should use this observation when re-calculating camera calibration + includeObservationInCalibration: bool + snapshotName: str + # The actual image the snapshot is from + snapshotData: JsonMat + + +@dataclass +class CameraCalibration: + resolution: Size + cameraIntrinsics: JsonMatOfDoubles + distCoeffs: JsonMatOfDoubles + observations: list[Observation] + calobjectWarp: list[float] + calobjectSize: Size + calobjectSpacing: float + + +def __convert_cal_to_mrcal_cameramodel( + cal: CameraCalibration, +) -> mrcal.cameramodel | None: + if len(cal.distCoeffs.data) == 5: + model = "LENSMODEL_OPENCV5" + elif len(cal.distCoeffs.data) == 8: + model = "LENSMODEL_OPENCV8" + else: + print("Unknown camera model? giving up") + return None + + def opencv_to_mrcal_intrinsics(ocv): + return [ocv[0], ocv[4], ocv[2], ocv[5]] + + def pose_to_rt(pose: Pose3d): + r = _Quat( + w=pose.rotation.quaternion.W, + x=pose.rotation.quaternion.X, + y=pose.rotation.quaternion.Y, + z=pose.rotation.quaternion.Z, + ).toRotationVector() + t = [ + pose.translation.x, + pose.translation.y, + pose.translation.z, + ] + return np.concatenate((r, t)) + + imagersize = (cal.resolution.width, cal.resolution.height) + + # Always weight=1 for Photon data + WEIGHT = 1 + observations_board = np.array( + [ + # note that we expect row-major observations here. I think this holds + np.array( + list(map(lambda it: [it.x, it.y, WEIGHT], o.locationInImageSpace)) + ).reshape((cal.calobjectSize.width, cal.calobjectSize.height, 3)) + for o in cal.observations + ] + ) + + optimization_inputs = { + "intrinsics": np.array( + [ + opencv_to_mrcal_intrinsics(cal.cameraIntrinsics.data) + + cal.distCoeffs.data + ], + dtype=np.float64, + ), + "extrinsics_rt_fromref": np.zeros((0, 6), dtype=np.float64), + "frames_rt_toref": np.array( + [pose_to_rt(o.optimisedCameraToObject) for o in cal.observations] + ), + "points": None, + "observations_board": observations_board, + "indices_frame_camintrinsics_camextrinsics": np.array( + [[i, 0, -1] for i in range(len(cal.observations))], dtype=np.int32 + ), + "observations_point": None, + "indices_point_camintrinsics_camextrinsics": None, + "lensmodel": model, + "imagersizes": np.array([imagersize], dtype=np.int32), + "calobject_warp": ( + np.array(cal.calobjectWarp) if len(cal.calobjectWarp) > 0 else None + ), + # We always do all the things + "do_optimize_intrinsics_core": True, + "do_optimize_intrinsics_distortions": True, + "do_optimize_extrinsics": True, + "do_optimize_frames": True, + "do_optimize_calobject_warp": len(cal.calobjectWarp) > 0, + "do_apply_outlier_rejection": True, + "do_apply_regularization": True, + "verbose": False, + "calibration_object_spacing": cal.calobjectSpacing, + "imagepaths": np.array([it.snapshotName for it in cal.observations]), + } + + return mrcal.cameramodel( + optimization_inputs=optimization_inputs, + icam_intrinsics=0, + ) + + +def convert_photon_to_mrcal(photon_cal_json_path: str, output_folder: str): + """ + Unpack a Photon calibration JSON (eg, photon_calibration_Microsoft_LifeCam_HD-3000_800x600.json) into + the output_folder directory with images and corners.vnl file for use with mrcal. + """ + with open(photon_cal_json_path, "r") as cal_json: + # Convert to nested objects instead of nameddicts on json-loads + class Generic: + @classmethod + def from_dict(cls, dict): + obj = cls() + obj.__dict__.update(dict) + return obj + + camera_cal_data: CameraCalibration = json.loads( + cal_json.read(), object_hook=Generic.from_dict + ) + + # Create output_folder if not exists + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # Decode each image and save it as a png + for obs in camera_cal_data.observations: + image = obs.snapshotData.data + decoded_data = base64.b64decode(image) + np_data = np.frombuffer(decoded_data, np.uint8) + img = cv2.imdecode(np_data, cv2.IMREAD_UNCHANGED) + cv2.imwrite(f"{output_folder}/{obs.snapshotName}", img) + + # And create a VNL file for use with mrcal + with open(f"{output_folder}/corners.vnl", "w+") as vnl_file: + vnl_file.write("# filename x y level\n") + + for obs in camera_cal_data.observations: + for corner in obs.locationInImageSpace: + # Always level zero + vnl_file.write(f"{obs.snapshotName} {corner.x} {corner.y} 0\n") + + vnl_file.flush() + + mrcal_model = __convert_cal_to_mrcal_cameramodel(camera_cal_data) + + with open(f"{output_folder}/camera-0.cameramodel", "w+") as mrcal_file: + mrcal_model.write( + mrcal_file, + note="Generated from PhotonVision calibration file: " + + photon_cal_json_path + + "\nCalobject_warp (m): " + + str(camera_cal_data.calobjectWarp), + ) + + +def main(): + parser = argparse.ArgumentParser( + description="Convert Photon calibration JSON for use with mrcal" + ) + parser.add_argument("input", type=str, help="Path to Photon calibration JSON file") + parser.add_argument( + "output_folder", type=str, help="Output folder for mrcal VNL file + images" + ) + + args = parser.parse_args() + + convert_photon_to_mrcal(args.input, args.output_folder) + + +if __name__ == "__main__": + main() diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..b7a9f77 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,9 @@ +build/* +.DS_Store +.vscode/* +.idea/* +source/_build +source/docs/_build + +venv/* +.venv/* diff --git a/docs/.styleguide b/docs/.styleguide new file mode 100644 index 0000000..5d98301 --- /dev/null +++ b/docs/.styleguide @@ -0,0 +1,16 @@ + +modifiableFileExclude { + \.jpg$ + \.jpeg$ + \.png$ + \.gif$ + \.so$ + \.pdf$ + \.mp4$ + \.dll$ + \.webp$ + \.ico$ + \.rknn$ + \.svg$ + gradlew +} diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 0000000..2f244ac --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,395 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..f2ae9eb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,24 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = -W --keep-going +SPHINXBUILD = sphinx-build +SOURCEDIR = source +LINTER = doc8 +LINTEROPTS = --ignore D001 # D001 is linelength +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +lint: + @$(LINTER) $(LINTEROPTS) $(SOURCEDIR) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.MD b/docs/README.MD new file mode 100644 index 0000000..1e7a067 --- /dev/null +++ b/docs/README.MD @@ -0,0 +1,9 @@ +# PhotonVision ReadTheDocs + +[![Documentation Status](https://readthedocs.org/projects/photonvision-docs/badge/?version=latest)](https://docs.photonvision.org/en/latest/?badge=latest) + +PhotonVision is a free open-source vision processing software for FRC teams. + +This repository is the source code for our ReadTheDocs documentation, which can be found [here](https://docs.photonvision.org). + +[Contribution and formatting guidelines for this project](https://docs.photonvision.org/en/latest/docs/contributing/photonvision-docs/index.html) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..770cb6b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXOPTS=-W --keep-going + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..84c1582 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,44 @@ +alabaster==0.7.13 +Babel==2.13.1 +beautifulsoup4==4.12.2 +certifi==2023.11.17 +charset-normalizer==3.3.2 +colorama==0.4.6 +doc8==0.11.2 +docopt==0.6.2 +docutils==0.18.1 +furo==2023.9.10 +idna==3.4 +imagesize==1.4.1 +Jinja2==3.0.3 +MarkupSafe==2.1.3 +packaging==23.2 +pbr==6.0.0 +pipreqs==0.4.13 +Pygments==2.17.1 +requests==2.31.0 +restructuredtext-lint==1.4.0 +six==1.16.0 +snowballstemmer==2.2.0 +soupsieve==2.5 +Sphinx==7.2.6 +sphinx-basic-ng==1.0.0b2 +sphinx-notfound-page==1.0.0 +sphinx-rtd-theme==1.3.0 +sphinx-tabs==3.4.4 +sphinx_design==0.5.0 +sphinxcontrib-applehelp==1.0.7 +sphinxcontrib-devhelp==1.0.5 +sphinxcontrib-ghcontributors==0.2.3 +sphinxcontrib-htmlhelp==2.0.4 +sphinxcontrib-jquery==4.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.6 +sphinxcontrib-serializinghtml==1.1.9 +sphinxext-opengraph==0.9.0 +sphinxext-remoteliteralinclude==0.4.0 +stevedore==5.1.0 +urllib3==2.1.0 +yarg==0.1.9 +sphinx-autobuild==2024.4.16 +myst_parser==3.0.1 diff --git a/docs/source/404.md b/docs/source/404.md new file mode 100644 index 0000000..0af59ce --- /dev/null +++ b/docs/source/404.md @@ -0,0 +1,7 @@ +--- +orphan: true +--- + +# Requested Page Not Found + +This page you were looking for was not found. If you think this is a mistake, [file an issue on our GitHub.](https://github.com/PhotonVision/photonvision-docs/issues) diff --git a/docs/source/Makefile b/docs/source/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/source/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/source/_static/assets/3d2019.mp4 b/docs/source/_static/assets/3d2019.mp4 new file mode 100644 index 0000000..a41a9d7 Binary files /dev/null and b/docs/source/_static/assets/3d2019.mp4 differ diff --git a/docs/source/_static/assets/3d2020.mp4 b/docs/source/_static/assets/3d2020.mp4 new file mode 100644 index 0000000..1033dc1 Binary files /dev/null and b/docs/source/_static/assets/3d2020.mp4 differ diff --git a/docs/source/_static/assets/AreaRatioFullness.mp4 b/docs/source/_static/assets/AreaRatioFullness.mp4 new file mode 100644 index 0000000..01585b5 Binary files /dev/null and b/docs/source/_static/assets/AreaRatioFullness.mp4 differ diff --git a/docs/source/_static/assets/PhotonVision-Header-noBG.png b/docs/source/_static/assets/PhotonVision-Header-noBG.png new file mode 100644 index 0000000..64a7f26 Binary files /dev/null and b/docs/source/_static/assets/PhotonVision-Header-noBG.png differ diff --git a/docs/source/_static/assets/PhotonVision-Header-onWhite.png b/docs/source/_static/assets/PhotonVision-Header-onWhite.png new file mode 100644 index 0000000..d35c18c Binary files /dev/null and b/docs/source/_static/assets/PhotonVision-Header-onWhite.png differ diff --git a/docs/source/_static/assets/RoundLogo.png b/docs/source/_static/assets/RoundLogo.png new file mode 100644 index 0000000..e6be308 Binary files /dev/null and b/docs/source/_static/assets/RoundLogo.png differ diff --git a/docs/source/_static/assets/RoundLogoLight.png b/docs/source/_static/assets/RoundLogoLight.png new file mode 100644 index 0000000..ee460b2 Binary files /dev/null and b/docs/source/_static/assets/RoundLogoLight.png differ diff --git a/docs/source/_static/assets/calibration_small.mp4 b/docs/source/_static/assets/calibration_small.mp4 new file mode 100644 index 0000000..eed1054 Binary files /dev/null and b/docs/source/_static/assets/calibration_small.mp4 differ diff --git a/docs/source/_static/assets/colorPicker.mp4 b/docs/source/_static/assets/colorPicker.mp4 new file mode 100644 index 0000000..500b657 Binary files /dev/null and b/docs/source/_static/assets/colorPicker.mp4 differ diff --git a/docs/source/_static/assets/groupingSorting.mp4 b/docs/source/_static/assets/groupingSorting.mp4 new file mode 100644 index 0000000..e8f3b1d Binary files /dev/null and b/docs/source/_static/assets/groupingSorting.mp4 differ diff --git a/docs/source/_static/assets/import-export-settings.mp4 b/docs/source/_static/assets/import-export-settings.mp4 new file mode 100644 index 0000000..d020f98 Binary files /dev/null and b/docs/source/_static/assets/import-export-settings.mp4 differ diff --git a/docs/source/_static/assets/logGui.mp4 b/docs/source/_static/assets/logGui.mp4 new file mode 100644 index 0000000..333b04f Binary files /dev/null and b/docs/source/_static/assets/logGui.mp4 differ diff --git a/docs/source/_static/assets/objdetectFiltering.mp4 b/docs/source/_static/assets/objdetectFiltering.mp4 new file mode 100644 index 0000000..4a328df Binary files /dev/null and b/docs/source/_static/assets/objdetectFiltering.mp4 differ diff --git a/docs/source/_static/assets/offsetandmultiple.mp4 b/docs/source/_static/assets/offsetandmultiple.mp4 new file mode 100644 index 0000000..555c094 Binary files /dev/null and b/docs/source/_static/assets/offsetandmultiple.mp4 differ diff --git a/docs/source/_static/assets/simaimandrange.mp4 b/docs/source/_static/assets/simaimandrange.mp4 new file mode 100644 index 0000000..a481540 Binary files /dev/null and b/docs/source/_static/assets/simaimandrange.mp4 differ diff --git a/docs/source/_static/assets/tuningHueSatVal.mp4 b/docs/source/_static/assets/tuningHueSatVal.mp4 new file mode 100644 index 0000000..23bf600 Binary files /dev/null and b/docs/source/_static/assets/tuningHueSatVal.mp4 differ diff --git a/docs/source/_static/css/pv-icons.css b/docs/source/_static/css/pv-icons.css new file mode 100644 index 0000000..0dd7bbc --- /dev/null +++ b/docs/source/_static/css/pv-icons.css @@ -0,0 +1,17 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ + +@font-face { + font-family: FontAwesome; + src: url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713); + src: url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"), url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"), url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"), url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"), url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg"); + font-weight: 400; + font-style:normal +} + +.code-block-caption>.headerlink, dl dt>.headerlink, h1>.headerlink, h2>.headerlink, h3>.headerlink, h4>.headerlink, h5>.headerlink, h6>.headerlink, p.caption>.headerlink, table>caption>.headerlink { + font-family: FontAwesome; + font-size: 0.75em; +} diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html new file mode 100644 index 0000000..0a48581 --- /dev/null +++ b/docs/source/_templates/layout.html @@ -0,0 +1,74 @@ +{# Import the theme's layout. #} +{% extends '!layout.html' %} + +{%- block extrahead %} + + +{# Call the parent block #} +{{ super() }} +{% endblock %} + +{%- block extrafooter %} +{# Add custom things to the head HTML tag #} + +
+ + +
+ +
+ + +
+ + + +{# Call the parent block #} +{{ super() }} +{%- endblock %} diff --git a/docs/source/assets/PhotonVision-Header-noBG.png b/docs/source/assets/PhotonVision-Header-noBG.png new file mode 100644 index 0000000..64a7f26 Binary files /dev/null and b/docs/source/assets/PhotonVision-Header-noBG.png differ diff --git a/docs/source/assets/PhotonVision-Header-onWhite.png b/docs/source/assets/PhotonVision-Header-onWhite.png new file mode 100644 index 0000000..d35c18c Binary files /dev/null and b/docs/source/assets/PhotonVision-Header-onWhite.png differ diff --git a/docs/source/assets/RectLogo.png b/docs/source/assets/RectLogo.png new file mode 100644 index 0000000..340f6de Binary files /dev/null and b/docs/source/assets/RectLogo.png differ diff --git a/docs/source/assets/RoundLogo.png b/docs/source/assets/RoundLogo.png new file mode 100644 index 0000000..e6be308 Binary files /dev/null and b/docs/source/assets/RoundLogo.png differ diff --git a/docs/source/assets/RoundLogoWhite.png b/docs/source/assets/RoundLogoWhite.png new file mode 100644 index 0000000..ee460b2 Binary files /dev/null and b/docs/source/assets/RoundLogoWhite.png differ diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..8de2d44 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,153 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os + +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- Project information ----------------------------------------------------- + +project = "PhotonVision" +copyright = "2024, PhotonVision" +author = "Banks Troutman, Matt Morley" + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx_rtd_theme", + "sphinx.ext.autosectionlabel", + "sphinx.ext.todo", + "sphinx_tabs.tabs", + "notfound.extension", + "sphinxext.remoteliteralinclude", + "sphinxext.opengraph", + "sphinxcontrib.ghcontributors", + "sphinx_design", + "myst_parser", + "sphinx.ext.mathjax", + "sphinx.ext.graphviz", +] + +# Configure OpenGraph support + +ogp_site_url = "https://docs.photonvision.org/en/latest/" +ogp_site_name = "PhotonVision Documentation" +ogp_image = "https://raw.githubusercontent.com/PhotonVision/photonvision-docs/master/source/assets/RectLogo.png" + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# Enable hover content on glossary term +hoverxref_roles = ["term"] + +# Autosection labels prefix document path and filename +autosectionlabel_prefix_document = True + +# -- Options for HTML output ------------------------------------------------- + +html_title = "PhotonVision Docs" + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "furo" +html_favicon = "assets/RoundLogo.png" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +source_suffix = [".rst", ".md"] + + +def setup(app): + app.add_css_file("css/pv-icons.css") + + +pygments_style = "sphinx" + +html_theme_options = { + "sidebar_hide_name": True, + "light_logo": "assets/PhotonVision-Header-onWhite.png", + "dark_logo": "assets/PhotonVision-Header-noBG.png", + "light_css_variables": { + "font-stack": "-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, roboto, noto, arial, sans-serif;", + "admonition-font-size": "1rem", + "admonition-title-font-size": "1rem", + "color-background-primary": "#ffffff", + "color-background-secondary": "#f7f7f7", + "color-background-hover": "#efeff400", + "color-background-hover--transparent": "#efeff400", + "color-brand-primary": "#006492", + "color-brand-content": "#006492", + "color-foreground-primary": "#2d2d2d", + "color-foreground-secondary": "#39a4d5", + "color-foreground-muted": "#2d2d2d", + "color-foreground-border": "#ffffff", + "color-background-border": "ffffff", + "color-api-overall": "#101010", + }, + "dark_css_variables": { + "color-background-primary": "#242c37", + "color-background-secondary": "#006492", + "color-background-hover": "#efeff400", + "color-background-hover--transparent": "#efeff400", + "color-brand-primary": "#ffd843", + "color-brand-secondary": "#39a4d5", + "color-brand-content": "#ffd843", + "color-foreground-primary": "#ffffff", + "color-foreground-secondary": "#ffffff", + "color-foreground-muted": "#ffffff", + "color-foreground-border": "transparent", + "color-background-border": "transparent", + "color-api-overall": "#101010", + "color-inline-code-background": "#0d0d0d", + }, + "footer_icons": [ + { + "name": "GitHub", + "url": "https://github.com/photonvision/photonvision", + "html": """ + + + + """, + "class": "", + }, + ], +} + +suppress_warnings = ["epub.unknown_project_files"] + +sphinx_tabs_valid_builders = ["epub", "linkcheck"] + +# -- Options for linkcheck ------------------------------------------------- + +# Excluded links for linkcheck +# These should be periodically checked by hand to ensure that they are still functional +linkcheck_ignore = [R"https://www.raspberrypi.com/software/", R"http://10\..+"] + +token = os.environ.get("GITHUB_TOKEN", None) +if token: + linkcheck_auth = [(R"https://github.com/.+", token)] + +# MyST configuration (https://myst-parser.readthedocs.io/en/latest/configuration.html) +myst_enable_extensions = ["colon_fence"] diff --git a/docs/source/docs/additional-resources/best-practices.md b/docs/source/docs/additional-resources/best-practices.md new file mode 100644 index 0000000..7dd11ec --- /dev/null +++ b/docs/source/docs/additional-resources/best-practices.md @@ -0,0 +1,29 @@ +# Best Practices For Competition + +## Before Competition + +- Ensure you have spares of the relevant electronics if you can afford it (switch, coprocessor, cameras, etc.). +- Download the latest release .jar onto your computer and update your Pi if necessary (only update if the release is labeled "critical" or similar, we do not recommend updating right before an event in case there are unforeseen bugs). +- Test out PhotonVision at your home setup. +- Ensure that you have set up SmartDashboard / Shuffleboard to view your camera streams during matches. +- Follow all the recommendations under the Networking section in installation (network switch and static IP). +- Use high quality ethernet cables that have been rigorously tested. +- Set up port forwarding using the guide in the Networking section in installation. + +## During the Competition + +- Make sure you take advantage of the field calibration time given at the start of the event: + - Bring your robot to the field at the allotted time. + - Turn on your robot and pull up the dashboard on your driver station. + - Point your robot at the AprilTags(s) and ensure you get a consistent tracking (you hold one AprilTag consistently, the ceiling lights aren't detected, etc.). + - If you have problems with your pipeline, go to the pipeline tuning section and retune the pipeline using the guide there. + - Move the robot close, far, angled, and around the field to ensure no extra AprilTags are found. + - Go to a practice match to ensure everything is working correctly. +- After field calibration, use the "Export Settings" button in the "Settings" page to create a backup. + - Do this for each coprocessor on your robot that runs PhotonVision, and name your exports with meaningful names. + - This will contain camera information/calibration, pipeline information, network settings, etc. + - In the event of software/hardware failures (IE lost SD Card, broken device), you can then use the "Import Settings" button and select "All Settings" to restore your settings. + - This effectively works as a snapshot of your PhotonVision data that can be restored at any point. +- Before every match, check the ethernet connection going into your coprocessor and that it is seated fully. +- Ensure that exposure is as low as possible and that you don't have the dashboard up when you don't need it to reduce bandwidth. +- Stream at as low of a resolution as possible while still detecting AprilTags to stay within field bandwidth limits. diff --git a/docs/source/docs/additional-resources/config.md b/docs/source/docs/additional-resources/config.md new file mode 100644 index 0000000..e29fb1e --- /dev/null +++ b/docs/source/docs/additional-resources/config.md @@ -0,0 +1,50 @@ +# Filesystem Directory + +PhotonVision stores and loads settings in the {code}`photonvision_config` directory, in the same folder as the PhotonVision JAR is stored. On supported hardware, this is in the {code}`/opt/photonvision` directory. The contents of this directory can be exported as a zip archive from the settings page of the interface, under "export settings". This export will contain everything detailed below. These settings can later be uploaded using "import settings", to restore configurations from previous backups. + +## Directory Structure + +The directory structure is outlined below. + +```{image} images/configDir.png +:alt: Config directory structure +:width: 600 +``` + +- calibImgs + - Images saved from the last run of the calibration routine +- cameras + - Contains a subfolder for each camera. This folder contains the following files: + - pipelines folder, which contains a {code}`json` file for each user-created pipeline. + - config.json, which contains all camera-specific configuration. This includes FOV, pitch, current pipeline index, and calibration data + - drivermode.json, which contains settings for the driver mode pipeline +- imgSaves + - Contains images saved with the input/output save commands. +- logs + - Contains timestamped logs in the format {code}`photonvision-YYYY-MM-D_HH-MM-SS.log`. These timestamps will likely be significantly behind the real time. Coprocessors on the robot have no way to get current time. +- hardwareSettings.json + - Contains hardware settings. Currently this includes only the LED brightness. +- networkSettings.json + - Contains network settings, including team number (or remote network tables address), static/dynamic settings, and hostname. + +## Importing and Exporting Settings + +The entire settings directory can be exported as a ZIP archive from the settings page. + +```{raw} html + +``` + +A variety of files can be imported back into PhotonVision: + +- ZIP Archive ({code}`.zip`) + - Useful for restoring a full configuration from a different PhotonVision instance. +- Single Config File + - Currently-supported Files + - {code}`hardwareConfig.json` + - {code}`hardwareSettings.json` + - {code}`networkSettings.json` + - Useful for simple hardware or network configuration tasks without overwriting all settings. diff --git a/docs/source/docs/additional-resources/images/configDir.png b/docs/source/docs/additional-resources/images/configDir.png new file mode 100644 index 0000000..4911794 Binary files /dev/null and b/docs/source/docs/additional-resources/images/configDir.png differ diff --git a/docs/source/docs/additional-resources/nt-api.md b/docs/source/docs/additional-resources/nt-api.md new file mode 100644 index 0000000..e89c26b --- /dev/null +++ b/docs/source/docs/additional-resources/nt-api.md @@ -0,0 +1,70 @@ +# NetworkTables API + +## About + +:::{warning} +PhotonVision interfaces with PhotonLib, our vendor dependency, using NetworkTables. If you are running PhotonVision on a robot (ie. with a RoboRIO), you should **turn the NetworkTables server switch (in the settings tab) off** in order to get PhotonLib to work. Also ensure that you set your team number. The NetworkTables server should only be enabled if you know what you're doing! +::: + +## API + +:::{warning} +NetworkTables is not a supported setup/viable option when using PhotonVision as we only send one target at a time (this is problematic when using AprilTags, which will return data from multiple tags at once). We recommend using PhotonLib. +::: + +The tables below contain the the name of the key for each entry that PhotonVision sends over the network and a short description of the key. The entries should be extracted from a subtable with your camera's nickname (visible in the PhotonVision UI) under the main `photonvision` table. + +### Getting Target Information + +| Key | Type | Description | +| --------------- | ---------- | ------------------------------------------------------------------------ | +| `rawBytes` | `byte[]` | A byte-packed string that contains target info from the same timestamp. | +| `latencyMillis` | `double` | The latency of the pipeline in milliseconds. | +| `hasTarget` | `boolean` | Whether the pipeline is detecting targets or not. | +| `targetPitch` | `double` | The pitch of the target in degrees (positive up). | +| `targetYaw` | `double` | The yaw of the target in degrees (positive right). | +| `targetArea` | `double` | The area (percent of bounding box in screen) as a percent (0-100). | +| `targetSkew` | `double` | The skew of the target in degrees (counter-clockwise positive). | +| `targetPose` | `double[]` | The pose of the target relative to the robot (x, y, z, qw, qx, qy, qz) | +| `targetPixelsX` | `double` | The target crosshair location horizontally, in pixels (origin top-right) | +| `targetPixelsY` | `double` | The target crosshair location vertically, in pixels (origin top-right) | + +### Changing Settings + +| Key | Type | Description | +| --------------- | --------- | --------------------------- | +| `pipelineIndex` | `int` | Changes the pipeline index. | +| `driverMode` | `boolean` | Toggles driver mode. | + +### Saving Images + +PhotonVision can save images to file on command. The image is saved when PhotonVision detects the command went from `false` to `true`. + +PhotonVision will automatically set these back to `false` after 500ms. + +Be careful saving images rapidly - it will slow vision processing performance and take up disk space very quickly. + +Images are returned as part of the .zip package from the "Export" operation in the Settings tab. + +| Key | Type | Description | +| ------------------ | --------- | ------------------------------------------------- | +| `inputSaveImgCmd` | `boolean` | Triggers saving the current input image to file. | +| `outputSaveImgCmd` | `boolean` | Triggers saving the current output image to file. | + +:::{warning} +If you manage to make calls to these commands faster than 500ms (between calls), additional photos will not be captured. +::: + +### Global Entries + +These entries are global, meaning that they should be called on the main `photonvision` table. + +| Key | Type | Description | +| --------- | ----- | -------------------------------------------------------- | +| `ledMode` | `int` | Sets the LED Mode (-1: default, 0: off, 1: on, 2: blink) | + +:::{warning} +Setting the LED mode to -1 (default) when `multiple` cameras are connected may result in unexpected behavior. {ref}`This is a known limitation of PhotonVision. ` + +Single camera operation should work without issue. +::: diff --git a/docs/source/docs/advanced-installation/images/gh_actions_1.png b/docs/source/docs/advanced-installation/images/gh_actions_1.png new file mode 100644 index 0000000..babf9ea Binary files /dev/null and b/docs/source/docs/advanced-installation/images/gh_actions_1.png differ diff --git a/docs/source/docs/advanced-installation/images/gh_actions_2.png b/docs/source/docs/advanced-installation/images/gh_actions_2.png new file mode 100644 index 0000000..f2fcfe8 Binary files /dev/null and b/docs/source/docs/advanced-installation/images/gh_actions_2.png differ diff --git a/docs/source/docs/advanced-installation/images/gh_actions_3.png b/docs/source/docs/advanced-installation/images/gh_actions_3.png new file mode 100644 index 0000000..18ae952 Binary files /dev/null and b/docs/source/docs/advanced-installation/images/gh_actions_3.png differ diff --git a/docs/source/docs/advanced-installation/index.md b/docs/source/docs/advanced-installation/index.md new file mode 100644 index 0000000..377414f --- /dev/null +++ b/docs/source/docs/advanced-installation/index.md @@ -0,0 +1,18 @@ +# Advanced Installation + +This page will help you install PhotonVision on non-supported coprocessor. + +## Step 1: Software Install + +This section will walk you through how to install PhotonVision on your coprocessor. Your coprocessor is the device that has the camera and you are using to detect targets (ex. if you are using a Limelight / Raspberry Pi, that is your coprocessor and you should follow those instructions). + +:::{warning} +You only need to install PhotonVision on the coprocessor/device that is being used to detect targets, you do NOT need to install it on the device you use to view the webdashboard. All you need to view the webdashboard is for a device to be on the same network as your vision coprocessor and an internet browser. +::: + +```{toctree} +:maxdepth: 3 + +sw_install/index +prerelease-software +``` diff --git a/docs/source/docs/advanced-installation/prerelease-software.md b/docs/source/docs/advanced-installation/prerelease-software.md new file mode 100644 index 0000000..1122ff9 --- /dev/null +++ b/docs/source/docs/advanced-installation/prerelease-software.md @@ -0,0 +1,23 @@ +# Installing Pre-Release Versions + +Pre-release/development version of PhotonVision can be tested by installing/downloading artifacts from Github Actions (see below), which are built automatically on commits to open pull requests and to PhotonVision's `master` branch, or by {ref}`compiling PhotonVision locally `. + +:::{warning} +If testing a pre-release version of PhotonVision with a robot, PhotonLib must be updated to match the version downloaded! If not, packet schema definitions may not match and unexpected things will occur. To update PhotonLib, refer to {ref}`installing specific version of PhotonLib`. +::: + +GitHub Actions builds pre-release version of PhotonVision automatically on PRs and on each commit merged to master. To test a particular commit to master, navigate to the [PhotonVision commit list](https://github.com/PhotonVision/photonvision/commits/master/) and click on the check mark (below). Scroll to "Build / Build fat JAR - PLATFORM", click details, and then summary. From here, JAR and image files can be downloaded to be flashed or uploaded using "Offline Update". + +```{image} images/gh_actions_1.png +:alt: Github Actions Badge +``` + +```{image} images/gh_actions_2.png +:alt: Github Actions artifact list +``` + +Built JAR files (but not image files) can also be downloaded from PRs before they are merged. Navigate to the PR in GitHub, and select Checks at the top. Click on "Build" to display the same artifact list as above. + +```{image} images/gh_actions_3.png +:alt: Github Actions artifacts from PR +``` diff --git a/docs/source/docs/advanced-installation/sw_install/advanced-cmd.md b/docs/source/docs/advanced-installation/sw_install/advanced-cmd.md new file mode 100644 index 0000000..ba97421 --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/advanced-cmd.md @@ -0,0 +1,56 @@ +# Advanced Command Line Usage + +PhotonVision exposes some command line options which may be useful for customizing execution on Debian-based installations. + +## Running a JAR File + +Assuming `java` has been installed, and the appropriate environment variables have been set upon installation (a package manager like `apt` should automatically set these), you can use `java -jar` to run a JAR file. If you downloaded the latest stable JAR of PhotonVision from the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases), you can run the following to start the program: + +```bash +java -jar /path/to/photonvision/photonvision.jar +``` + +## Updating a JAR File + +When you need to update your JAR file, run the following: + +```bash +wget https://git.io/JqkQ9 -O update.sh +sudo chmod +x update.sh +sudo ./update.sh +sudo reboot now +``` + +## Creating a `systemd` Service + +You can also create a systemd service that will automatically run on startup. To do so, first navigate to `/lib/systemd/system`. Create a file called `photonvision.service` (or name it whatever you want) using `touch photonvision.service`. Then open this file in the editor of your choice and paste the following text: + +``` +[Unit] +Description=Service that runs PhotonVision + +[Service] +WorkingDirectory=/path/to/photonvision +# Optional: run photonvision at "nice" -10, which is higher priority than standard +# Nice=-10 +ExecStart=/usr/bin/java -jar /path/to/photonvision/photonvision.jar + +[Install] +WantedBy=multi-user.target +``` + +Then copy the `.service` file to `/etc/systemd/system/` using `cp photonvision.service /etc/systemd/system/photonvision.service`. Then modify the file to have `644` permissions using `chmod 644 /etc/systemd/system/photonvision.service`. + +:::{note} +Many ARM processors have a big.LITTLE architecture where some of the CPU cores are more powerful than others. On this type of architecture, you may get more consistent performance by limiting which cores PhotonVision can use. To do this, add the parameter `AllowedCPUs` to the systemd service file in the `[Service]` section. + +For instance, for an Orange Pi 5, cores 4 through 7 are the fast ones, and you can target those cores with the line `AllowedCPUs=4-7`. +::: + +## Installing the `systemd` Service + +To install the service, simply run `systemctl enable photonvision.service`. + +:::{note} +It is recommended to reload configurations by running `systemctl daemon-reload`. +::: diff --git a/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json b/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json new file mode 100644 index 0000000..5465d0d --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json @@ -0,0 +1,9 @@ +{ + "deviceName" : "Limelight 2+", + "supportURL" : "https://limelightvision.io", + "ledPins" : [ 13, 18 ], + "ledsCanDim" : true, + "ledPWMRange" : [ 0, 100 ], + "ledPWMFrequency" : 30000, + "vendorFOV" : 75.76079874010732 +} diff --git a/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json b/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json new file mode 100644 index 0000000..b38176e --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json @@ -0,0 +1,7 @@ +{ + "deviceName" : "Limelight 2", + "supportURL" : "https://limelightvision.io", + "ledPins" : [ 17, 18 ], + "ledsCanDim" : false, + "vendorFOV" : 75.76079874010732 +} diff --git a/docs/source/docs/advanced-installation/sw_install/images/angryIP.png b/docs/source/docs/advanced-installation/sw_install/images/angryIP.png new file mode 100644 index 0000000..20247d2 Binary files /dev/null and b/docs/source/docs/advanced-installation/sw_install/images/angryIP.png differ diff --git a/docs/source/docs/advanced-installation/sw_install/images/nano.png b/docs/source/docs/advanced-installation/sw_install/images/nano.png new file mode 100644 index 0000000..b5ba54b Binary files /dev/null and b/docs/source/docs/advanced-installation/sw_install/images/nano.png differ diff --git a/docs/source/docs/advanced-installation/sw_install/index.md b/docs/source/docs/advanced-installation/sw_install/index.md new file mode 100644 index 0000000..cc15c6f --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/index.md @@ -0,0 +1,21 @@ +# Software Installation + +## Desktop Environments + +```{toctree} +:maxdepth: 1 + +windows-pc +linux-pc +mac-os +``` + +## Other + +```{toctree} +:maxdepth: 1 + +other-coprocessors +advanced-cmd +romi +``` diff --git a/docs/source/docs/advanced-installation/sw_install/linux-pc.md b/docs/source/docs/advanced-installation/sw_install/linux-pc.md new file mode 100644 index 0000000..7299344 --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/linux-pc.md @@ -0,0 +1,47 @@ +# Linux PC Installation + +PhotonVision may be run on a Debian-based Linux Desktop PC for basic testing and evaluation. + +:::{note} +You do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi). +::: + +## Installing Java + +PhotonVision requires a JDK installed and on the system path. JDK 17 is needed (different versions will not work). If you don't have JDK 17 already, run the following to install it: + +``` +$ sudo apt-get install openjdk-17-jdk +``` + +:::{warning} +Using a JDK other than JDK17 will cause issues when running PhotonVision and is not supported. +::: + +## Downloading the Latest Stable Release of PhotonVision + +Go to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the relevant .jar file for your coprocessor. + +:::{note} +If your coprocessor has a 64 bit ARM based CPU architecture (OrangePi, Raspberry Pi, etc.), download the LinuxArm64.jar file. + +If your coprocessor has an 64 bit x86 based CPU architecture (Mini PC, laptop, etc.), download the Linuxx64.jar file. +::: + +:::{warning} +Be careful to pick the latest stable release. "Draft" or "Pre-Release" versions are not stable and often have bugs. +::: + +## Running PhotonVision + +To run PhotonVision, open a terminal window of your choice and run the following command: + +``` +$ java -jar /path/to/photonvision/photonvision-xxx.jar +``` + +If your computer has a compatible webcam connected, PhotonVision should startup without any error messages. If there are error messages, your webcam isn't supported or another issue has occurred. If it is the latter, please open an issue on the [PhotonVision issues page](https://github.com/PhotonVision/photonvision/issues). + +## Accessing the PhotonVision Interface + +Once the Java backend is up and running, you can access the main vision interface by navigating to `localhost:5800` inside your browser. diff --git a/docs/source/docs/advanced-installation/sw_install/mac-os.md b/docs/source/docs/advanced-installation/sw_install/mac-os.md new file mode 100644 index 0000000..55431b4 --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/mac-os.md @@ -0,0 +1,53 @@ +# Mac OS Installation + +:::{warning} +Due to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, the PhotonVision server backend may have issues running macOS. +::: + +:::{note} +You do not need to install PhotonVision on a Mac in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi). +::: + +VERY Limited macOS support is available. + +## Installing Java + +PhotonVision requires a JDK installed and on the system path. JDK 17 is needed (different versions will not work). You may already have this if you have installed WPILib 2025+. If not, [download and install it from here](https://adoptium.net/temurin/releases?version=17). + +:::{warning} +Using a JDK other than JDK17 will cause issues when running PhotonVision and is not supported. +::: + +## Downloading the Latest Stable Release of PhotonVision + +Go to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the relevant .jar file for your coprocessor. + +:::{note} +If you have an M1/M2 Mac, download the macarm64.jar file. + +If you have an Intel based Mac, download the macx64.jar file. +::: + +:::{warning} +Be careful to pick the latest stable release. "Draft" or "Pre-Release" versions are not stable and often have bugs. +::: + +## Running PhotonVision + +To run PhotonVision, open a terminal window of your choice and run the following command: + +``` +$ java -jar /path/to/photonvision/photonvision-xxx.jar +``` + +:::{warning} +Due to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, the PhotonVision using test mode is all that is known to work currently. +::: + +## Accessing the PhotonVision Interface + +Once the Java backend is up and running, you can access the main vision interface by navigating to `localhost:5800` inside your browser. + +:::{warning} +Due to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, it is unlikely any streams will open from real webcams. +::: diff --git a/docs/source/docs/advanced-installation/sw_install/other-coprocessors.md b/docs/source/docs/advanced-installation/sw_install/other-coprocessors.md new file mode 100644 index 0000000..6afb65f --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/other-coprocessors.md @@ -0,0 +1,39 @@ +# Other Debian-Based Co-Processor Installation + +:::{warning} +Working with unsupported coprocessors requires some level of "know how" of your system. The install script has only been tested on Debian/Raspberry Pi OS Buster and Ubuntu Bionic. If any issues arise with your specific OS, please open an issue on our [issues page](https://github.com/PhotonVision/photonvision/issues). +::: + +:::{note} +We'd love to have your input! If you get PhotonVision working on another coprocessor, consider documenting your steps and submitting a [docs issue](https://github.com/PhotonVision/photonvision-docs/issues)., [pull request](https://github.com/PhotonVision/photonvision-docs/pulls) , or [ping us on Discord](https://discord.com/invite/wYxTwym). For example, Limelight and Romi install instructions came about because someone spent the time to figure it out, and did a writeup. +::: + +## Installing PhotonVision + +We provide an [install script](https://git.io/JJrEP) for other Debian-based systems (with `apt`) that will automatically install PhotonVision and make sure that it runs on startup. + +```bash +$ wget https://git.io/JJrEP -O install.sh +$ sudo chmod +x install.sh +$ sudo ./install.sh +$ sudo reboot now +``` + +:::{note} +Your co-processor will require an Internet connection for this process to work correctly. +::: + +For installation on any other co-processors, we recommend reading the {ref}`advanced command line documentation `. + +## Updating PhotonVision + +PhotonVision can be updated by downloading the latest jar file, copying it onto the processor, and restarting the service. + +For example, from another computer, run the following commands. Substitute the correct username for "\[user\]" ( Provided images use username "pi") + +```bash +$ scp [jar name].jar [user]@photonvision.local:~/ +$ ssh [user]@photonvision.local +$ sudo mv [jar name].jar /opt/photonvision/photonvision.jar +$ sudo systemctl restart photonvision.service +``` diff --git a/docs/source/docs/advanced-installation/sw_install/romi.md b/docs/source/docs/advanced-installation/sw_install/romi.md new file mode 100644 index 0000000..6a26fff --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/romi.md @@ -0,0 +1,43 @@ +# Romi Installation + +The [Romi](https://docs.wpilib.org/en/latest/docs/romi-robot/index.html) is a small robot that can be controlled with the WPILib software. The main controller is a Raspberry Pi that must be imaged with [WPILibPi](https://docs.wpilib.org/en/latest/docs/romi-robot/imaging-romi.html) . + +## Installation + +The WPILibPi image includes FRCVision, which reserves USB cameras; to use PhotonVision, we need to edit the `/home/pi/runCamera` script to disable it. First we will need to make the file system writeable; the easiest way to do this is to go to `10.0.0.2` and choose "Writable" at the top. + +SSH into the Raspberry Pi (using Windows command line, or a tool like [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/) ) at the Romi's default address `10.0.0.2`. The default user is `pi`, and the password is `raspberry`. + +:::.. The following paragraph can be restored when WPILibPi becomes compatible with the current version of PhotonVision. +:::.. Follow the process for installing PhotonVision on {ref}`"Other Debian-Based Co-Processor Installation" `. As it mentions, this will require an internet connection so connecting the Raspberry Pi to an internet-connected router via an Ethernet cable will be the easiest solution. The pi must remain writable while you are following these steps! + +:::..Temporary instructions explaining how to install the older version of PhotonVision on a Romi. Remove when no longer needed. +:::{attention} +The version of WPILibPi for the Romi is 2023.2.1, which is not compatible with the current version of PhotonVision. **If you are using WPILibPi 2023.2.1 on your Romi, you must install PhotonVision v2023.4.2 or earlier!** + +To install a compatible version of PhotonVision, enter these commands in the SSH terminal connected to the Raspberry Pi. This will download and run the install script, which will intall PhotonVision on your Raspberry Pi and configure it to run at startup. + +```bash +$ wget https://git.io/JJrEP -O install.sh +$ sudo chmod +x install.sh +$ sudo ./install.sh -v 2023.4.2 +``` +The install script requires an internet connection, so connecting the Raspberry Pi to an internet-connected router via an Ethernet cable will be the easiest solution. The pi must remain writable while you are following these steps! +::: +:::..End of temporary instructions. + +Next, from the SSH terminal, run `sudo nano /home/pi/runCamera` then arrow down to the start of the exec line and press "Enter" to add a new line. Then add `#` before the exec command to comment it out. Then, arrow up to the new line and type `sleep 10000`. Hit "Ctrl + O" and then "Enter" to save the file. Finally press "Ctrl + X" to exit nano. Now, reboot the Romi by typing `sudo reboot now`. + +```{image} images/nano.png + +``` + +After the Romi reboots, you should be able to open the PhotonVision UI at: [`http://10.0.0.2:5800/`](http://10.0.0.2:5800/). From here, you can adjust {ref}`Settings ` and configure {ref}`Pipelines `. + +:::{warning} +In order for settings, logs, etc. to be saved / take effect, ensure that PhotonVision is in writable mode. +::: + +:::{attention} +When using an older version of PhotonVision, the user interface and features may be different than what appears in the online documentation. The [Documentation](http://10.0.0.2:5800/#/docs) link in the User Interface will open a bundled version of the documentation that matches the PhotonVision version running on your coprocessor. +::: diff --git a/docs/source/docs/advanced-installation/sw_install/windows-pc.md b/docs/source/docs/advanced-installation/sw_install/windows-pc.md new file mode 100644 index 0000000..d152cef --- /dev/null +++ b/docs/source/docs/advanced-installation/sw_install/windows-pc.md @@ -0,0 +1,45 @@ +# Windows PC Installation + +PhotonVision may be run on a Windows Desktop PC for basic testing and evaluation. + +:::{note} +You do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi). +::: + +## Install Bonjour + +Bonjour provides more stable networking when using Windows PCs. Install [Bonjour here](https://support.apple.com/downloads/DL999/en_US/BonjourPSSetup.exe) before continuing to ensure a stable experience while using PhotonVision. + +## Installing Java + +PhotonVision requires a JDK installed and on the system path. **JDK 17 is needed. Windows Users must use the JDK that ships with WPILib.** [Download and install it from here.](https://github.com/wpilibsuite/allwpilib/releases/tag/v2025.1.1-beta-2) Either ensure the only Java on your PATH is the WPILIB Java or specify it to gradle with `-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk`: + +``` +> ./gradlew run "-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk" +``` + +:::{warning} +Using a JDK other than WPILIB's JDK17 will cause issues when running PhotonVision and is not supported. +::: + +## Downloading the Latest Stable Release of PhotonVision + +Go to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the winx64.jar file. + +## Running PhotonVision + +To run PhotonVision, open a terminal window of your choice and run the following command: + +``` +> java -jar C:\path\to\photonvision\NAME OF JAR FILE GOES HERE.jar +``` + +If your computer has a compatible webcam connected, PhotonVision should startup without any error messages. If there are error messages, your webcam isn't supported or another issue has occurred. If it is the latter, please open an issue on the [PhotonVision issues page](https://github.com/PhotonVision/photonvision/issues). + +:::{warning} +Using an integrated laptop camera may cause issues when trying to run PhotonVision. If you are unable to run PhotonVision on a laptop with an integrated camera, try disabling the camera's driver in Windows Device Manager. +::: + +## Accessing the PhotonVision Interface + +Once the Java backend is up and running, you can access the main vision interface by navigating to `localhost:5800` inside your browser. diff --git a/docs/source/docs/apriltag-pipelines/2D-tracking-tuning.md b/docs/source/docs/apriltag-pipelines/2D-tracking-tuning.md new file mode 100644 index 0000000..3a00e3d --- /dev/null +++ b/docs/source/docs/apriltag-pipelines/2D-tracking-tuning.md @@ -0,0 +1,54 @@ +# 2D AprilTag Tuning / Tracking + +## Tracking AprilTags + +Before you get started tracking AprilTags, ensure that you have followed the previous sections on installation, wiring and networking. Next, open the Web UI, go to the top right card, and switch to the "AprilTag" or "Aruco" type. You should see a screen similar to the one below. + +```{image} images/apriltag.png +:align: center +``` + +You are now able to detect and track AprilTags in 2D (yaw, pitch, roll, etc.). In order to get 3D data from your AprilTags, please see {ref}`here. ` + +## Tuning AprilTags + +AprilTag pipelines come with reasonable defaults to get you up and running with tracking. However, in order to optimize your performance and accuracy, you must tune your AprilTag pipeline using the settings below. Note that the settings below are different between the AprilTag and Aruco detectors but the concepts are the same. + +```{image} images/apriltag-tune.png +:align: center +:scale: 45 % +``` + +### Target Family + +Target families are defined by two numbers (before and after the h). The first number is the number of bits the tag is able to encode (which means more tags are available in the respective family) and the second is the hamming distance. Hamming distance describes the ability for error correction while identifying tag ids. A high hamming distance generally means that it will be easier for a tag to be identified even if there are errors. However, as hamming distance increases, the number of available tags decreases. The 2024 FRC game will be using 36h11 tags, which can be found [here](https://github.com/AprilRobotics/apriltag-imgs/tree/master/tag36h11). + +### Decimate + +Decimation (also known as down-sampling) is the process of reducing the sampling frequency of a signal (in our case, the image). Increasing decimate will lead to an increased detection rate while decreasing detection distance. We recommend keeping this at the default value. + +### Blur + +This controls the sigma of Gaussian blur for tag detection. In clearer terms, increasing blur will make the image blurrier, decreasing it will make it closer to the original image. We strongly recommend that you keep blur to a minimum (0) due to it's high performance intensity unless you have an extremely noisy image. + +### Threads + +Threads refers to the threads within your coprocessor's CPU. The theoretical maximum is device dependent, but we recommend that users to stick to one less than the amount of CPU threads that your coprocessor has. Increasing threads will increase performance at the cost of increased CPU load, temperature increase, etc. It may take some experimentation to find the most optimal value for your system. + +### Refine Edges + +The edges of the each polygon are adjusted to "snap to" high color differences surrounding it. It is recommended to use this in tandem with decimate as it can increase the quality of the initial estimate. + +### Pose Iterations + +Pose iterations represents the amount of iterations done in order for the AprilTag algorithm to converge on its pose solution(s). A smaller number between 0-100 is recommended. A smaller amount of iterations cause a more noisy set of poses when looking at the tag straight on, while higher values much more consistently stick to a (potentially wrong) pair of poses. WPILib contains many useful filter classes in order to account for a noisy tag reading. + +### Max Error Bits + +Max error bits, also known as hamming distance, is the number of positions at which corresponding pieces of data / tag are different. Put more generally, this is the number of bits (think of these as squares in the tag) that need to be changed / corrected in the tag to correctly detect it. A higher value means that more tags will be detected while a lower value cuts out tags that could be "questionable" in terms of detection. + +We recommend a value of 0 for the 16h5 and at most 3 for the 36h11 family. + +### Decision Margin Cutoff + +The decision margin cutoff is how much “margin” the detector has left before it rejects a tag; increasing this rejects poorer tags. We recommend you keep this value around a 30. diff --git a/docs/source/docs/apriltag-pipelines/3D-tracking.md b/docs/source/docs/apriltag-pipelines/3D-tracking.md new file mode 100644 index 0000000..416d12a --- /dev/null +++ b/docs/source/docs/apriltag-pipelines/3D-tracking.md @@ -0,0 +1,13 @@ +# 3D Tracking + +3D AprilTag tracking will allow you to track the real-world position and rotation of a tag relative to the camera's image sensor. This is useful for robot pose estimation and other applications like autonomous scoring. In order to use 3D tracking, you must first {ref}`calibrate your camera `. Once you have, you need to enable 3D mode in the UI and you will now be able to get 3D pose information from the tag! For information on getting and using this information in your code, see {ref}`the programming reference. `. + +## Ambiguity + +Translating from 2D to 3D using data from the calibration and the four tag corners can lead to "pose ambiguity", where it appears that the AprilTag pose is flipping between two different poses. You can read more about this issue `here. ` Ambiguity is calculated as the ratio of reprojection errors between two pose solutions (if they exist), where reprojection error is the error corresponding to the image distance between where the apriltag's corners are detected vs where we expect to see them based on the tag's estimated camera relative pose. + +There are a few steps you can take to resolve/mitigate this issue: + +1. Mount cameras at oblique angles so it is less likely that the tag will be seen straight on. +2. Use the {ref}`MultiTag system ` in order to combine the corners from multiple tags to get a more accurate and unambiguous pose. +3. Reject all tag poses where the ambiguity ratio (available via PhotonLib) is greater than 0.2. diff --git a/docs/source/docs/apriltag-pipelines/about-apriltags.md b/docs/source/docs/apriltag-pipelines/about-apriltags.md new file mode 100644 index 0000000..0eba914 --- /dev/null +++ b/docs/source/docs/apriltag-pipelines/about-apriltags.md @@ -0,0 +1,14 @@ +# About AprilTags + +```{image} images/pv-apriltag.png +:align: center +:scale: 20 % +``` + +AprilTags are a common type of visual fiducial marker. Visual fiducial markers are artificial landmarks added to a scene to allow "localization" (finding your current position) via images. In simpler terms, tags mark known points of reference that you can use to find your current location. They are similar to QR codes in which they encode information, however, they hold only a single number. By placing AprilTags in known locations around the field and detecting them using PhotonVision, you can easily get full field localization / pose estimation. Alternatively, you can use AprilTags the same way you used retroreflective tape, simply using them to turn to goal without any pose estimation. + +A more technical explanation can be found in the [WPILib documentation](https://docs.wpilib.org/en/latest/docs/software/vision-processing/apriltag/apriltag-intro.html). + +:::{note} +You can get FIRST's [official PDF of the targets used in 2024 here](https://firstfrc.blob.core.windows.net/frc2024/FieldAssets/Apriltag_Images_and_User_Guide.pdf). +::: diff --git a/docs/source/docs/apriltag-pipelines/coordinate-systems.md b/docs/source/docs/apriltag-pipelines/coordinate-systems.md new file mode 100644 index 0000000..c828321 --- /dev/null +++ b/docs/source/docs/apriltag-pipelines/coordinate-systems.md @@ -0,0 +1,40 @@ +# Coordinate Systems + +## Field and Robot Coordinate Frame + +PhotonVision follows the WPILib conventions for the robot and field coordinate systems, as defined [here](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/geometry/coordinate-systems.html). + +You define the camera to robot transform in the robot coordinate frame. + +## Camera Coordinate Frame + +OpenCV by default uses x-left/y-down/z-out for camera transforms. PhotonVision applies a base rotation to this transformation to make robot to tag transforms more in line with the WPILib coordinate system. The x, y, and z axes are also shown in red, green, and blue in the 3D mini-map and targeting overlay in the UI. + +- The origin is the focal point of the camera lens +- The x-axis points out of the camera +- The y-axis points to the left +- The z-axis points upwards + +```{image} images/camera-coord.png +:align: center +:scale: 45 % +``` + +```{image} images/multiple-tags.png +:align: center +:scale: 45 % +``` + +## AprilTag Coordinate Frame + +The AprilTag coordinate system is defined as follows, relative to the center of the AprilTag itself, and when viewing the tag as a robot would. Again, PhotonVision changes this coordinate system to be more in line with WPILib. This means that a robot facing a tag head-on would see a robot-to-tag transform with a translation only in x, and a rotation of 180 degrees about z. The tag coordinate system is also shown with x/y/z in red/green/blue in the UI target overlay and mini-map. + +- The origin is the center of the tag +- The x-axis is normal to the plane the tag is printed on, pointing outward from the visible side of the tag. +- The y-axis points to the right +- The z-axis points upwards + +```{image} images/apriltag-coords.png +:align: center +:scale: 45 % +``` diff --git a/docs/source/docs/apriltag-pipelines/detector-types.md b/docs/source/docs/apriltag-pipelines/detector-types.md new file mode 100644 index 0000000..76d01b1 --- /dev/null +++ b/docs/source/docs/apriltag-pipelines/detector-types.md @@ -0,0 +1,15 @@ +# AprilTag Pipeline Types + +PhotonVision offers two different AprilTag pipeline types based on different implementations of the underlying algorithm. Each one has its advantages / disadvantages, which are detailed below. + +:::{note} +Note that both of these pipeline types detect AprilTag markers and are just two different algorithms for doing so. +::: + +## AprilTag + +The AprilTag pipeline type is based on the [AprilTag](https://april.eecs.umich.edu/software/apriltag.html) library from the University of Michigan and we recommend it for most use cases. It is (to our understanding) most accurate pipeline type, but is also ~2x slower than AruCo. This was the pipeline type used by teams in the 2023 season and is well tested. + +## AruCo + +The AruCo pipeline is based on the [AruCo](https://docs.opencv.org/4.8.0/d9/d6a/group__aruco.html) library implementation from OpenCV. It is ~2x higher fps and ~2x lower latency than the AprilTag pipeline type, but is less accurate. We recommend this pipeline type for teams that need to run at a higher framerate or have a lower powered device. This pipeline type is new for the 2024 season and is not as well tested as AprilTag. diff --git a/docs/source/docs/apriltag-pipelines/images/apriltag-coords.png b/docs/source/docs/apriltag-pipelines/images/apriltag-coords.png new file mode 100644 index 0000000..6e4d1b9 Binary files /dev/null and b/docs/source/docs/apriltag-pipelines/images/apriltag-coords.png differ diff --git a/docs/source/docs/apriltag-pipelines/images/apriltag-tune.png b/docs/source/docs/apriltag-pipelines/images/apriltag-tune.png new file mode 100644 index 0000000..bd704d7 Binary files /dev/null and b/docs/source/docs/apriltag-pipelines/images/apriltag-tune.png differ diff --git a/docs/source/docs/apriltag-pipelines/images/apriltag.png b/docs/source/docs/apriltag-pipelines/images/apriltag.png new file mode 100644 index 0000000..dceda34 Binary files /dev/null and b/docs/source/docs/apriltag-pipelines/images/apriltag.png differ diff --git a/docs/source/docs/apriltag-pipelines/images/camera-coord.png b/docs/source/docs/apriltag-pipelines/images/camera-coord.png new file mode 100644 index 0000000..7bf3322 Binary files /dev/null and b/docs/source/docs/apriltag-pipelines/images/camera-coord.png differ diff --git a/docs/source/docs/apriltag-pipelines/images/field-layout.png b/docs/source/docs/apriltag-pipelines/images/field-layout.png new file mode 100644 index 0000000..1e1ec0a Binary files /dev/null and b/docs/source/docs/apriltag-pipelines/images/field-layout.png differ diff --git a/docs/source/docs/apriltag-pipelines/images/multiple-tags.png b/docs/source/docs/apriltag-pipelines/images/multiple-tags.png new file mode 100644 index 0000000..a9e23b4 Binary files /dev/null and b/docs/source/docs/apriltag-pipelines/images/multiple-tags.png differ diff --git a/docs/source/docs/apriltag-pipelines/images/multitag-ui.png b/docs/source/docs/apriltag-pipelines/images/multitag-ui.png new file mode 100644 index 0000000..0eab8f7 Binary files /dev/null and b/docs/source/docs/apriltag-pipelines/images/multitag-ui.png differ diff --git a/docs/source/docs/apriltag-pipelines/images/pv-apriltag.png b/docs/source/docs/apriltag-pipelines/images/pv-apriltag.png new file mode 100644 index 0000000..5cbb06b Binary files /dev/null and b/docs/source/docs/apriltag-pipelines/images/pv-apriltag.png differ diff --git a/docs/source/docs/apriltag-pipelines/index.md b/docs/source/docs/apriltag-pipelines/index.md new file mode 100644 index 0000000..1a9044e --- /dev/null +++ b/docs/source/docs/apriltag-pipelines/index.md @@ -0,0 +1,10 @@ +# AprilTag Detection + +```{toctree} +about-apriltags +detector-types +2D-tracking-tuning +3D-tracking +multitag +coordinate-systems +``` diff --git a/docs/source/docs/apriltag-pipelines/multitag.md b/docs/source/docs/apriltag-pipelines/multitag.md new file mode 100644 index 0000000..da5169f --- /dev/null +++ b/docs/source/docs/apriltag-pipelines/multitag.md @@ -0,0 +1,65 @@ +# MultiTag Localization + +PhotonVision can combine AprilTag detections from multiple simultaneously observed AprilTags from a particular camera with information about where tags are expected to be located on the field to produce a better estimate of where the camera (and therefore robot) is located on the field. PhotonVision can calculate this multi-target result on your coprocessor, reducing CPU usage on your RoboRio. This result is sent over NetworkTables along with other detected targets as part of the `PhotonPipelineResult` provided by PhotonLib. + +:::{warning} +MultiTag requires an accurate field layout JSON to be uploaded! Differences between this layout and the tags' physical location will drive error in the estimated pose output. +::: + +## Enabling MultiTag + +Ensure that your camera is calibrated and 3D mode is enabled. Navigate to the Output tab and enable "Do Multi-Target Estimation". This enables MultiTag to use the uploaded field layout JSON to calculate your camera's pose in the field. This 3D transform will be shown as an additional table in the "targets" tab, along with the IDs of AprilTags used to compute this transform. + +```{image} images/multitag-ui.png +:alt: Multitarget enabled and running in the PhotonVision UI +:width: 600 +``` + +:::{note} +By default, enabling multi-target will disable calculating camera-to-target transforms for each observed AprilTag target to increase performance; the X/Y/angle numbers shown in the target table of the UI are instead calculated using the tag's expected location (per the field layout JSON) and the field-to-camera transform calculated using MultiTag. If you additionally want the individual camera-to-target transform calculated using SolvePNP for each target, enable "Always Do Single-Target Estimation". +::: + +This multi-target pose estimate can be accessed using PhotonLib. We suggest using {ref}`the PhotonPoseEstimator class ` with the `MULTI_TAG_PNP_ON_COPROCESSOR` strategy to simplify code, but the transform can be directly accessed using `getMultiTagResult`/`MultiTagResult()` (Java/C++). + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + var result = camera.getLatestResult(); + if (result.getMultiTagResult().estimatedPose.isPresent) { + Transform3d fieldToCamera = result.getMultiTagResult().estimatedPose.best; + } + + + .. code-block:: C++ + + auto result = camera.GetLatestResult(); + if (result.MultiTagResult().result.isPresent) { + frc::Transform3d fieldToCamera = result.MultiTagResult().result.best; + } + + .. code-block:: Python + + # Coming Soon! + +``` + +:::{note} +The returned field to camera transform is a transform from the fixed field origin to the camera's coordinate system. This does not change based on alliance color, and by convention is on the BLUE ALLIANCE wall. +::: + +## Updating the Field Layout + +PhotonVision ships by default with the [2024 field layout JSON](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2024-crescendo.json). The layout can be inspected by navigating to the settings tab and scrolling down to the "AprilTag Field Layout" card, as shown below. + +```{image} images/field-layout.png +:alt: The currently saved field layout in the Photon UI +:width: 600 +``` + +An updated field layout can be uploaded by navigating to the "Device Control" card of the Settings tab and clicking "Import Settings". In the pop-up dialog, select the "AprilTag Layout" type and choose an updated layout JSON (in the same format as the WPILib field layout JSON linked above) using the paperclip icon, and select "Import Settings". The AprilTag layout in the "AprilTag Field Layout" card below should be updated to reflect the new layout. + +:::{note} +Currently, there is no way to update this layout using PhotonLib, although this feature is under consideration. +::: diff --git a/docs/source/docs/assets/AprilTag16h5.pdf b/docs/source/docs/assets/AprilTag16h5.pdf new file mode 100644 index 0000000..b1f7fe2 Binary files /dev/null and b/docs/source/docs/assets/AprilTag16h5.pdf differ diff --git a/docs/source/docs/assets/settings.png b/docs/source/docs/assets/settings.png new file mode 100644 index 0000000..486a839 Binary files /dev/null and b/docs/source/docs/assets/settings.png differ diff --git a/docs/source/docs/calibration/calibration.md b/docs/source/docs/calibration/calibration.md new file mode 100644 index 0000000..e4e9fb3 --- /dev/null +++ b/docs/source/docs/calibration/calibration.md @@ -0,0 +1,147 @@ +# Calibrating Your Camera + +:::{important} +In order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance. +::: + +To calibrate a camera, images of a Charuco board (or chessboard) are taken. By comparing where the grid corners should be in object space (for example, a corner once every inch in an 8x6 grid) with where they appear in the camera image, we can find a least-squares estimate for intrinsic camera properties like focal lengths, center point, and distortion coefficients. For more on camera calibration, please review the [OpenCV documentation](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html). + +:::{warning} +While any resolution can be calibrated, higher resolutions may be too performance-intensive for some coprocessors to handle. Therefore, we recommend experimenting to see what works best for your coprocessor. +::: + +:::{note} +The calibration data collected during calibration is specific to each physical camera, as well as each individual resolution. +::: + +## Calibration Tips + +Accurate camera calibration is required in order to get accurate pose measurements when using AprilTags and 3D mode. The tips below should help ensure success: + +01. Ensure your the images you take have the target in different positions and angles, with as big of a difference between angles as possible. It is important to make sure the target overlay still lines up with the board while doing this. Tilt no more than 45 degrees. +02. Use as big of a calibration target as your printer can print. +03. Ensure that your printed pattern has enough white border around it. +04. Ensure your camera stays in one position during the duration of the calibration. +05. Make sure you get all 12 images from varying distances and angles. +06. Take at least one image that covers the total image area, and generally ensure that you get even coverage of the lens with your image set. +07. Have good lighting, having a diffusely lit target would be best (light specifically shining on the target without shadows). +08. Ensure the calibration target is completely flat and does not bend or fold in any way. It should be mounted/taped down to something flat and then used for calibration, do not just hold it up. +09. Avoid having targets that are parallel to the lens of the camera / straight on towards the camera as much as possible. You want angles and variations within your calibration images. + +Following the ideas above should help in getting an accurate calibration. + +## Calibrating using PhotonVision + +### 1. Navigate to the calibration section in the UI. + +The Cameras tab of the UI houses PhotonVision's camera calibration tooling. It assists users with calibrating their cameras, as well as allows them to view previously calibrated resolutions. We support both charuco and chessboard calibrations. + +### 2. Print out the calibration target. + +In the Camera Calibration tab, we'll print out the calibration target using the "Download" button. This should be printed on 8.5x11 printer paper. This page shows using an 8x8 charuco board (or chessboard depending on the selected calibration type). + +:::{warning} +Ensure that there is no scaling applied during printing (it should be at 100%) and that the PDF is printed as is on regular printer paper. Check the square size with calipers or an accurate measuring device after printing to ensure squares are sized properly, and enter the true size of the square in the UI text box. For optimal results, various resources are available online to calibrate your specific printer if needed. +::: + +### 3. Select calibration resolution and fill in appropriate target data. + +We'll next select a resolution to calibrate and populate our pattern spacing, marker size, and board size. The provided chessboard and charuco board are an 8x8 grid of 1 inch square. The provided charuco board uses the 4x4 dictionary with a marker size of 0.75 inches (this board does not need the old OpenCV pattern selector selected). Printers are not perfect, and you need to measure your calibration target and enter the correct marker size (size of the aruco marker) and pattern spacing (aka size of the black square) using calipers or similar. Finally, once our entered data is correct, we'll click "start calibration." + +:::{warning} Old OpenCV Pattern selector. This should be used in the case that the calibration image is generated from a version of OpenCV before version 4.6.0. This would include targets created by calib.io. If this selector is not set correctly the calibration will be completely invalid. For more info view [this GitHub issue](https://github.com/opencv/opencv_contrib/issues/3291). +::: + +### 4. Take at calibration images from various angles. + +Now, we'll capture images of our board from various angles. It's important to check that the board overlay matches the board in your image. The further the overdrawn points are from the true position of the chessboard corners, the less accurate the final calibration will be. We'll want to capture enough images to cover the whole camera's FOV (with a minimum of 12). Once we've got our images, we'll click "Finish calibration" and wait for the calibration process to complete. If all goes well, the mean error and FOVs will be shown in the table on the right. The FOV should be close to the camera's specified FOV (usually found in a datasheet) usually within + or - 10 degrees. The mean error should also be low, usually less than 1 pixel. + +```{raw} html + +``` + +## Accessing Calibration Images + +Details about a particular calibration can be viewed by clicking on that resolution in the calibrations tab. This tab allows you to download raw calibration data, upload a previous calibration, and inspect details about calculated camera intrinsic. + +```{image} images/cal-details.png +:alt: Captured calibration images +:width: 600 +``` + +:::{note} +More info on what these parameters mean can be found in [OpenCV's docs](https://docs.opencv.org/4.8.0/d4/d94/tutorial_camera_calibration.html) +::: + +- Fx/Fy: Estimated camera focal length, in mm +- Fx/Cy: Estimated camera optical center, in pixels. This should be at about the center of the image +- Distortion: OpenCV camera model distortion coefficients +- FOV: calculated using estimated focal length and image size. Useful for gut-checking calibration results +- Mean Err: Mean reprojection error, or distance between expected and observed chessboard cameras for the full calibration dataset + +Below these outputs are the snapshots collected for calibration, along with a per-snapshot mean reprojection error. A snapshot with a larger reprojection error might indicate a bad snapshot, due to effects such as motion blur or misidentified chessboard corners. + +Calibration images can also be extracted from the downloaded JSON file using [this Python script](https://raw.githubusercontent.com/PhotonVision/photonvision/master/devTools/calibrationUtils.py). This script will unpack calibration images, and also generate a VNL file for use [with mrcal](https://mrcal.secretsauce.net/). + +``` +python3 /path/to/calibrationUtils.py path/to/photon_calibration.json /path/to/output/folder +``` + +```{image} images/unpacked-json.png +:alt: Captured calibration images +:width: 600 +``` + +## Investigating Calibration Data with mrcal + +[mrcal](https://mrcal.secretsauce.net/tour.html) is a command-line tool for camera calibration and visualization. PhotonVision has the option to use the mrcal backend during camera calibration to estimate intrinsics. mrcal can also be used post-calibration to inspect snapshots and provide feedback. These steps will closely follow the [mrcal tour](https://mrcal.secretsauce.net/tour-initial-calibration.html) -- I'm aggregating commands and notes here, but the mrcal documentation is much more thorough. + +Start by [Installing mrcal](https://mrcal.secretsauce.net/install.html). Note that while mrcal *calibration* using PhotonVision is supported on all platforms, but investigation right now only works on Linux. Some users have also reported luck using [WSL 2 on Windows](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) as well. You may also need to install `feedgnuplot`. On Ubuntu systems, these commands should be run from a standalone terminal and *not* the one [built into vscode](https://github.com/ros2/ros2/issues/1406). + +Let's run `calibrationUtils.py` as described above, and then cd into the output folder. From here, you can follow the mrcal tour, just replacing the VNL filename and camera imager size as necessary. My camera calibration was at 1280x720, so I've set the XY limits to that below. + +``` +$ cd /path/to/output/folder +$ ls +matt@photonvision:~/Documents/Downloads/2024-01-02_lifecam_1280$ ls + corners.vnl img0.png img10.png img11.png img12.png img13.png img1.png + img2.png img3.png img4.png img5.png img6.png img7.png img8.png + img9.png cameramodel_0.cameramodel + +$ < corners.vnl \ + vnl-filter -p x,y | \ + feedgnuplot --domain --square --set 'xrange [0:1280] noextend' --set 'yrange [720:0] noextend' +``` + +```{image} images/mrcal-coverage.svg +:alt: A diagram showing the locations of all detected chessboard corners. +``` + +As you can see, we didn't do a fantastic job of covering our whole camera sensor -- there's a big gap across the whole right side, for example. We also only have 14 calibration images. We've also got our "cameramodel" file, which can be used by mrcal to display additional debug info. + +Let's inspect our reprojection error residuals. We expect their magnitudes and directions to be random -- if there's patterns in the colors shown, then our calibration probably doesn't fully explain our physical camera sensor. + +``` +$ mrcal-show-residuals --magnitudes --set 'cbrange [0:1.5]' ./camera-0.cameramodel +$ mrcal-show-residuals --directions --unset key ./camera-0.cameramodel +``` + +```{image} images/residual-magnitudes.svg +:alt: A diagram showing residual magnitudes +``` + +```{image} images/residual-directions.svg +:alt: A diagram showing residual directions +``` + +Clearly we don't have anywhere near enough data to draw any meaningful conclusions (yet). But for fun, let's dig into [camera uncertainty estimation](https://mrcal.secretsauce.net/tour-uncertainty.html). This diagram shows how expected projection error changes due to noise in calibration inputs. Lower projection error across a larger area of the sensor imply a better calibration that more fully covers the whole sensor. For my calibration data, you can tell the projection error isolines (lines of constant expected projection error) are skewed to the left, following my dataset (which was also skewed left). + +``` +$ mrcal-show-projection-uncertainty --unset key ./cameramodel_0.cameramodel +``` + +```{image} images/camera-uncertainty.svg +:alt: A diagram showing camera uncertainty +``` diff --git a/docs/source/docs/calibration/images/cal-details.png b/docs/source/docs/calibration/images/cal-details.png new file mode 100644 index 0000000..ad9ab63 Binary files /dev/null and b/docs/source/docs/calibration/images/cal-details.png differ diff --git a/docs/source/docs/calibration/images/camera-uncertainty.svg b/docs/source/docs/calibration/images/camera-uncertainty.svg new file mode 100644 index 0000000..547c536 --- /dev/null +++ b/docs/source/docs/calibration/images/camera-uncertainty.svg @@ -0,0 +1,6201 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 400 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 800 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 400 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 700 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Projection uncertainty (in pixels) based on calibration input noise. Looking out to infinity + + + + + + + + + diff --git a/docs/source/docs/calibration/images/mrcal-coverage.svg b/docs/source/docs/calibration/images/mrcal-coverage.svg new file mode 100644 index 0000000..f4cb086 --- /dev/null +++ b/docs/source/docs/calibration/images/mrcal-coverage.svg @@ -0,0 +1,10773 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 400 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 700 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 400 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 800 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/docs/calibration/images/residual-directions.svg b/docs/source/docs/calibration/images/residual-directions.svg new file mode 100644 index 0000000..899badb --- /dev/null +++ b/docs/source/docs/calibration/images/residual-directions.svg @@ -0,0 +1,10306 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 400 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 700 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 400 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 800 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Imager y + + + + + + + + + + + + + + + + + + + + + +Imager x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-150 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 150 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fitted residuals. Directions shown as colors. Magnitudes ignored + + + + + + + + + diff --git a/docs/source/docs/calibration/images/residual-magnitudes.svg b/docs/source/docs/calibration/images/residual-magnitudes.svg new file mode 100644 index 0000000..10fdf82 --- /dev/null +++ b/docs/source/docs/calibration/images/residual-magnitudes.svg @@ -0,0 +1,10360 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 400 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 700 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 400 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 800 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Imager y + + + + + + + + + + + + + + + + + + + + + +Imager x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Fitted residuals. Errors shown as colors + + + + + + + + + diff --git a/docs/source/docs/calibration/images/unpacked-json.png b/docs/source/docs/calibration/images/unpacked-json.png new file mode 100644 index 0000000..045c563 Binary files /dev/null and b/docs/source/docs/calibration/images/unpacked-json.png differ diff --git a/docs/source/docs/contributing/assets/git-download.png b/docs/source/docs/contributing/assets/git-download.png new file mode 100644 index 0000000..5a19355 Binary files /dev/null and b/docs/source/docs/contributing/assets/git-download.png differ diff --git a/docs/source/docs/contributing/building-docs.md b/docs/source/docs/contributing/building-docs.md new file mode 100644 index 0000000..73e45fe --- /dev/null +++ b/docs/source/docs/contributing/building-docs.md @@ -0,0 +1,33 @@ +# Building the PhotonVision Documentation + +To build the PhotonVision documentation, you will require [Git](https://git-scm.com) and [Python 3.6 or greater](https://www.python.org). + +## Cloning the Documentation Repository + +Documentation lives within the main PhotonVision repository within the `docs` sub-folder. If you are planning on contributing, it is recommended to create a fork of the [PhotonVision repository](https://github.com/PhotonVision/photonvision). To clone this fork, run the following command in a terminal window: + +`git clone https://github.com/[your username]/photonvision` + +## Installing Python Dependencies + +You must install a set of Python dependencies in order to build the documentation. To do so, you can run the following command in the docs sub-folder: + +`~/photonvision/docs$ python -m pip install -r requirements.txt` + +## Building the Documentation + +In order to build the documentation, you can run the following command in the docs sub-folder. This will automatically build docs every time a file changes, and serves them locally at `localhost:8000` by default. + +`~/photonvision/docs$ sphinx-autobuild --open-browser source source/_build/html` + +## Opening the Documentation + +The built documentation is located at `docs/build/html/index.html` relative to the root project directory, or can be accessed via the local web server if using sphinx-autobuild. + +## Docs Builds on Pull Requests + +Pre-merge builds of docs can be found at: `https://photonvision-docs--PRNUMBER.org.readthedocs.build/en/PRNUMBER/index.html`. These docs are republished on every commit to a pull request made to PhotonVision/photonvision-docs. For example, PR 325 would have pre-merge documentation published to `https://photonvision-docs--325.org.readthedocs.build/en/325/index.html`. Additionally, the pull request will have a link directly to the pre-release build of the docs. This build only runs when there is a change to files in the docs sub-folder. + +## Style Guide + +PhotonVision follows the frc-docs style guide which can be found [here](https://docs.wpilib.org/en/stable/docs/contributing/style-guide.html). In order to run the linter locally (which builds on doc8 and checks for compliance with the style guide), follow the instructions [on GitHub](https://github.com/wpilibsuite/ohnoyoudidnt). diff --git a/docs/source/docs/contributing/building-photon.md b/docs/source/docs/contributing/building-photon.md new file mode 100644 index 0000000..2ced21e --- /dev/null +++ b/docs/source/docs/contributing/building-photon.md @@ -0,0 +1,274 @@ +# Build Instructions + +This section contains the build instructions from the source code available at [our GitHub page](https://github.com/PhotonVision/photonvision). + +## Development Setup + +### Prerequisites + +**Java Development Kit:** + + This project requires Java Development Kit (JDK) 17 to be compiled. This is the same Java version that comes with WPILib for 2025+. **Windows Users must use the JDK that ships with WPILib.** For other platforms, you can follow the instructions to install JDK 17 for your platform [here](https://bell-sw.com/pages/downloads/#jdk-17-lts). + +**Node JS:** + + The UI is written in Node JS. To compile the UI, Node 18.20.4 to Node 20.0.0 is required. To install Node JS follow the instructions for your platform [on the official Node JS website](https://nodejs.org/en/download/). However, modify this line + +```bash +nvm install 20 +``` + +so that it instead reads + +```javascript +nvm install 18.20.4 +``` + +## Compiling Instructions + +### Getting the Source Code + +Get the source code from git: + +```bash +git clone https://github.com/PhotonVision/photonvision +``` + +or alternatively download the source code from GitHub and extract the zip: + +```{image} assets/git-download.png +:alt: Download source code from git +:width: 600 +``` + +### Install Necessary Node JS Dependencies + +In the photon-client directory: + +```bash +npm install +``` + +### Build and Copy UI to Java Source + +In the root directory: + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Linux + + ``./gradlew buildAndCopyUI`` + + .. tab-item:: macOS + + ``./gradlew buildAndCopyUI`` + + .. tab-item:: Windows (cmd) + + ``gradlew buildAndCopyUI`` +``` + +### Build and Run PhotonVision + +To compile and run the project, issue the following command in the root directory: + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Linux + + ``./gradlew run`` + + .. tab-item:: macOS + + ``./gradlew run`` + + .. tab-item:: Windows (cmd) + + ``gradlew run`` +``` + +Running the following command under the root directory will build the jar under `photon-server/build/libs`: + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Linux + + ``./gradlew shadowJar`` + + .. tab-item:: macOS + + ``./gradlew shadowJar`` + + .. tab-item:: Windows (cmd) + + ``gradlew shadowJar`` +``` + +### Build and Run PhotonVision on a Raspberry Pi Coprocessor + +As a convenience, the build has a built-in `deploy` command which builds, deploys, and starts the current source code on a coprocessor. + +An architecture override is required to specify the deploy target's architecture. + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Linux + + ``./gradlew clean`` + + ``./gradlew deploy -PArchOverride=linuxarm64`` + + .. tab-item:: macOS + + ``./gradlew clean`` + + ``./gradlew deploy -PArchOverride=linuxarm64`` + + .. tab-item:: Windows (cmd) + + ``gradlew clean`` + + ``gradlew deploy -PArchOverride=linuxarm64`` +``` + +The `deploy` command is tested against Raspberry Pi coprocessors. Other similar coprocessors may work too. + +### Using PhotonLib Builds + +The build process automatically generates a vendordep JSON of your local build at `photon-lib/build/generated/vendordeps/photonlib.json`. + +The photonlib source can be published to your local maven repository after building: + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Linux + + ``./gradlew publishToMavenLocal`` + + .. tab-item:: macOS + + ``./gradlew publishToMavenLocal`` + + .. tab-item:: Windows (cmd) + + ``gradlew publishToMavenLocal`` +``` + +After adding the generated vendordep to your project, add the following to your project's `build.gradle` under the `plugins {}` block. + +```Java +repositories { + mavenLocal() +} +``` + +### Debugging PhotonVision Running Locally + +One way is by running the program using gradle with the {code}`--debug-jvm` flag. Run the program with {code}`./gradlew run --debug-jvm`, and attach to it with VSCode by adding the following to {code}`launch.json`. Note args can be passed with {code}`--args="foobar"`. + +``` +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Attach to Remote Program", + "request": "attach", + "hostName": "localhost", + "port": "5005", + "projectName": "photon-core", + } + ] +} +``` + +PhotonVision can also be run using the gradle tasks plugin with {code}`"args": "--debug-jvm"` added to launch.json. + +### Debugging PhotonVision Running on a CoProcessor + +Set up a VSCode configuration in {code}`launch.json` + +``` +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Attach to CoProcessor", + "request": "attach", + "hostName": "photonvision.local", + "port": "5801", + "projectName": "photon-core" + }, + ] +} +``` + +Stop any existing instance of PhotonVision. + +Launch the program with the following additional argument to the JVM: {code}`java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5801 photonvision.jar` + +Once the program says it is listening on port 5801, launch the debug configuration in VSCode. + +The program will wait for the VSCode debugger to attach before proceeding. + +### Running examples + +You can run one of the many built in examples straight from the command line, too! They contain a fully featured robot project, and some include simulation support. The projects can be found inside the photonlib-*-examples subdirectories for each language. + +#### Running C++/Java + +PhotonLib must first be published to your local maven repository. This will also copy the generated vendordep json file into each example. After that, the simulateJava/simulateNative task can be used like a normal robot project. Robot simulation with attached debugger is technically possible by using simulateExternalJava and modifying the launch script it exports, though not yet supported. + +``` +~/photonvision$ ./gradlew publishToMavenLocal + +~/photonvision$ cd photonlib-java-examples +~/photonvision/photonlib-java-examples$ ./gradlew :simulateJava + +~/photonvision$ cd photonlib-cpp-examples +~/photonvision/photonlib-cpp-examples$ ./gradlew :simulateNative +``` + +#### Running Python + +PhotonLibPy must first be built into a wheel. + +``` +> cd photon-lib/py +> buildAndTest.bat +``` + +Then, you must enable using the development wheels. robotpy will use pip behind the scenes, and this bat file tells pip about your development artifacts. + +Note: This is best done in a virtual environment. + +``` +> enableUsingDevBuilds.bat +``` + +Then, run the examples: + +``` +> cd photonlib-python-examples +> run.bat +``` + +#### Downloading Pipeline Artifacts + +Using the [GitHub CLI](https://cli.github.com/), we can download artifacts from pipelines by run ID and name: + +``` +~/photonvision$ gh run download 11759699679 -n jar-Linux +``` diff --git a/docs/source/docs/contributing/design-descriptions/image-rotation.md b/docs/source/docs/contributing/design-descriptions/image-rotation.md new file mode 100644 index 0000000..e307c26 --- /dev/null +++ b/docs/source/docs/contributing/design-descriptions/image-rotation.md @@ -0,0 +1,61 @@ +# Calibration and Image Rotation + +## Rotating Points + +To stay consistent with the OpenCV camera coordinate frame, we put the origin in the top left, with X right, Y down, and Z out (as required by the right-hand rule). Intuitively though, if I ask you to rotate an image 90 degrees clockwise though, you'd probably rotate it about -Z in this coordinate system. Just be aware of this inconsistency. + +![](images/image_corner_frames.png) + +If we have any one point in any of those coordinate systems, we can transform it into any of the other ones using standard geometry libraries by performing relative transformations (like in this pseudocode): + +``` +Translation2d tag_corner1 = new Translation2d(); +Translation2d rotated = tag_corner1.relativeTo(ORIGIN_ROTATED_90_CCW); +``` + +## Image Distortion + +The distortion coefficients for OPENCV8 is given in order `[k1 k2 p1 p2 k3 k4 k5 k6]`. Mrcal names these coefficients `[k_0 k_1, k_2, k_3, k_4, k_5, k_6, k_7]`. + +```{math} + \begin{align*} + \vec P &\equiv \frac{\vec p_{xy}}{p_z} \\ + r &\equiv \left|\vec P\right| \\ + \vec P_\mathrm{radial} &\equiv \frac{ 1 + k_0 r^2 + k_1 r^4 + k_4 r^6}{ 1 + k_5 r^2 + k_6 r^4 + k_7 r^6} \vec P \\ + \vec P_\mathrm{tangential} &\equiv + \left[ \begin{aligned} + 2 k_2 P_0 P_1 &+ k_3 \left(r^2 + 2 P_0^2 \right) \\ + 2 k_3 P_0 P_1 &+ k_2 \left(r^2 + 2 P_1^2 \right) + \end{aligned}\right] \\ + \vec q &= \vec f_{xy} \left( \vec P_\mathrm{radial} + \vec P_\mathrm{tangential} \right) + \vec c_{xy} + \end{align*} +``` + +From this, we observe at `k_0, k_1, k_4, k_5, k_6, k_7` depend only on the norm of {math}`\vec P`, and will be constant given a rotated image. However, `k_2` and `k_3` go with {math}`P_0 \cdot P_1`, `k_3` with {math}`P_0^2`, and `k_2` with {math}`P_1^2`. + +Let's try a concrete example. With a 90 degree CCW rotation, we have {math}`P0=-P_{1\mathrm{rotated}}` and {math}`P1=P_{0\mathrm{rotated}}`. Let's substitute in + +```{math} + \begin{align*} + \left[ \begin{aligned} + 2 k_2 P_0 P_1 &+ k_3 \left(r^2 + 2 P_0^2 \right) \\ + 2 k_3 P_0 P_1 &+ k_2 \left(r^2 + 2 P_1^2 \right) + \end{aligned}\right] &= + \left[ \begin{aligned} + 2 k_{2\mathrm{rotated}} (-P_{1\mathrm{rotated}}) P_{0\mathrm{rotated}} &+ k_{3\mathrm{rotated}} \left(r^2 + 2 (-P_{1\mathrm{rotated}})^2 \right) \\ + 2 k_{3\mathrm{rotated}} (-P_{1\mathrm{rotated}}) P_{0\mathrm{rotated}} &+ k_{2\mathrm{rotated}} \left(r^2 + 2 P_{0\mathrm{rotated}}^2 \right) + \end{aligned}\right] \\ + &= + \left[ \begin{aligned} + -2 k_{2\mathrm{rotated}} P_{1\mathrm{rotated}} P_{0\mathrm{rotated}} &+ k_{3\mathrm{rotated}} \left(r^2 + 2 P_{1\mathrm{rotated}}^2 \right) \\ + -2 k_{3\mathrm{rotated}} P_{1\mathrm{rotated}} P_{0\mathrm{rotated}} &+ k_{2\mathrm{rotated}} \left(r^2 + 2 P_{0\mathrm{rotated}}^2 \right) + \end{aligned}\right] + \end{align*} +``` + +By inspection, this results in just applying another 90 degree rotation to the k2/k3 parameters. Proof is left as an exercise for the reader. Note that we can repeat this rotation to yield equations for tangential distortion for 180 and 270 degrees. + +```{math} + k_2'=-k_3 + k_3'=k_2 +``` diff --git a/docs/source/docs/contributing/design-descriptions/images/image_corner_frames.png b/docs/source/docs/contributing/design-descriptions/images/image_corner_frames.png new file mode 100644 index 0000000..e00a898 Binary files /dev/null and b/docs/source/docs/contributing/design-descriptions/images/image_corner_frames.png differ diff --git a/docs/source/docs/contributing/design-descriptions/index.md b/docs/source/docs/contributing/design-descriptions/index.md new file mode 100644 index 0000000..a436f48 --- /dev/null +++ b/docs/source/docs/contributing/design-descriptions/index.md @@ -0,0 +1,7 @@ +# Software Architecture Design Descriptions + +```{toctree} +:maxdepth: 1 +image-rotation +time-sync +``` diff --git a/docs/source/docs/contributing/design-descriptions/time-sync.md b/docs/source/docs/contributing/design-descriptions/time-sync.md new file mode 100644 index 0000000..507adaa --- /dev/null +++ b/docs/source/docs/contributing/design-descriptions/time-sync.md @@ -0,0 +1,111 @@ +# Time Synchronization Protocol Specification, Version 1.0 + +Protocol Revision 1.0, 08/25/2024 + +## Background + +In a distributed compute environment like robots, time synchronization between computers is increasingly important. Currently, [NetworkTables Version 4.1](https://github.com/wpilibsuite/allwpilib/blob/main/ntcore/doc/networktables4.adoc) provides support for time synchronization of clients with the NetworkTables server using binary PING/PONG messages sent over WebSockets. This approach, while fundamentally the same as is described in this memo, has demonstrated some opportunities for improvement: + +- PING/PONG messages are processed in the same queue as other NetworkTables messages. Depending on the underlying implementation and processor speed, this can incur message processing delays and increase client-calculated Round-Trip Time (RTT), and cause messages to arrive at the server timestamped in the future. +- Messages use WebSockets over TCP for their transport layer. We don't need the robustness guarantees of TCP as our connection is stateless. + +For these reasons, a time synchronization solution separate from NetworkTables communication was desired. Architecture decisions made to address these issues are: + +- Use the User Datagram Protocol (UDP) transport layer, as we don't need the robustness guarantees afforded by TCP. As a Client, if a PING isn't replied to, we'll just try again at the start of the next PING window. As a bonus, we are free to use UDP port 5810 as NetworkTables only uses TCP Port 5810/5811 as of Version 4.1. +- Use a separate thread from the current NetworkTables libUV runner. + + +## Prior Art + +The [NetworkTables 4.1 timestamp synchronization](https://github.com/wpilibsuite/allwpilib/blob/main/ntcore/doc/networktables4.adoc#timestamps) approach, an implementation of [Cristian's Algorithm](https://en.wikipedia.org/wiki/Cristian%27s_algorithm). We also implement Cristian’s Algorithm. + +The [Precision Time Protocol](https://en.wikipedia.org/wiki/Precision_Time_Protocol#Synchronization) at it's core does something similar with Sync/Delay_Req/Delay_Resp. We do not have (guaranteed) access to hardware timestamping, but we utilize this PING/PONG pattern to estimate total round-trip time. + + +## Roles + +```{graphviz} +digraph CristianAlgorithm { + ratio=0.5; + bgcolor="transparent"; + + node [ + fontcolor = "#e6e6e6", + style = filled, + color = "#e6e6e6", + fillcolor = "#333333" + fontsize=10; + ] + + edge [ + color = "#e6e6e6", + fontcolor = "#e6e6e6" + fontsize=10; + ] + + rankdir=LR; + node [shape=box, style=filled, color=lightblue]; + + user_send [label="User Sends T1"]; + server_receive [label="Server Receives T1"]; + server_send [label="Server Sends T2"]; + user_receive [label="User Receives T2"]; + user_compute [label="User Computes Time"]; + + user_send -> server_receive [label="T1 (Request)"]; + server_receive -> server_send [label="T1 received by server"]; + server_send -> user_receive [label="T2 sent by server"]; + user_receive -> user_compute [label="T2 received by user"]; + user_compute -> user_send [label="Computed Time: T3 = T2 + (deltaT2 - deltaT1)/2"]; +} +``` + +Time Synchronization Protocol (TSP) participants can assume either a server role or a client role. The server role is responsible for listening for incoming time synchronization requests from clients and replying appropriately. The client role is responsible for sending "Ping" messages to the server and listening for "Pong" replies to estimate the offset between the server and client time bases. + +All time values shall use units of microseconds. The epoch of the time base this is measured against is unspecified. + +Clients shall periodically (e.g. every few seconds) send, in a manner that minimizes transmission delays, a **TSP Ping Message** that contains the client's current local time. + +When the server receives a **TSP Ping Message** from any client, it shall respond to the client, in a manner that minimizes transmission delays, with a **TSP Pong message** encoding a timestamp of its (the server's) current local time (in microseconds), and the client-provided data value. + +When the client receives a **TSP Pong Message** from the server, it shall verify that the `Client Local Time` corresponds to the currently in-flight TSP Ping message; if not, it shall drop this packet. The round trip time (RTT) shall be computed from the delta between the message's data value and the current local time. If the RTT is less than that from previous measurements, the client shall use the timestamp in the message plus ½ the RTT as the server time equivalent to the current local time, and use this equivalence to compute server time base timestamps from local time for future messages. + +## Transport + +Communication between server and clients shall occur over the User Datagram Protocol (UDP) Port 5810. + +## Message Format + +The message format forgoes CRCs (as these are provided by the Ethernet physical layer) or packet delimination (as our packetsa are assumed be under the network MTU). **TSP Ping** and **TSP Pong** messages shall be encoded in a manor compatible with a WPILib packed struct with respect to byte alignment and endienness. + +### TSP Ping + +| Offset | Format | Data | Notes | +| ------ | ------ | ---- | ----- | +| 0 | uint8 | Protocol version | This field shall always set to 1 (0b1) for TSP Version 1. | +| 1 | uint8 | Message ID | This field shall always be set to 1 (0b1). | +| 2 | uint64 | Client Local Time | The client's local time value, at the time this Ping message was sent. | + +### TSP Pong + +| Offset | Format | Data | Notes | +| ------ | ------ | ---- | ----- | +| 0 | uint8 | Protocol version | This field shall always set to 1 (0b1) for TSP Version 1. +| 1 | uint8 | Message ID | This field shall always be set to 2 (0b2). +| 2 | uint64 | Client Local Time | The client's local time value from the Ping message that this Pong is generated in response to. +| 10 | uint64 | Server Local Time | The current time at the server, at the time this Pong message was sent. + + +## Optional Protocol Extensions + +Clients may publish statistics to NetworkTables. If they do, they shall publish to a key that is globally unique per participant in the Time Synronization network. If a client implements this, it shall provide the following publishers: + +| Key | Type | Notes | +| ------ | ------ | ---- | +| offset_us | Integer | The time offset that, when added to the client's local clock, provides server time | +| ping_tx_count | Integer | The total number of TSP Ping packets transmitted | +| ping_rx_count | Integer | The total number of TSP Ping packets received | +| pong_rx_time_us | Integer | The time, in client local time, that the last pong was received | +| rtt2_us | Integer | The time in us from last complete (ping transmission to pong reception) | + +PhotonVision has chosen to publish to the sub-table `/photonvision/.timesync/{DEVICE_HOSTNAME}`. Future implementations of this protocol may decide to implement this as a structured data type. diff --git a/docs/source/docs/contributing/developer-docs/index.md b/docs/source/docs/contributing/developer-docs/index.md new file mode 100644 index 0000000..6f6aa85 --- /dev/null +++ b/docs/source/docs/contributing/developer-docs/index.md @@ -0,0 +1,5 @@ +# PhotonVision Developer Documentation + +```{toctree} +photonlib-backups +``` diff --git a/docs/source/docs/contributing/developer-docs/photonlib-backups.md b/docs/source/docs/contributing/developer-docs/photonlib-backups.md new file mode 100644 index 0000000..87e0170 --- /dev/null +++ b/docs/source/docs/contributing/developer-docs/photonlib-backups.md @@ -0,0 +1,16 @@ +# Photonlib Developer Docs + +Our maven server is located at https://maven.photonvision.org/#/. This server runs [Reposilite](https://hub.docker.com/r/dzikoysk/reposilite) in Docker, and uses Caddy for serving requests. + + +## Backing up using Rsync + +The Clarkson Open Source Institute at Clarkson University provides a mirror of our artifacts available [online](https://mirror.clarkson.edu/photonvision). Learn more about them at [their homepage](https://mirror.clarkson.edu/home). + +Artifacts from our Maven server can also be backed up locally to a folder called `photonlib-backup` using the following command, which excludes "snapshots" for space reasons: + +``` +rsync -avzrHy --no-perms --no-group --no-owner --ignore-errors --exclude ".~tmp~" --exclude "snapshots/org/photonvision/photontargeting*" \ +--exclude "snapshots/org/photonvision/photonlib*" maven.photonvision.org::reposilite-data \ +/path/to/photonlib-backup +``` diff --git a/docs/source/docs/contributing/index.md b/docs/source/docs/contributing/index.md new file mode 100644 index 0000000..4de6c43 --- /dev/null +++ b/docs/source/docs/contributing/index.md @@ -0,0 +1,8 @@ +# Contributing to PhotonVision Projects + +```{toctree} +building-photon +building-docs +developer-docs/index +design-descriptions/index +``` diff --git a/docs/source/docs/description.md b/docs/source/docs/description.md new file mode 100644 index 0000000..2d342d2 --- /dev/null +++ b/docs/source/docs/description.md @@ -0,0 +1,46 @@ +# About PhotonVision + +## Description + +PhotonVision is a free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition. PhotonVision is designed to get vision working on your robot _quickly_, without the significant cost of other similar solutions. +Using PhotonVision, teams can go from setting up a camera and coprocessor to detecting and tracking AprilTags and other targets by simply tuning sliders. With an easy to use interface, comprehensive documentation, and a feature rich vendor dependency, no experience is necessary to use PhotonVision. No matter your resources, using PhotonVision is easy compared to its alternatives. + +## Advantages + +PhotonVision has a myriad of advantages over similar solutions, including: + +### Affordable + +Compared to alternatives, PhotonVision is much cheaper to use (at the cost of your coprocessor and camera) compared to alternatives that cost \$400. This allows your team to save money while still being competitive. + +### Easy to Use User Interface + +The PhotonVision user interface is simple and modular, making things easier for the user. With a simpler interface, you can focus on what matters most, tracking targets, rather than how to use our UI. A major unique quality is that the PhotonVision UI includes an offline copy of our documentation for your ease of access at competitions. + +### PhotonLib Vendor Dependency + +The PhotonLib vendor dependency allows you to easily get necessary target data (without having to work directly with NetworkTables) while also providing utility methods to get distance and position on the field. This helps your team focus less on getting data and more on using it to do cool things. This also has the benefit of having a structure that ensures all data is from the same timestamp, which is helpful for latency compensation. + +### User Calibration + +Using PhotonVision allows the user to calibrate for their specific camera, which will get you the best tracking results. This is extremely important as every camera (even if it is the same model) will have it's own quirks and user calibration allows for those to be accounted for. + +### High FPS Processing + +Compared to alternative solutions, PhotonVision boasts higher frames per second which allows for a smoother video stream and detection of targets to ensure you aren't losing out on any performance. + +### Low Latency + +PhotonVision provides low latency processing to make sure you get vision measurements as fast as possible, which makes complex vision tasks easier. We guarantee that all measurements are sent from the same timestamp, making life easier for your programmers. + +### Fully Open Source and Active Developer Community + +You can find all of our code on [GitHub](https://github.com/PhotonVision), including code for our main program, documentation, vendor dependency (PhotonLib), and more. This helps you see everything working behind the scenes and increases transparency. This also allows users to make pull requests for features that they want to add in to PhotonVision that will be reviewed by the development team. PhotonVision is licensed under the GNU General Public License (GPLv3) which you can learn more about [here](https://www.gnu.org/licenses/quick-guide-gplv3.html). + +### Multi-Camera Support + +You can use multiple cameras within PhotonVision, allowing you to see multiple angles without the need to buy multiple coprocessors. This makes vision processing more affordable and simpler for your team. + +### Comprehensive Documentation + +Using our comprehensive documentation, you will be able to easily start vision processing by following a series of simple steps. diff --git a/docs/source/docs/examples/aimandrange.md b/docs/source/docs/examples/aimandrange.md new file mode 100644 index 0000000..62abe02 --- /dev/null +++ b/docs/source/docs/examples/aimandrange.md @@ -0,0 +1,50 @@ +# Combining Aiming and Getting in Range + +The following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/master/photonlib-java-examples/aimandrange)/[C++](https://github.com/PhotonVision/photonvision/tree/master/photonlib-cpp-examples/aimandrange)). + +## Knowledge and Equipment Needed + +- Everything required in {ref}`Aiming at a Target `. + +## Code + +Now that you know how to aim toward the AprilTag, let's also drive the correct distance from the AprilTag. + +To do this, we'll use the *pitch* of the target in the camera image and trigonometry to figure out how far away the robot is from the AprilTag. Then, like before, we'll use the P term of a PID controller to drive the robot to the correct distance. + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Java + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/aimandrange/src/main/java/frc/robot/Robot.java + :language: java + :lines: 84-131 + :linenos: + :lineno-start: 84 + + .. tab-item:: C++ (Header) + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimandrange/src/main/include/Robot.h + :language: c++ + :lines: 25-63 + :linenos: + :lineno-start: 25 + + .. tab-item:: C++ (Source) + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimandrange/src/main/cpp/Robot.cpp + :language: c++ + :lines: 58-107 + :linenos: + :lineno-start: 58 + + .. tab-item:: Python + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/aimandrange/robot.py + :language: python + :lines: 44-95 + :linenos: + :lineno-start: 44 + +``` diff --git a/docs/source/docs/examples/aimingatatarget.md b/docs/source/docs/examples/aimingatatarget.md new file mode 100644 index 0000000..b152af0 --- /dev/null +++ b/docs/source/docs/examples/aimingatatarget.md @@ -0,0 +1,55 @@ +# Aiming at a Target + +The following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/master/photonlib-java-examples/aimattarget)). + +## Knowledge and Equipment Needed + +- A Robot +- A camera mounted rigidly to the robot's frame, cenetered and pointed forward. +- A coprocessor running PhotonVision with an AprilTag or Aurco 2D Pipeline. +- [A printout of AprilTag 7](https://firstfrc.blob.core.windows.net/frc2024/FieldAssets/Apriltag_Images_and_User_Guide.pdf), mounted on a rigid and flat surface. + +## Code + +Now that you have properly set up your vision system and have tuned a pipeline, you can now aim your robot at an AprilTag using the data from PhotonVision. The _yaw_ of the target is the critical piece of data that will be needed first. + +Yaw is reported to the roboRIO over Network Tables. PhotonLib, our vender dependency, is the easiest way to access this data. The documentation for the Network Tables API can be found {ref}`here ` and the documentation for PhotonLib {ref}`here `. + +In this example, while the operator holds a button down, the robot will turn towards the AprilTag using the P term of a PID loop. To learn more about how PID loops work, how WPILib implements them, and more, visit [Advanced Controls (PID)](https://docs.wpilib.org/en/stable/docs/software/advanced-control/introduction/index.html) and [PID Control in WPILib](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/controllers/pidcontroller.html#pid-control-in-wpilib). + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Java + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/aimattarget/src/main/java/frc/robot/Robot.java + :language: java + :lines: 77-117 + :linenos: + :lineno-start: 77 + + .. tab-item:: C++ (Header) + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimattarget/src/main/include/Robot.h + :language: c++ + :lines: 25-60 + :linenos: + :lineno-start: 25 + + .. tab-item:: C++ (Source) + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimattarget/src/main/cpp/Robot.cpp + :language: c++ + :lines: 56-96 + :linenos: + :lineno-start: 56 + + .. tab-item:: Python + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/aimattarget/robot.py + :language: python + :lines: 46-70 + :linenos: + :lineno-start: 46 + +``` diff --git a/docs/source/docs/examples/images/poseest_demo.gif b/docs/source/docs/examples/images/poseest_demo.gif new file mode 100644 index 0000000..5326725 Binary files /dev/null and b/docs/source/docs/examples/images/poseest_demo.gif differ diff --git a/docs/source/docs/examples/index.md b/docs/source/docs/examples/index.md new file mode 100644 index 0000000..501b1b5 --- /dev/null +++ b/docs/source/docs/examples/index.md @@ -0,0 +1,9 @@ +# Code Examples + +```{toctree} +:maxdepth: 1 + +aimingatatarget +aimandrange +poseest +``` diff --git a/docs/source/docs/examples/poseest.md b/docs/source/docs/examples/poseest.md new file mode 100644 index 0000000..27ed51a --- /dev/null +++ b/docs/source/docs/examples/poseest.md @@ -0,0 +1,211 @@ +# Using WPILib Pose Estimation, Simulation, and PhotonVision Together + +The following example comes from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/master/photonlib-java-examples/poseest)/[C++](https://github.com/PhotonVision/photonvision/tree/master/photonlib-cpp-examples/poseest)/[Python](https://github.com/PhotonVision/photonvision/tree/master/photonlib-python-examples/poseest)). Full code is available at that links. + +## Knowledge and Equipment Needed + +- Everything required in {ref}`Combining Aiming and Getting in Range `, plus some familiarity with WPILib pose estimation functionality. + +## Background + +This example demonstrates integration of swerve drive control, a basic swerve physics simulation, and PhotonLib's simulated vision system functionality. + +## Walkthrough + +### Estimating Pose + +The {code}`Drivetrain` class includes functionality to fuse multiple sensor readings together (including PhotonVision) into a best-guess of the pose on the field. + +Please reference the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-pose_state-estimators.html) on using the {code}`SwerveDrivePoseEstimator` class. + +We use the 2024 game's AprilTag Locations: + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Java + :sync: java + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java + :language: java + :lines: 68-68 + :linenos: + :lineno-start: 68 + + .. tab-item:: C++ + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Constants.h + :language: c++ + :lines: 42-43 + :linenos: + :lineno-start: 42 + + .. tab-item:: Python + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py + :language: python + :lines: 46-46 + :linenos: + :lineno-start: 46 + +``` + + + +To incorporate PhotonVision, we need to create a {code}`PhotonCamera`: + + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Java + :sync: java + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java + :language: java + :lines: 57-57 + :linenos: + :lineno-start: 57 + + .. tab-item:: C++ + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h + :language: c++ + :lines: 145-145 + :linenos: + :lineno-start: 145 + + .. tab-item:: Python + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py + :language: python + :lines: 44-44 + :linenos: + :lineno-start: 44 +``` + +During periodic execution, we read back camera results. If we see AprilTags in the image, we calculate the camera-measured pose of the robot and pass it to the {code}`Drivetrain`. + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Java + :sync: java + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java + :language: java + :lines: 64-74 + :linenos: + :lineno-start: 64 + + .. tab-item:: C++ + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/cpp/Robot.cpp + :language: c++ + :lines: 38-46 + :linenos: + :lineno-start: 38 + + .. tab-item:: Python + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py + :language: python + :lines: 54-56 + :linenos: + :lineno-start: 54 + +``` + +### Simulating the Camera + +First, we create a new {code}`VisionSystemSim` to represent our camera and coprocessor running PhotonVision, and moving around our simulated field. + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Java + :sync: java + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java + :language: java + :lines: 65-69 + :linenos: + :lineno-start: 65 + + .. tab-item:: C++ + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h + :language: c++ + :lines: 49-52 + :linenos: + :lineno-start: 49 + + .. tab-item:: Python + + # Coming Soon! + +``` + +Then, we add configure the simulated vision system to match the camera system being simulated. + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Java + :sync: java + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java + :language: java + :lines: 69-82 + :linenos: + :lineno-start: 69 + + .. tab-item:: C++ + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h + :language: c++ + :lines: 53-65 + :linenos: + :lineno-start: 53 + + .. tab-item:: Python + + # Coming Soon! +``` + + +### Updating the Simulated Vision System + +During simulation, we periodically update the simulated vision system. + +```{eval-rst} +.. tab-set:: + + .. tab-item:: Java + :sync: java + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java + :language: java + :lines: 114-132 + :linenos: + :lineno-start: 114 + + .. tab-item:: C++ + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/cpp/Robot.cpp + :language: c++ + :lines: 95-109 + :linenos: + :lineno-start: 95 + + .. tab-item:: Python + + # Coming Soon! +``` + +The rest is done behind the scenes. + +```{image} images/poseest_demo.gif +:alt: Simulated swerve drive and vision system working together in teleoperated mode. +:width: 1200 +``` diff --git a/docs/source/docs/hardware/customhardware.md b/docs/source/docs/hardware/customhardware.md new file mode 100644 index 0000000..43252b7 --- /dev/null +++ b/docs/source/docs/hardware/customhardware.md @@ -0,0 +1,121 @@ +# Deploying on Custom Hardware + +## Configuration + +By default, PhotonVision attempts to make minimal assumptions of the hardware it runs on. However, it may be configured to enable custom LED control, branding, and other functionality. + +`hardwareConfig.json` is the location for this configuration. It is included when settings are exported, and can be uploaded as part of a .zip, or on its own. + +## LED Support + +For Raspberry-Pi based hardware, PhotonVision can use [PiGPIO](https://abyz.me.uk/rpi/pigpio/) to control IO pins. The mapping of which pins control which LED's is part of the hardware config. The pins are active-high: set high when LED's are commanded on, and set low when commanded off. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: json + + { + "ledPins" : [ 13 ], + "ledSetCommand" : "", + "ledsCanDim" : true, + "ledPWMRange" : [ 0, 100 ], + "ledPWMSetRange" : "", + "ledPWMFrequency" : 0, + "ledDimCommand" : "", + "ledBlinkCommand" : "", + "statusRGBPins" : [ ], + } +``` + +:::{note} +No hardware boards with status RGB LED pins or non-dimming LED's have been tested yet. Please reach out to the development team if these features are desired, they can assist with configuration and testing. +::: + +## Hardware Interaction Commands + +For Non-Raspberry-Pi hardware, users must provide valid hardware-specific commands for some parts of the UI interaction (including performance metrics, and executing system restarts). + +Leaving a command blank will disable the associated functionality. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: json + + { + "cpuTempCommand" : "", + "cpuMemoryCommand" : "", + "cpuUtilCommand" : "", + "gpuMemoryCommand" : "", + "gpuTempCommand" : "", + "ramUtilCommand" : "", + "restartHardwareCommand" : "", + } +``` + +:::{note} +These settings have no effect if PhotonVision detects it is running on a Raspberry Pi. See [the MetricsBase class](https://github.com/PhotonVision/photonvision/blob/dbd631da61b7c86b70fa6574c2565ad57d80a91a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java) for the commands utilized. +::: + +## Known Camera FOV + +If your hardware contains a camera with a known field of vision, it can be entered into the hardware configuration. This will prevent users from editing it in the GUI. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: json + + { + "vendorFOV" : 98.9 + } +``` + +## Cosmetic & Branding + +To help differentiate your hardware from other solutions, some customization is allowed. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: json + + { + "deviceName" : "Super Cool Custom Hardware", + "deviceLogoPath" : "", + "supportURL" : "https://cat-bounce.com/", + } +``` + +:::{note} +Not all configuration is currently presented in the User Interface. Additional file uploads may be needed to support custom images. +::: + +## Example + +Here is a complete example `hardwareConfig.json`: + +```{eval-rst} +.. tab-set-code:: + .. code-block:: json + + { + "deviceName" : "Blinky McBlinkface", + "deviceLogoPath" : "", + "supportURL" : "https://www.youtube.com/watch?v=b-CvLWbfZhU", + "ledPins" : [2, 13], + "ledSetCommand" : "", + "ledsCanDim" : true, + "ledPWMRange" : [ 0, 100 ], + "ledPWMSetRange" : "", + "ledPWMFrequency" : 0, + "ledDimCommand" : "", + "ledBlinkCommand" : "", + "statusRGBPins" : [ ], + "cpuTempCommand" : "", + "cpuMemoryCommand" : "", + "cpuUtilCommand" : "", + "gpuMemoryCommand" : "", + "gpuTempCommand" : "", + "ramUtilCommand" : "", + "restartHardwareCommand" : "", + "vendorFOV" : 72.5 + } +``` diff --git a/docs/source/docs/hardware/images/bootConfigTxt.png b/docs/source/docs/hardware/images/bootConfigTxt.png new file mode 100644 index 0000000..1c3f5b8 Binary files /dev/null and b/docs/source/docs/hardware/images/bootConfigTxt.png differ diff --git a/docs/source/docs/hardware/images/rollingshutter.gif b/docs/source/docs/hardware/images/rollingshutter.gif new file mode 100644 index 0000000..e1a6f01 Binary files /dev/null and b/docs/source/docs/hardware/images/rollingshutter.gif differ diff --git a/docs/source/docs/hardware/index.md b/docs/source/docs/hardware/index.md new file mode 100644 index 0000000..bb4a1a9 --- /dev/null +++ b/docs/source/docs/hardware/index.md @@ -0,0 +1,9 @@ +# Hardware Selection + +```{toctree} +:maxdepth: 2 + +selecting-hardware +picamconfig +customhardware +``` diff --git a/docs/source/docs/hardware/picamconfig.md b/docs/source/docs/hardware/picamconfig.md new file mode 100644 index 0000000..4a2226a --- /dev/null +++ b/docs/source/docs/hardware/picamconfig.md @@ -0,0 +1,58 @@ +# Pi Camera Configuration + +## Background + +The Raspberry Pi CSI Camera port is routed through and processed by the GPU. Since the GPU boots before the CPU, it must be configured properly for the attached camera. Additionally, this configuration cannot be changed without rebooting. + +The GPU is not always capable of detecting other cameras automatically. The file `/boot/config.txt` is parsed by the GPU at boot time to determine what camera, if any, is expected to be attached. This file must be updated for some cameras. + +:::{warning} +Incorrect camera configuration will cause the camera to not be detected. It looks exactly the same as if the camera was unplugged. +::: + +## Updating `config.txt` + +After flashing the pi image onto an SD card, open the `boot` segment in a file browser. + +:::{note} +Windows may report "There is a problem with this drive". This should be ignored. +::: + +Locate `config.txt` in the folder, and open it with your favorite text editor. + +```{image} images/bootConfigTxt.png +``` + +Within the file, find this block of text: + +``` +############################################################## +### PHOTONVISION CAM CONFIG +### Comment/Uncomment to change which camera is supported +### Picam V1, V2 or HQ: uncomment (remove leading # ) from camera_auto_detect=1, +### and comment out all following lines +### IMX290/327/OV9281/Any other cameras that require additional overlays: +### Comment out (add a # ) to camera_auto_detect=1, and uncomment the line for +### the sensor you're trying to user + +cameraAutoDetect=1 + +# dtoverlay=imx290,clock-frequency=74250000 +# dtoverlay=imx290,clock-frequency=37125000 +# dtoverlay=imx378 +# dtoverlay=ov9281 + +############################################################## +``` + +Remove the leading `#` character to uncomment the line associated with your camera. Add a `#` in front of other cameras. + +:::{warning} +Leave lines outside the PhotonVision Camera Config block untouched. They are necessary for proper raspberry pi functionality. +::: + +Save the file, close the editor, and eject the drive. The boot configuration should now be ready for your selected camera. + +## Additional Information + +See [the libcamera documentation](https://github.com/raspberrypi/documentation/blob/679fab721855a3e8f17aa51819e5c2a7c447e98d/documentation/asciidoc/computers/camera/rpicam_configuration.adoc) for more details on configuring cameras. diff --git a/docs/source/docs/hardware/selecting-hardware.md b/docs/source/docs/hardware/selecting-hardware.md new file mode 100644 index 0000000..81c0994 --- /dev/null +++ b/docs/source/docs/hardware/selecting-hardware.md @@ -0,0 +1,79 @@ +# Selecting Hardware + +:::{note} +It is highly recommended that you read the {ref}`quick start guide`, and use the hardware recommended there that +is not touched on here. +::: + +In order to use PhotonVision, you need a coprocessor and a camera. Other than the recommended hardware found in the {ref}`quick start guide`, this page will help you select hardware that should work for photonvision even though it is not supported/recommended. + +## Choosing a Coprocessor + +### Minimum System Requirements + +- Ubuntu 22.04 LTS or Windows 10/11 + - We don't recommend using Windows for anything except testing out the system on a local machine. +- CPU: ARM Cortex-A53 (the CPU on Raspberry Pi 3) or better +- At least 8GB of storage +- 2GB of RAM + - PhotonVision isn't very RAM intensive, but you'll need at least 2GB to run the OS and PhotonVision. +- The following IO: + - At least 1 USB or MIPI-CSI port for the camera + - Note that we only support using the Raspberry Pi's MIPI-CSI port, other MIPI-CSI ports from other coprocessors will probably not work. + - Ethernet port for networking + +### Coprocessor Recommendations + +When selecting a coprocessor, it is important to consider various factors, particularly when it comes to AprilTag detection. Opting for a coprocessor with a more powerful CPU can generally result in higher FPS AprilTag detection, leading to more accurate pose estimation. However, it is important to note that there is a point of diminishing returns, where the benefits of a more powerful CPU may not outweigh the additional cost. Other coprocessors can be used but may require some extra work / command line usage in order to get it working properly. + +## Choosing a Camera + +PhotonVision works with Pi Cameras and most USB Cameras. Other cameras such as webcams, virtual cameras, etc. are not officially supported and may not work. It is important to note that fisheye cameras should only be used as a driver camera / gamepeice detection and not for detecting targets / AprilTags. + +PhotonVision relies on [CSCore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) to detect and process cameras, so camera support is determined based off compatibility with CScore along with native support for the camera within your OS (ex. [V4L compatibility](https://en.wikipedia.org/wiki/Video4Linux) if using a Linux machine like a Raspberry Pi). + +:::{note} +Logitech Cameras and integrated laptop cameras will not work with PhotonVision due to oddities with their drivers. We recommend using a different camera. +::: + +:::{note} +We do not currently support the usage of two of the same camera on the same coprocessor. You can only use two or more cameras if they are of different models or they are from Arducam, which has a [tool that allows for cameras to be renamed](https://docs.arducam.com/UVC-Camera/Serial-Number-Tool-Guide/). +::: + +### Cameras Attributes + +For colored shape detection, any non-fisheye camera supported by PhotonVision will work. We recommend a high fps USB camera. + +For driver camera, we recommend a USB camera with a fisheye lens, so your driver can see more of the field. + +For AprilTag detection, we recommend you use a global shutter camera that has ~100 degree diagonal FOV. This will allow you to see more AprilTags in frame, and will allow for more accurate pose estimation. You also want a camera that supports high FPS, as this will allow you to update your pose estimator at a higher frequency. + +Another cause of image distortion is 'rolling shutter.' This occurs when the camera captures pixels sequentially from top to bottom, which can also lead to distortion if the camera or object is moving. + +```{image} images/rollingshutter.gif +:align: center +``` + +### Using Multiple Cameras + +Using multiple cameras on your robot will help you detect more AprilTags at once and improve your pose estimation as a result. In order to use multiple cameras, you will need to create multiple PhotonPoseEstimators and add all of their measurements to a single drivetrain pose estimator. Please note that the accuracy of your robot to camera transform is especially important when using multiple cameras as any error in the transform will cause your pose estimations to "fight" each other. For more information, see {ref}`the programming reference. `. + +## Performance Matrix + +```{raw} html + + + + + +``` + +Please submit performance data to be added to the matrix here: + +```{raw} html + + + + + +``` diff --git a/docs/source/docs/integration/advancedStrategies.md b/docs/source/docs/integration/advancedStrategies.md new file mode 100644 index 0000000..54f66f1 --- /dev/null +++ b/docs/source/docs/integration/advancedStrategies.md @@ -0,0 +1,68 @@ +# Advanced Strategies + +Advanced strategies for using vision processing results involve working with the robot's *pose* on the field. + +A *pose* is a combination an X/Y coordinate, and an angle describing where the robot's front is pointed. A pose is always considered *relative* to some fixed point on the field. + +WPILib provides a [Pose2d](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/geometry/pose.html) class to describe poses in software. + +PhotonVision can supply correcting information to keep estimates of *pose* accurate over a full match. + +## Knowledge and Equipment Needed + +- A Coprocessor running PhotonVision + \- Accurate camera calibration to support "3D mode" required +- A Drivetrain with wheels and sensors + \- Sufficient sensors to measure wheel rotation + \- Capable of closed-loop velocity control +- A gyroscope or IMU measuring actual robot heading +- Experience using some path-planning library + +## Robot Poses from the Camera + +When using 3D mode in PhotonVision, an additional step is run to estimate the 3D position of camera, relative to one or more AprilTags. + +This process does not produce a *unique* solution. There are multiple possible camera positions which might explain the image it observed. Additionally, the camera is rarely mounted in the exact center of a robot. + +For these reasons, the 3D information must be filtered and transformed before they can describe the robot's pose. + +PhotonLib provides {ref}`a utility class to assist with this process on the roboRIO `. Alternatively, {ref}`a "multi-tag" strategy can do this process on the coprocessor. `. + +## Field-Relative Pose Estimation + +The camera's guess of the robot pose generally should be *fused* with other sensor readings. + +WPILib provides [a set of pose estimation classes](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-pose-estimators.html) for doing this work. + +## I have a Pose Estimate, Now What? + +### Triggering Actions Automatically + +A simple way to use a pose estimate is to activate robot functions automatically when in the correct spot on the field. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + Pose3d robotPose; + boolean launcherSpinCmd; + + // ... + + if(robotPose.X() < 1.5){ + // Near blue alliance wall, start spinning the launcher wheel + launcherSpinCmd = True; + } else { + // Far away, no need to run launcher. + launcherSpinCmd = False; + } + + // ... +``` + +### PathPlanning + +A common, but more complex usage of a pose estimate is an input to a path-following algorithm. Specifically, the pose estimate is used to correct for the robot straying off of the pre-defined path. + +See the {ref}`Pose Estimation ` example for details on integrating this. diff --git a/docs/source/docs/integration/background.md b/docs/source/docs/integration/background.md new file mode 100644 index 0000000..1341e3a --- /dev/null +++ b/docs/source/docs/integration/background.md @@ -0,0 +1,19 @@ +# Vision - Robot Integration Background + +## Vision Processing's Purpose + +Each year, the FRC game requires a fundamental operation: **Align the Robot to a Goal**. + +Regardless of whether that alignment point is for picking up gamepieces, or for scoring, fast and effective robots must be able to align to them quickly and repeatably. + +Software strategies can be used to help augment the ability of a human operator, or step in when a human operator is not allowed to control the robot. + +*Vision Processing* is one key *input* to these software strategies. However, the inputs your coprocessor provides must be interpreted and converted (ultimately) to motor voltage commands. + +There are many valid strategies for doing this transformation. Picking a strategy is a balancing act between: + +> 1. Available team resources (time, programming skills, previous experience) +> 2. Precision of alignment required +> 3. Team willingness to take on risk + +Simple strategies are low-risk - they require comparatively little effort to implement and tune, but have hard limits on the complexity of motion they can control on the robot. Advanced methods allow for more complex and precise movement, but take more effort to implement and tune. For this reason, it is more risky to attempt to use them. diff --git a/docs/source/docs/integration/index.md b/docs/source/docs/integration/index.md new file mode 100644 index 0000000..b5bc243 --- /dev/null +++ b/docs/source/docs/integration/index.md @@ -0,0 +1,9 @@ +# Robot Integration + +```{toctree} +:maxdepth: 2 + +background +simpleStrategies +advancedStrategies +``` diff --git a/docs/source/docs/integration/simpleStrategies.md b/docs/source/docs/integration/simpleStrategies.md new file mode 100644 index 0000000..1e90b5c --- /dev/null +++ b/docs/source/docs/integration/simpleStrategies.md @@ -0,0 +1,32 @@ +# Simple Strategies + +Simple strategies for using vision processor outputs involve using the target's position in the 2D image to infer *range* and *angle* to a particular AprilTag. + +## Knowledge and Equipment Needed + +- A Coprocessor running PhotonVision +- A Drivetrain with wheels +- An AprilTag to aim at + +## Angle Alignment + +The simplest way to align a robot to an AprilTag is to rotate the drivetrain until the tag is centered in the camera image. To do this, + +1. Read the current yaw angle to the AprilTag from the vision Coprocessor. +2. If too far off to one side, command the drivetrain to rotate in the opposite direction to compensate. + +See the {ref}`Aiming at a Target ` example for more information. + +NOTE: This works if the camera is centered on the robot. This is easiest from a software perspective. If the camera is not centered, take a peek at the next example - it shows how to account for an offset. + +## Adding Range Alignment + +By looking at the position of the AprilTag in the "vertical" direction in the image, and applying some trigonometry, the distance between the robot and the camera can be deduced. + +1. Read the current pitch angle to the AprilTag from the vision coprocessor. +2. Do math to calculate the distance to the AprilTag. +2. If too far in one direction, command the drivetrain to travel in the opposite direction to compensate. + +This can be done simultaneously while aligning to the desired angle. + +See the {ref}`Aim and Range ` example for more information. diff --git a/docs/source/docs/objectDetection/about-object-detection.md b/docs/source/docs/objectDetection/about-object-detection.md new file mode 100644 index 0000000..70b9512 --- /dev/null +++ b/docs/source/docs/objectDetection/about-object-detection.md @@ -0,0 +1,52 @@ +# About Object Detection + +## How does it work? + +PhotonVision supports object detection using neural network accelerator hardware built into Orange Pi 5/5+ coprocessors. The Neural Processing Unit, or NPU, is [used by PhotonVision](https://github.com/PhotonVision/rknn_jni/tree/main) to massively accelerate certain math operations like those needed for running ML-based object detection. + +For the 2024 season, PhotonVision ships with a **pre-trained NOTE detector** (shown above), as well as a mechanism for swapping in custom models. Future development will focus on enabling lower friction management of multiple custom models. + +```{image} images/notes-ui.png + +``` + +## Tracking Objects + +Before you get started with object detection, ensure that you have followed the previous sections on installation, wiring, and networking. Next, open the Web UI, go to the top right card, and switch to the “Object Detection” type. You should see a screen similar to the image above. + +PhotonVision currently ships with a NOTE detector based on a [YOLOv5 model](https://docs.ultralytics.com/yolov5/). This model is trained to detect one or more object "classes" (such as cars, stoplights, or in our case, NOTES) in an input image. For each detected object, the model outputs a bounding box around where in the image the object is located, what class the object belongs to, and a unitless confidence between 0 and 1. + +:::{note} +This model output means that while its fairly easy to say that "this rectangle probably contains a NOTE", we don't have any information about the NOTE's orientation or location. Further math in user code would be required to make estimates about where an object is physically located relative to the camera. +::: + +## Tuning and Filtering + +Compared to other pipelines, object detection exposes very few tuning handles. The Confidence slider changes the minimum confidence that the model needs to have in a given detection to consider it valid, as a number between 0 and 1 (with 0 meaning completely uncertain and 1 meaning maximally certain). + +```{raw} html + +``` + +The same area, aspect ratio, and target orientation/sort parameters from {ref}`reflective pipelines ` are also exposed in the object detection card. + +## Letterboxing + +Photonvision will letterbox your camera frame to 640x640. This means that if you select a resolution that is larger than 640 it will be scaled down to fit inside a 640x640 frame with black bars if needed. Smaller frames will be scaled up with black bars if needed. + +## Training Custom Models + +Coming soon! + +## Uploading Custom Models + +:::{warning} +PhotonVision currently ONLY supports YOLOv5 models trained and converted to `.rknn` format for RK3588 CPUs! Other models require different post-processing code and will NOT work. The model conversion process is also highly particular. Proceed with care. +::: + +Our [pre-trained NOTE model](https://github.com/PhotonVision/photonvision/blob/master/photon-server/src/main/resources/models/note-640-640-yolov5s.rknn) is automatically extracted from the JAR when PhotonVision starts, only if a file named “note-640-640-yolov5s.rknn” and "labels.txt" does not exist in the folder `photonvision_config/models/`. This technically allows power users to replace the model and label files with new ones without rebuilding Photon from source and uploading a new JAR. + +Use a program like WinSCP or FileZilla to access your coprocessor's filesystem, and copy the new `.rknn` model file into /home/pi. Next, SSH into the coprocessor and `sudo mv /path/to/new/model.rknn /opt/photonvision/photonvision_config/models/note-640-640-yolov5s.rknn`. Repeat this process with the labels file, which should contain one line per label the model outputs with no training newline. Next, restart PhotonVision via the web UI. diff --git a/docs/source/docs/objectDetection/images/notes-ui.png b/docs/source/docs/objectDetection/images/notes-ui.png new file mode 100644 index 0000000..9993d5e Binary files /dev/null and b/docs/source/docs/objectDetection/images/notes-ui.png differ diff --git a/docs/source/docs/objectDetection/index.md b/docs/source/docs/objectDetection/index.md new file mode 100644 index 0000000..694b8c1 --- /dev/null +++ b/docs/source/docs/objectDetection/index.md @@ -0,0 +1,8 @@ +# Object Detection + +```{toctree} +:maxdepth: 0 +:titlesonly: true + +about-object-detection +``` diff --git a/docs/source/docs/pipelines/about-pipelines.md b/docs/source/docs/pipelines/about-pipelines.md new file mode 100644 index 0000000..744a6b8 --- /dev/null +++ b/docs/source/docs/pipelines/about-pipelines.md @@ -0,0 +1,46 @@ +--- +orphan: true +--- + +# About Pipelines + +## What is a pipeline? + +A vision pipeline represents a series of steps that are used to acquire an image, process it, and analyzing it to find a target. In most FRC games, this means processing an image in order to detect a piece of retroreflective tape or an AprilTag. + +## Types of Pipelines + +### Reflective + +This is the most common pipeline type and it is based on detecting targets with retroreflective tape. In the contours tab of this pipeline type, you can filter the area, width/height ratio, fullness, degree of speckle rejection. + +### Colored Shape + +This pipeline type is based on detecting different shapes like circles, triangles, quadrilaterals, or a polygon. An example usage would be detecting yellow PowerCells from the 2020 FRC game. You can read more about the specific settings available in the contours page. + +### AprilTag / AruCo + +This pipeline type is based on detecting AprilTag fiducial markers. More information about AprilTags can be found in the WPILib documentation. While being more performance intensive than the reflective and colored shape pipeline, it has the benefit of providing easy to use 3D pose information which allows localization. + +:::{note} +In order to get 3D Pose data about AprilTags, you are required to {ref}`calibrate your camera`. +::: + +## Note About Multiple Cameras and Pipelines + +When using more than one camera, it is important to keep in mind that all cameras run one pipeline each, all publish to NT, and all send both streams. This will have a noticeable affect on performance and we recommend users limit themselves to 1-2 cameras per coprocessor. + +## Pipeline Steps + +Reflective and Colored Shape Pipelines have 4 steps (represented as 4 tabs): + +1. Input: This tab allows the raw camera image to be modified before it gets processed. Here, you can set exposure, brightness, gain, orientation, and resolution. +2. Threshold (Only Reflective and Colored Shape): This tabs allows you to filter our specific colors/pixels in your camera stream through HSV tuning. The end goal here is having a black and white image that will only have your target lit up. +3. Contours: After thresholding, contiguous white pixels are grouped together, and described by a curve that outlines the group. This curve is called a "contour" which represent various targets on your screen. Regardless of type, you can filter how the targets are grouped, their intersection, and how the targets are sorted. Other available filters will change based on different pipeline types. +4. Output: Now that you have filtered all of your contours, this allows you to manipulate the detected target via orientation, the offset point, and offset. + +AprilTag / AruCo Pipelines have 3 steps: + +1. Input: This is the same as the above. +2. AprilTag: This step include AprilTag specific tuning parameters, such as decimate, blur, threads, pose iterations, and more. +3. Output: This is the same as the above. diff --git a/docs/source/docs/pipelines/images/motionblur.gif b/docs/source/docs/pipelines/images/motionblur.gif new file mode 100644 index 0000000..e1a6f01 Binary files /dev/null and b/docs/source/docs/pipelines/images/motionblur.gif differ diff --git a/docs/source/docs/pipelines/images/pipelinetype.png b/docs/source/docs/pipelines/images/pipelinetype.png new file mode 100644 index 0000000..906be5f Binary files /dev/null and b/docs/source/docs/pipelines/images/pipelinetype.png differ diff --git a/docs/source/docs/pipelines/index.md b/docs/source/docs/pipelines/index.md new file mode 100644 index 0000000..7d8ba7d --- /dev/null +++ b/docs/source/docs/pipelines/index.md @@ -0,0 +1,7 @@ +# Pipelines + +```{toctree} +about-pipelines +input +output +``` diff --git a/docs/source/docs/pipelines/input.md b/docs/source/docs/pipelines/input.md new file mode 100644 index 0000000..ec93c12 --- /dev/null +++ b/docs/source/docs/pipelines/input.md @@ -0,0 +1,45 @@ +# Camera Tuning / Input + +PhotonVision's "Input" tab contains settings that affect the image captured by the currently selected camera. This includes camera exposure and brightness, as well as resolution and orientation. + +## Resolution + +Resolution changes the resolution of the image captured. While higher resolutions are often more accurate than lower resolutions, they also run at a slower update rate. + +When using the reflective/colored shape pipeline, detection should be run as low of a resolution as possible as you are only trying to detect simple contours (essentially colored blobs). + +When using the AprilTag pipeline, you should try to use as high of a resolution as you can while still maintaining a reasonable FPS measurement. This is because higher resolution allows you to detect tags with higher accuracy and from larger distances. + +## Exposure and brightness + +Camera exposure and brightness control how bright the captured image will be, although they function differently. Camera exposure changes how long the camera shutter lets in light, which changes the overall brightness of the captured image. This is in contrast to brightness, which is a post-processing effect that boosts the overall brightness of the image at the cost of desaturating colors (making colors look less distinct). + +:::{important} +For all pipelines, exposure time should be set as low as possible while still allowing for the target to be reliably tracked. This allows for faster processing as decreasing exposure will increase your camera FPS. +::: + +For reflective pipelines, after adjusting exposure and brightness, the target should be lit green (or the color of the vision tracking LEDs used). The more distinct the color of the target, the more likely it will be tracked reliably. + +:::{note} +Unlike with retroreflective tape, AprilTag tracking is not very dependent on lighting consistency. If you have trouble detecting tags due to low light, you may want to try increasing exposure, but this will likely decrease your achievable framerate. +::: + +### AprilTags and Motion Blur + +For AprilTag pipelines, your goal is to reduce the "motion blur" as much as possible. Motion blur is the visual streaking/smearing on the camera stream as a result of movement of the camera or object of focus. You want to mitigate this as much as possible because your robot is constantly moving and you want to be able to read as many tags as you possibly can. The possible solutions to this include: + +1. Cranking your exposure as low as it goes and increasing your gain/brightness. This will decrease the effects of motion blur and increase FPS. +2. Using a global shutter (as opposed to rolling shutter) camera. This should eliminate most, if not all motion blur. +3. Only rely on tags when not moving. + +```{image} images/motionblur.gif +:align: center +``` + +## Orientation + +Orientation can be used to rotate the image prior to vision processing. This can be useful for cases where the camera is not oriented parallel to the ground. Do note that this operation can in some cases significantly reduce FPS. + +## Stream Resolution + +This changes the resolution which is used to stream frames from PhotonVision. This does not change the resolution used to perform vision processing. This is useful to reduce bandwidth consumption on the field. In some high-resolution cases, decreasing stream resolution can increase processing FPS. diff --git a/docs/source/docs/pipelines/output.md b/docs/source/docs/pipelines/output.md new file mode 100644 index 0000000..072cf19 --- /dev/null +++ b/docs/source/docs/pipelines/output.md @@ -0,0 +1,22 @@ +# Output + +The output card contains sections for target manipulation and offset modes. + +## Target Manipulation + +In this section, the Target Offset Point changes where the "center" of the target is. This can be useful if the pitch/yaw of the middle of the top edge of the target is desired, rather than the center of mass of the target. The "top"/"bottom"/"left"/"right" of the target are defined by the Target Orientation selection. For example, a 400x200px target in landscape mode would have the "top" offset point located at the middle of the uppermost long edge of the target, while in portrait mode the "top" offset point would be located in the middle of the topmost short edge (in this case, either the left or right sides). + +This section also includes a switch to enable processing and sending multiple targets, up to 5, simultaneously. This information is available through PhotonLib. Note that the {code}`GetPitch`/{code}`GetYaw` methods will report the pitch/yaw of the "best" (lowest indexed) target. + +```{raw} html + +``` + +## Robot Offset + +PhotonVision offers both single and dual point offset modes. In single point mode, the "Take Point" button will set the crosshair location to the center of the current "best" target. + +In dual point mode, two snapshots are required. Take one snapshot with the target far away, and the other with the target closer. The position of the crosshair will be linearly interpolated between these two points based on the area of the current "best" target. This might be useful if single point is not accurate across the range of the tracking distance, or for significantly offset cameras. diff --git a/docs/source/docs/programming/index.md b/docs/source/docs/programming/index.md new file mode 100644 index 0000000..3fe28f5 --- /dev/null +++ b/docs/source/docs/programming/index.md @@ -0,0 +1,11 @@ +--- +orphan: true +--- + +# Programming Reference + +```{toctree} +:maxdepth: 1 + +photonlib/index +``` diff --git a/docs/source/docs/programming/photonlib/adding-vendordep.md b/docs/source/docs/programming/photonlib/adding-vendordep.md new file mode 100644 index 0000000..5a08d93 --- /dev/null +++ b/docs/source/docs/programming/photonlib/adding-vendordep.md @@ -0,0 +1,48 @@ +# Installing PhotonLib + +## What is PhotonLib? + +PhotonLib is the C++ and Java vendor dependency that accompanies PhotonVision. We created this vendor dependency to make it easier for teams to retrieve vision data from their integrated vision system. + +PhotonLibPy is a minimal, pure-python implementation of PhotonLib. + +## Online Install - Java/C++ + +Click on the WPI icon on the top right of your VS Code window or hit Ctrl+Shift+P (Cmd+Shift+P on macOS) to bring up the command palette. Type, "Manage Vendor Libraries" and select the "WPILib: Manage Vendor Libraries" option. Then, select the "Install new library (online)" option. + +```{image} images/adding-offline-library.png +``` + +Paste the following URL into the box that pops up: + +`https://maven.photonvision.org/repository/internal/org/photonvision/photonlib-json/1.0/photonlib-json-1.0.json` + +:::{note} +It is recommended to Build Robot Code at least once when connected to the Internet before heading to an area where Internet connectivity is limited (for example, a competition). This ensures that the relevant files are downloaded to your filesystem. +::: + +Refer to [The WPILib docs](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) for more details on installing vendor libraries. + +## Offline Install - Java/C++ + +Download the latest PhotonLib release from our GitHub releases page (named something like `` photonlib-VERSION.zip` ``), and extract the contents to `$HOME/wpilib/YEAR`. This adds PhotonLib maven artifacts to your local maven repository. PhotonLib will now also appear available in the "install vendor libraries (offline)" menu in WPILib VSCode. Refer to [the WPILib docs](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) for more details on installing vendor libraries offline. + +## Install - Python + +Add photonlibpy to `pyproject.toml`. + +```toml +# Other pip packages to install +requires = [ + "photonlibpy", +] +``` + +See [The WPILib/RobotPy docs](https://docs.wpilib.org/en/stable/docs/software/python/pyproject_toml.html) for more information on using `pyproject.toml.` + +## Install Specific Version - Java/C++ + +In cases where you want to test a specific version of PhotonLib, make sure you have finished the steps in Online Install - Java/C++ and then manually change the version string in the PhotonLib vendordep json file(at ``/path/to/your/project/vendordep/photonlib.json``) to your desired version. + +```{image} images/photonlib-vendordep-json.png +``` diff --git a/docs/source/docs/programming/photonlib/controlling-led.md b/docs/source/docs/programming/photonlib/controlling-led.md new file mode 100644 index 0000000..dfaf9b3 --- /dev/null +++ b/docs/source/docs/programming/photonlib/controlling-led.md @@ -0,0 +1,20 @@ +# Controlling LEDs + +You can control the vision LEDs of supported hardware via PhotonLib using the `setLED()` method on a `PhotonCamera` instance. In Java and C++, an `VisionLEDMode` enum class is provided to choose values from. These values include, `kOff`, `kOn`, `kBlink`, and `kDefault`. `kDefault` uses the default LED value from the selected pipeline. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Blink the LEDs. + camera.setLED(VisionLEDMode.kBlink); + + .. code-block:: C++ + + // Blink the LEDs. + camera.SetLED(photonlib::VisionLEDMode::kBlink); + + .. code-block:: Python + + # Coming Soon! +``` diff --git a/docs/source/docs/programming/photonlib/driver-mode-pipeline-index.md b/docs/source/docs/programming/photonlib/driver-mode-pipeline-index.md new file mode 100644 index 0000000..03fc112 --- /dev/null +++ b/docs/source/docs/programming/photonlib/driver-mode-pipeline-index.md @@ -0,0 +1,72 @@ +# Driver Mode and Pipeline Index/Latency + +After {ref}`creating a PhotonCamera `, one can toggle Driver Mode and change the Pipeline Index of the vision program from robot code. + +## Toggle Driver Mode + +You can use the `setDriverMode()`/`SetDriverMode()` (Java and C++ respectively) to toggle driver mode from your robot program. Driver mode is an unfiltered / normal view of the camera to be used while driving the robot. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // Set driver mode to on. + camera.setDriverMode(true); + + .. code-block:: C++ + + // Set driver mode to on. + camera.SetDriverMode(true); + + .. code-block:: Python + + # Coming Soon! +``` + +## Setting the Pipeline Index + +You can use the `setPipelineIndex()`/`SetPipelineIndex()` (Java and C++ respectively) to dynamically change the vision pipeline from your robot program. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // Change pipeline to 2 + camera.setPipelineIndex(2); + + .. code-block:: C++ + + // Change pipeline to 2 + camera.SetPipelineIndex(2); + + .. code-block:: Python + + # Coming Soon! +``` + +## Getting the Pipeline Latency + +You can also get the pipeline latency from a pipeline result using the `getLatencyMillis()`/`GetLatency()` (Java and C++ respectively) methods on a `PhotonPipelineResult`. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Get the pipeline latency. + double latencySeconds = result.getLatencyMillis() / 1000.0; + + .. code-block:: C++ + + // Get the pipeline latency. + units::second_t latency = result.GetLatency(); + + .. code-block:: Python + + # Coming Soon! +``` + +:::{note} +The C++ version of PhotonLib returns the latency in a unit container. For more information on the Units library, see [here](https://docs.wpilib.org/en/stable/docs/software/basic-programming/cpp-units.html). +::: diff --git a/docs/source/docs/programming/photonlib/getting-target-data.md b/docs/source/docs/programming/photonlib/getting-target-data.md new file mode 100644 index 0000000..1d14088 --- /dev/null +++ b/docs/source/docs/programming/photonlib/getting-target-data.md @@ -0,0 +1,257 @@ +# Getting Target Data + +## Constructing a PhotonCamera + +### What is a PhotonCamera? + +`PhotonCamera` is a class in PhotonLib that allows a user to interact with one camera that is connected to hardware that is running PhotonVision. Through this class, users can retrieve yaw, pitch, roll, robot-relative pose, latency, and a wealth of other information. + +The `PhotonCamera` class has two constructors: one that takes a `NetworkTable` and another that takes in the name of the network table that PhotonVision is broadcasting information over. For ease of use, it is recommended to use the latter. The name of the NetworkTable (for the string constructor) should be the same as the camera's nickname (from the PhotonVision UI). + +```{eval-rst} +.. tab-set-code:: + + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-java-examples/src/main/java/org/photonlib/examples/aimattarget/Robot.java + :language: java + :lines: 51-52 + + .. rli:: https://github.com/PhotonVision/photonvision/raw/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-cpp-examples/src/main/cpp/examples/aimattarget/include/Robot.h + :language: c++ + :lines: 42-43 + + .. code-block:: Python + + # Change this to match the name of your camera as shown in the web ui + self.camera = PhotonCamera("your_camera_name_here") + +``` + +:::{warning} +Teams must have unique names for all of their cameras regardless of which coprocessor they are attached to. +::: + +## Getting the Pipeline Result + +### What is a Photon Pipeline Result? + +A `PhotonPipelineResult` is a container that contains all information about currently detected targets from a `PhotonCamera`. You can retrieve the latest pipeline result using the PhotonCamera instance. + +Use the `getLatestResult()`/`GetLatestResult()` (Java and C++ respectively) to obtain the latest pipeline result. An advantage of using this method is that it returns a container with information that is guaranteed to be from the same timestamp. This is important if you are using this data for latency compensation or in an estimator. + +```{eval-rst} +.. tab-set-code:: + + + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-java-examples/src/main/java/org/photonlib/examples/aimattarget/Robot.java + :language: java + :lines: 79-80 + + .. rli:: https://github.com/PhotonVision/photonvision/raw/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-cpp-examples/src/main/cpp/examples/aimattarget/cpp/Robot.cpp + :language: c++ + :lines: 35-36 + + .. code-block:: Python + + # Query the latest result from PhotonVision + result = self.camera.getLatestResult() + + +``` + +:::{note} +Unlike other vision software solutions, using the latest result guarantees that all information is from the same timestamp. This is achievable because the PhotonVision backend sends a byte-packed string of data which is then deserialized by PhotonLib to get target data. For more information, check out the [PhotonLib source code](https://github.com/PhotonVision/photonvision/tree/master/photon-lib). +::: + +## Checking for Existence of Targets + +Each pipeline result has a `hasTargets()`/`HasTargets()` (Java and C++ respectively) method to inform the user as to whether the result contains any targets. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Check if the latest result has any targets. + boolean hasTargets = result.hasTargets(); + + .. code-block:: C++ + + // Check if the latest result has any targets. + bool hasTargets = result.HasTargets(); + + .. code-block:: Python + + # Check if the latest result has any targets. + hasTargets = result.hasTargets() +``` + +:::{warning} +In Java/C++, You must *always* check if the result has a target via `hasTargets()`/`HasTargets()` before getting targets or else you may get a null pointer exception. Further, you must use the same result in every subsequent call in that loop. +::: + +## Getting a List of Targets + +### What is a Photon Tracked Target? + +A tracked target contains information about each target from a pipeline result. This information includes yaw, pitch, area, and robot relative pose. + +You can get a list of tracked targets using the `getTargets()`/`GetTargets()` (Java and C++ respectively) method from a pipeline result. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Get a list of currently tracked targets. + List targets = result.getTargets(); + + .. code-block:: C++ + + // Get a list of currently tracked targets. + wpi::ArrayRef targets = result.GetTargets(); + + .. code-block:: Python + + # Get a list of currently tracked targets. + targets = result.getTargets() +``` + +## Getting the Best Target + +You can get the {ref}`best target ` using `getBestTarget()`/`GetBestTarget()` (Java and C++ respectively) method from the pipeline result. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Get the current best target. + PhotonTrackedTarget target = result.getBestTarget(); + + .. code-block:: C++ + + // Get the current best target. + photonlib::PhotonTrackedTarget target = result.GetBestTarget(); + + + .. code-block:: Python + + # Coming Soon! + +``` + +## Getting Data From A Target + +- double `getYaw()`/`GetYaw()`: The yaw of the target in degrees (positive right). +- double `getPitch()`/`GetPitch()`: The pitch of the target in degrees (positive up). +- double `getArea()`/`GetArea()`: The area (how much of the camera feed the bounding box takes up) as a percent (0-100). +- double `getSkew()`/`GetSkew()`: The skew of the target in degrees (counter-clockwise positive). +- double\[\] `getCorners()`/`GetCorners()`: The 4 corners of the minimum bounding box rectangle. +- Transform2d `getCameraToTarget()`/`GetCameraToTarget()`: The camera to target transform. See [2d transform documentation here](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/geometry/transformations.html#transform2d-and-twist2d). + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Get information from target. + double yaw = target.getYaw(); + double pitch = target.getPitch(); + double area = target.getArea(); + double skew = target.getSkew(); + Transform2d pose = target.getCameraToTarget(); + List corners = target.getCorners(); + + .. code-block:: C++ + + // Get information from target. + double yaw = target.GetYaw(); + double pitch = target.GetPitch(); + double area = target.GetArea(); + double skew = target.GetSkew(); + frc::Transform2d pose = target.GetCameraToTarget(); + wpi::SmallVector, 4> corners = target.GetCorners(); + + .. code-block:: Python + + # Get information from target. + yaw = target.getYaw() + pitch = target.getPitch() + area = target.getArea() + skew = target.getSkew() + pose = target.getCameraToTarget() + corners = target.getDetectedCorners() +``` + +## Getting AprilTag Data From A Target + +:::{note} +All of the data above (**except skew**) is available when using AprilTags. +::: + +- int `getFiducialId()`/`GetFiducialId()`: The ID of the detected fiducial marker. +- double `getPoseAmbiguity()`/`GetPoseAmbiguity()`: How ambiguous the pose of the target is (see below). +- Transform3d `getBestCameraToTarget()`/`GetBestCameraToTarget()`: Get the transform that maps camera space (X = forward, Y = left, Z = up) to object/fiducial tag space (X forward, Y left, Z up) with the lowest reprojection error. +- Transform3d `getAlternateCameraToTarget()`/`GetAlternateCameraToTarget()`: Get the transform that maps camera space (X = forward, Y = left, Z = up) to object/fiducial tag space (X forward, Y left, Z up) with the highest reprojection error. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Get information from target. + int targetID = target.getFiducialId(); + double poseAmbiguity = target.getPoseAmbiguity(); + Transform3d bestCameraToTarget = target.getBestCameraToTarget(); + Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget(); + + .. code-block:: C++ + + // Get information from target. + int targetID = target.GetFiducialId(); + double poseAmbiguity = target.GetPoseAmbiguity(); + frc::Transform3d bestCameraToTarget = target.getBestCameraToTarget(); + frc::Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget(); + + .. code-block:: Python + + # Get information from target. + targetID = target.getFiducialId() + poseAmbiguity = target.getPoseAmbiguity() + bestCameraToTarget = target.getBestCameraToTarget() + alternateCameraToTarget = target.getAlternateCameraToTarget() +``` + +## Saving Pictures to File + +A `PhotonCamera` can save still images from the input or output video streams to file. This is useful for debugging what a camera is seeing while on the field and confirming targets are being identified properly. + +Images are stored within the PhotonVision configuration directory. Running the "Export" operation in the settings tab will download a .zip file which contains the image captures. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // Capture pre-process camera stream image + camera.takeInputSnapshot(); + + // Capture post-process camera stream image + camera.takeOutputSnapshot(); + + .. code-block:: C++ + + // Capture pre-process camera stream image + camera.TakeInputSnapshot(); + + // Capture post-process camera stream image + camera.TakeOutputSnapshot(); + + .. code-block:: Python + + # Capture pre-process camera stream image + camera.takeInputSnapshot() + + # Capture post-process camera stream image + camera.takeOutputSnapshot() +``` + +:::{note} +Saving images to file takes a bit of time and uses up disk space, so doing it frequently is not recommended. In general, the camera will save an image every 500ms. Calling these methods faster will not result in additional images. Consider tying image captures to a button press on the driver controller, or an appropriate point in an autonomous routine. +::: diff --git a/docs/source/docs/programming/photonlib/images/adding-offline-library.png b/docs/source/docs/programming/photonlib/images/adding-offline-library.png new file mode 100644 index 0000000..67afacf Binary files /dev/null and b/docs/source/docs/programming/photonlib/images/adding-offline-library.png differ diff --git a/docs/source/docs/programming/photonlib/images/photonlib-vendordep-json.png b/docs/source/docs/programming/photonlib/images/photonlib-vendordep-json.png new file mode 100644 index 0000000..6362174 Binary files /dev/null and b/docs/source/docs/programming/photonlib/images/photonlib-vendordep-json.png differ diff --git a/docs/source/docs/programming/photonlib/index.md b/docs/source/docs/programming/photonlib/index.md new file mode 100644 index 0000000..0e38862 --- /dev/null +++ b/docs/source/docs/programming/photonlib/index.md @@ -0,0 +1,12 @@ +# PhotonLib: Robot Code Interface + +```{toctree} +:maxdepth: 1 + +adding-vendordep +getting-target-data +using-target-data +robot-pose-estimator +driver-mode-pipeline-index +controlling-led +``` diff --git a/docs/source/docs/programming/photonlib/robot-pose-estimator.md b/docs/source/docs/programming/photonlib/robot-pose-estimator.md new file mode 100644 index 0000000..6dfd449 --- /dev/null +++ b/docs/source/docs/programming/photonlib/robot-pose-estimator.md @@ -0,0 +1,144 @@ +# AprilTags and PhotonPoseEstimator + +:::{note} +For more information on how to methods to get AprilTag data, look {ref}`here `. +::: + +PhotonLib includes a `PhotonPoseEstimator` class, which allows you to combine the pose data from all tags in view in order to get a field relative pose. The `PhotonPoseEstimator` class works with one camera per object instance, but more than one instance may be created. + +## Creating an `AprilTagFieldLayout` + +`AprilTagFieldLayout` is used to represent a layout of AprilTags within a space (field, shop at home, classroom, etc.). WPILib provides a JSON that describes the layout of AprilTags on the field which you can then use in the AprilTagFieldLayout constructor. You can also specify a custom layout. + +The API documentation can be found in here: [Java](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/apriltag/AprilTagFieldLayout.html) and [C++](https://github.wpilib.org/allwpilib/docs/release/cpp/classfrc_1_1_april_tag_field_layout.html). + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // The field from AprilTagFields will be different depending on the game. + AprilTagFieldLayout aprilTagFieldLayout = AprilTagFields.k2024Crescendo.loadAprilTagLayoutField(); + + .. code-block:: C++ + + // The parameter for LoadAPrilTagLayoutField will be different depending on the game. + frc::AprilTagFieldLayout aprilTagFieldLayout = frc::LoadAprilTagLayoutField(frc::AprilTagField::k2024Crescendo); + + .. code-block:: Python + + # Coming Soon! + +``` + +## Creating a `PhotonPoseEstimator` + +The PhotonPoseEstimator has a constructor that takes an `AprilTagFieldLayout` (see above), `PoseStrategy`, `PhotonCamera`, and `Transform3d`. `PoseStrategy` has six possible values: + +- MULTI_TAG_PNP_ON_COPROCESSOR + - Calculates a new robot position estimate by combining all visible tag corners. Recommended for all teams as it will be the most accurate. + - Must configure the AprilTagFieldLayout properly in the UI, please see {ref}`here ` for more information. +- LOWEST_AMBIGUITY + - Choose the Pose with the lowest ambiguity. +- CLOSEST_TO_CAMERA_HEIGHT + - Choose the Pose which is closest to the camera height. +- CLOSEST_TO_REFERENCE_POSE + - Choose the Pose which is closest to the pose from setReferencePose(). +- CLOSEST_TO_LAST_POSE + - Choose the Pose which is closest to the last pose calculated. +- AVERAGE_BEST_TARGETS + - Choose the Pose which is the average of all the poses from each tag. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + //Forward Camera + cam = new PhotonCamera("testCamera"); + Transform3d robotToCam = new Transform3d(new Translation3d(0.5, 0.0, 0.5), new Rotation3d(0,0,0)); //Cam mounted facing forward, half a meter forward of center, half a meter up from center. + + // Construct PhotonPoseEstimator + PhotonPoseEstimator photonPoseEstimator = new PhotonPoseEstimator(aprilTagFieldLayout, PoseStrategy.CLOSEST_TO_REFERENCE_POSE, cam, robotToCam); + + .. code-block:: C++ + + // Forward Camera + std::shared_ptr cameraOne = + std::make_shared("testCamera"); + // Camera is mounted facing forward, half a meter forward of center, half a + // meter up from center. + frc::Transform3d robotToCam = + frc::Transform3d(frc::Translation3d(0.5_m, 0_m, 0.5_m), + frc::Rotation3d(0_rad, 0_rad, 0_rad)); + + // ... Add other cameras here + + // Assemble the list of cameras & mount locations + std::vector< + std::pair, frc::Transform3d>> + cameras; + cameras.push_back(std::make_pair(cameraOne, robotToCam)); + + photonlib::RobotPoseEstimator estimator( + aprilTags, photonlib::CLOSEST_TO_REFERENCE_POSE, cameras); + + .. code-block:: Python + + kRobotToCam = wpimath.geometry.Transform3d( + wpimath.geometry.Translation3d(0.5, 0.0, 0.5), + wpimath.geometry.Rotation3d.fromDegrees(0.0, -30.0, 0.0), + ) + + self.cam = PhotonCamera("YOUR CAMERA NAME") + + self.camPoseEst = PhotonPoseEstimator( + loadAprilTagLayoutField(AprilTagField.k2024Crescendo), + PoseStrategy.CLOSEST_TO_REFERENCE_POSE, + self.cam, + kRobotToCam + ) +``` + +## Using a `PhotonPoseEstimator` + +Calling `update()` on your `PhotonPoseEstimator` will return an `EstimatedRobotPose`, which includes a `Pose3d` of the latest estimated pose (using the selected strategy) along with a `double` of the timestamp when the robot pose was estimated. You should be updating your [drivetrain pose estimator](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/state-space/state-space-pose-estimators.html) with the result from the `PhotonPoseEstimator` every loop using `addVisionMeasurement()`. + +```{eval-rst} +.. tab-set-code:: + .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/357d8a518a93f7a1f8084a79449249e613b605a7/photonlib-java-examples/apriltagExample/src/main/java/frc/robot/PhotonCameraWrapper.java + :language: java + :lines: 85-88 + + .. code-block:: C++ + + std::pair getEstimatedGlobalPose( + frc::Pose3d prevEstimatedRobotPose) { + robotPoseEstimator.SetReferencePose(prevEstimatedRobotPose); + units::millisecond_t currentTime = frc::Timer::GetFPGATimestamp(); + auto result = robotPoseEstimator.Update(); + if (result.second) { + return std::make_pair<>(result.first.ToPose2d(), + currentTime - result.second); + } else { + return std::make_pair(frc::Pose2d(), 0_ms); + } + } + + .. code-block:: Python + + # Coming Soon! + + + +``` + +You should be updating your [drivetrain pose estimator](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/state-space/state-space-pose-estimators.html) with the result from the `RobotPoseEstimator` every loop using `addVisionMeasurement()`. TODO: add example note + +## Additional `PhotonPoseEstimator` Methods + +### `setReferencePose(Pose3d referencePose)` + +Updates the stored reference pose when using the CLOSEST_TO_REFERENCE_POSE strategy. + +### `setLastPose(Pose3d lastPose)` + +Update the stored last pose. Useful for setting the initial estimate when using the CLOSEST_TO_LAST_POSE strategy. diff --git a/docs/source/docs/programming/photonlib/using-target-data.md b/docs/source/docs/programming/photonlib/using-target-data.md new file mode 100644 index 0000000..ff70071 --- /dev/null +++ b/docs/source/docs/programming/photonlib/using-target-data.md @@ -0,0 +1,138 @@ +# Using Target Data + +A `PhotonUtils` class with helpful common calculations is included within `PhotonLib` to aid teams in using AprilTag data in order to get positional information on the field. This class contains two methods, `calculateDistanceToTargetMeters()`/`CalculateDistanceToTarget()` and `estimateTargetTranslation2d()`/`EstimateTargetTranslation()` (Java and C++ respectively). + +## Estimating Field Relative Pose with AprilTags + +`estimateFieldToRobotAprilTag(Transform3d cameraToTarget, Pose3d fieldRelativeTagPose, Transform3d cameraToRobot)` returns your robot's `Pose3d` on the field using the pose of the AprilTag relative to the camera, pose of the AprilTag relative to the field, and the transform from the camera to the origin of the robot. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Calculate robot's field relative pose + if (aprilTagFieldLayout.getTagPose(target.getFiducialId()).isPresent()) { + Pose3d robotPose = PhotonUtils.estimateFieldToRobotAprilTag(target.getBestCameraToTarget(), aprilTagFieldLayout.getTagPose(target.getFiducialId()).get(), cameraToRobot); + } + .. code-block:: C++ + + //TODO + + .. code-block:: Python + + # Coming Soon! +``` + +## Estimating Field Relative Pose (Traditional) + +You can get your robot's `Pose2D` on the field using various camera data, target yaw, gyro angle, target pose, and camera position. This method estimates the target's relative position using `estimateCameraToTargetTranslation` (which uses pitch and yaw to estimate range and heading), and the robot's gyro to estimate the rotation of the target. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Calculate robot's field relative pose + Pose2D robotPose = PhotonUtils.estimateFieldToRobot( + kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, Rotation2d.fromDegrees(-target.getYaw()), gyro.getRotation2d(), targetPose, cameraToRobot); + + .. code-block:: C++ + + // Calculate robot's field relative pose + frc::Pose2D robotPose = photonlib::EstimateFieldToRobot( + kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, frc::Rotation2d(units::degree_t(-target.GetYaw())), frc::Rotation2d(units::degree_t(gyro.GetRotation2d)), targetPose, cameraToRobot); + + .. code-block:: Python + + # Coming Soon! + +``` + +## Calculating Distance to Target + +If your camera is at a fixed height on your robot and the height of the target is fixed, you can calculate the distance to the target based on your camera's pitch and the pitch to the target. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // TODO + + .. code-block:: C++ + + // TODO + + .. code-block:: Python + + # Coming Soon! + +``` + +:::{note} +The C++ version of PhotonLib uses the Units library. For more information, see [here](https://docs.wpilib.org/en/stable/docs/software/basic-programming/cpp-units.html). +::: + +## Calculating Distance Between Two Poses + +`getDistanceToPose(Pose2d robotPose, Pose2d targetPose)` allows you to calculate the distance between two poses. This is useful when using AprilTags, given that there may not be an AprilTag directly on the target. + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + double distanceToTarget = PhotonUtils.getDistanceToPose(robotPose, targetPose); + + .. code-block:: C++ + + //TODO + + .. code-block:: Python + + # Coming Soon! +``` + +## Estimating Camera Translation to Target + +You can get a [translation](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/geometry/pose.html#translation) to the target based on the distance to the target (calculated above) and angle to the target (yaw). + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + // Calculate a translation from the camera to the target. + Translation2d translation = PhotonUtils.estimateCameraToTargetTranslation( + distanceMeters, Rotation2d.fromDegrees(-target.getYaw())); + + .. code-block:: C++ + + // Calculate a translation from the camera to the target. + frc::Translation2d translation = photonlib::PhotonUtils::EstimateCameraToTargetTranslationn( + distance, frc::Rotation2d(units::degree_t(-target.GetYaw()))); + + .. code-block:: Python + + # Coming Soon! + +``` + +:::{note} +We are negating the yaw from the camera from CV (computer vision) conventions to standard mathematical conventions. In standard mathematical conventions, as you turn counter-clockwise, angles become more positive. +::: + +## Getting the Yaw To a Pose + +`getYawToPose(Pose2d robotPose, Pose2d targetPose)` returns the `Rotation2d` between your robot and a target. This is useful when turning towards an arbitrary target on the field (ex. the center of the hub in 2022). + +```{eval-rst} +.. tab-set-code:: + .. code-block:: Java + + Rotation2d targetYaw = PhotonUtils.getYawToPose(robotPose, targetPose); + .. code-block:: C++ + + //TODO + + .. code-block:: Python + + # Coming Soon! +``` diff --git a/docs/source/docs/quick-start/arducam-cameras.md b/docs/source/docs/quick-start/arducam-cameras.md new file mode 100644 index 0000000..82eaaaf --- /dev/null +++ b/docs/source/docs/quick-start/arducam-cameras.md @@ -0,0 +1,22 @@ +# Arducam Cameras + +Arducam cameras are supported for setups with multiple devices. This is possible because Arducam provides software that allows you to assign truly different device names to each camera. This feature is particularly useful in complex setups where multiple cameras are used simultaneously. + +## Setting Up Arducam Cameras + +1. **Download Arducam Software**: [Download and install the Arducam software from their official website.](https://docs.arducam.com/UVC-Camera/Serial-Number-Tool-Guide/) + +2. **Assign Device Names**: Use the Arducam software and Arducam [documentation](https://docs.arducam.com/UVC-Camera/Serial-Number-Tool-Guide/) to give each camera a unique device name. This will help in distinguishing between multiple cameras in your setup. + +## Steps to Configure in PhotonVision + +1. **Open PhotonVision Settings**: Navigate to the cameras page in PhotonVision. + +2. **Select Camera Model**: Select the proper camera. Use the Arducam model selector to specify the model of each Arducam camera connected to your system. + +3. **Save Settings**: Ensure that you save the settings after selecting the appropriate camera model for each device. + +```{image} images/setArducamModel.png +:alt: The camera model can be selected from the Arudcam model selector in the cameras tab +:align: center +``` diff --git a/docs/source/docs/quick-start/camera-calibration.md b/docs/source/docs/quick-start/camera-calibration.md new file mode 100644 index 0000000..8584750 --- /dev/null +++ b/docs/source/docs/quick-start/camera-calibration.md @@ -0,0 +1,33 @@ +# Camera Calibration + +:::{important} +In order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance. +::: + +If you’re not using cameras in 3D mode, calibration is optional, but it can still offer benefits. Calibrating cameras helps refine the pitch and yaw values, leading to more accurate positional data in every mode. {ref}`For a more in-depth view`. + +## Print the Calibration Target + +- Downloaded from our [demo site](https://demo.photonvision.org/#/cameras), or directly from your coprocessors cameras tab. +- Use the Charuco calibration board: + - Board Type: Charuco + - Tag Family: 4x4 + - Pattern Spacing: 1.00in + - Marker Size: 0.75in + - Board Height : 8 + - Board Width : 8 + +## Prepare the Calibration Target + +- Measure Accurately: Use calipers to measure the actual size of the squares and markers. Accurate measurements are crucial for effective calibration. +- Ensure Flatness: The calibration board must be perfectly flat, without any wrinkles or bends, to avoid introducing errors into the calibration process. + +## Calibrate your Camera + +- Take lots of photos: It's recommended to capture more than 50 images to properly calibrate your camera for accuracy. 12 is the bare minimum and may not provide good results. +- Other Tips + - Move the board not the camera. + - Take photos of lots of angles: The more angles the more better (up to 45 deg). + - A couple of up close images is good. + - Cover the entire cameras fov. + - Avoid images with the board facing straight towards the camera. diff --git a/docs/source/docs/quick-start/common-setups.md b/docs/source/docs/quick-start/common-setups.md new file mode 100644 index 0000000..888e270 --- /dev/null +++ b/docs/source/docs/quick-start/common-setups.md @@ -0,0 +1,49 @@ +# Common Hardware Setups + +## Coprocessors + +:::{note} +The Orange Pi 5 is the only currently supported device for object detection. +::: + +- Orange Pi 5 4GB + - Able to process two object detection streams at once while also processing 1 to 2 AprilTag streams at 1280x800 (30fps). +- Raspberry Pi 5 2GB + - A good cheaper option. Doesn't support object detection. Able to process 2 AprilTag streams at 1280x800 (30fps). + +## SD Cards + +:::{important} +It is highly recommended that you use an industrial micro SD card, as they offer far greater protection against corruption from improper shutdowns, like most cards +face every time the robot is turned off. +::: + +- 8GB or larger micro SD card + - Many teams have found that an industrial micro sd card are much more stable in competition. One example is the SanDisk industrial 16GB micro SD card. + +## Cameras + +- AprilTag + + - Innomaker or Arducam OV9281 UVC USB cameras. + +- Object Detection + + - Arducam OV9782 works well with its global shutter. + - Most other fixed-focus color UVC USB webcams. + +- Driver Camera + - OV9281 + - OV9782 + - Pi Camera Module V1 {ref}`(More setup info)` + - Most other fixed-focus UVC USB webcams + +## Power + +- Pololu S13V30F5 Regulator + + - Wide power range input. Recommended by many teams. + +- Redux Robotics Zinc-V Regulator + + - Recently released for the 2025 season, offering reliable and easy integration. diff --git a/docs/source/docs/quick-start/images/OrangePiPololu.png b/docs/source/docs/quick-start/images/OrangePiPololu.png new file mode 100644 index 0000000..54336f2 Binary files /dev/null and b/docs/source/docs/quick-start/images/OrangePiPololu.png differ diff --git a/docs/source/docs/quick-start/images/OrangePiPololuPigtail.png b/docs/source/docs/quick-start/images/OrangePiPololuPigtail.png new file mode 100644 index 0000000..172ba74 Binary files /dev/null and b/docs/source/docs/quick-start/images/OrangePiPololuPigtail.png differ diff --git a/docs/source/docs/quick-start/images/OrangePiZinc.png b/docs/source/docs/quick-start/images/OrangePiZinc.png new file mode 100644 index 0000000..9dcc106 Binary files /dev/null and b/docs/source/docs/quick-start/images/OrangePiZinc.png differ diff --git a/docs/source/docs/quick-start/images/OrangePiZincUSBC.png b/docs/source/docs/quick-start/images/OrangePiZincUSBC.png new file mode 100644 index 0000000..0bffbc5 Binary files /dev/null and b/docs/source/docs/quick-start/images/OrangePiZincUSBC.png differ diff --git a/docs/source/docs/quick-start/images/RPiPololu.png b/docs/source/docs/quick-start/images/RPiPololu.png new file mode 100644 index 0000000..dd1ae28 Binary files /dev/null and b/docs/source/docs/quick-start/images/RPiPololu.png differ diff --git a/docs/source/docs/quick-start/images/RPiPololuPigtail.png b/docs/source/docs/quick-start/images/RPiPololuPigtail.png new file mode 100644 index 0000000..10cc115 Binary files /dev/null and b/docs/source/docs/quick-start/images/RPiPololuPigtail.png differ diff --git a/docs/source/docs/quick-start/images/RPiZinc.png b/docs/source/docs/quick-start/images/RPiZinc.png new file mode 100644 index 0000000..f85530e Binary files /dev/null and b/docs/source/docs/quick-start/images/RPiZinc.png differ diff --git a/docs/source/docs/quick-start/images/RPiZincUSBC.png b/docs/source/docs/quick-start/images/RPiZincUSBC.png new file mode 100644 index 0000000..ea66a1c Binary files /dev/null and b/docs/source/docs/quick-start/images/RPiZincUSBC.png differ diff --git a/docs/source/docs/quick-start/images/editCameraName.png b/docs/source/docs/quick-start/images/editCameraName.png new file mode 100644 index 0000000..ccadcb4 Binary files /dev/null and b/docs/source/docs/quick-start/images/editCameraName.png differ diff --git a/docs/source/docs/quick-start/images/editHostname.png b/docs/source/docs/quick-start/images/editHostname.png new file mode 100644 index 0000000..eb897d7 Binary files /dev/null and b/docs/source/docs/quick-start/images/editHostname.png differ diff --git a/docs/source/docs/quick-start/images/motionblur.png b/docs/source/docs/quick-start/images/motionblur.png new file mode 100644 index 0000000..ed6cb4c Binary files /dev/null and b/docs/source/docs/quick-start/images/motionblur.png differ diff --git a/docs/source/docs/quick-start/images/networking-diagram-vividhosting.png b/docs/source/docs/quick-start/images/networking-diagram-vividhosting.png new file mode 100644 index 0000000..a66d8f4 Binary files /dev/null and b/docs/source/docs/quick-start/images/networking-diagram-vividhosting.png differ diff --git a/docs/source/docs/quick-start/images/networking-diagram.png b/docs/source/docs/quick-start/images/networking-diagram.png new file mode 100644 index 0000000..733c301 Binary files /dev/null and b/docs/source/docs/quick-start/images/networking-diagram.png differ diff --git a/docs/source/docs/quick-start/images/setArducamModel.png b/docs/source/docs/quick-start/images/setArducamModel.png new file mode 100644 index 0000000..8f42120 Binary files /dev/null and b/docs/source/docs/quick-start/images/setArducamModel.png differ diff --git a/docs/source/docs/quick-start/images/static.png b/docs/source/docs/quick-start/images/static.png new file mode 100644 index 0000000..b5e66e4 Binary files /dev/null and b/docs/source/docs/quick-start/images/static.png differ diff --git a/docs/source/docs/quick-start/index.md b/docs/source/docs/quick-start/index.md new file mode 100644 index 0000000..e9cce5d --- /dev/null +++ b/docs/source/docs/quick-start/index.md @@ -0,0 +1,13 @@ +# Quick Start + +```{toctree} +:maxdepth: 2 + +common-setups +quick-install +wiring +networking +arducam-cameras +camera-calibration +quick-configure +``` diff --git a/docs/source/docs/quick-start/networking.md b/docs/source/docs/quick-start/networking.md new file mode 100644 index 0000000..bbf30fa --- /dev/null +++ b/docs/source/docs/quick-start/networking.md @@ -0,0 +1,91 @@ +# Networking + +## Physical Networking + +:::{warning} +When using PhotonVision off robot, you _MUST_ plug the coprocessor into a physical router/radio. You can then connect your laptop/device used to view the webdashboard to the same network. Any other networking setup will not work and will not be supported in any capacity. +::: + +::::{tab-set} + +:::{tab-item} New Radio (2025 - present) + +```{danger} +Ensure that DIP switches 1 and 2 are turned off; otherwise, the radio PoE feature will fry your coprocessor. [More info.](https://frc-radio.vivid-hosting.net/getting-started/passive-power-over-ethernet-poe-for-downstream-devices) +``` + +```{image} images/networking-diagram-vividhosting.png +:alt: Wiring using a network switch and the new vivid hosting radio +``` + +::: + +:::{tab-item} Old Radio (pre 2025) + +PhotonVision _STRONGLY_ recommends the usage of a network switch on your robot. This is because the second radio port on the old FRC radios is known to be buggy and cause frequent connection issues that are detrimental during competition. An in-depth guide on how to install a network switch can be found [on FRC 900's website](https://zebracorns.org/blog/ZebraSwitch/). + +```{image} images/networking-diagram.png +:alt: Wiring using a network switch and the old open mesh radio +``` + +::: +:::: + +## Network Hostname + +Rename each device from the default "Photonvision" to a unique hostname (e.g., "Photon-OrangePi-Left" or "Photon-RPi5-Back"). This helps differentiate multiple coprocessors on your network, making it easier to manage them. Navigate to the settings page and scroll down to the network section. You will find the hostname is set to "photonvision" by default, this can only contain letters (A-Z), numeric characters (0-9), and the minus sign (-). + +```{image} images/editHostname.png +:alt: The hostname can be edited in the settings page under the network section. +``` + +## Digital Networking + +PhotonVision _STRONGLY_ recommends the usage of Static IPs as it increases reliability on the field and when using PhotonVision in general. To properly set up your static IP, follow the steps below: + +:::{warning} +Only use a static IP when connected to the **robot radio**, and never when testing at home, unless you are well versed in networking or have the relevant "know how". +::: + +1. Ensure your robot is on and you are connected to the robot network. +2. Navigate to `photonvision.local:5800`in your browser. +3. Open the settings tab on the left pane. +4. Under the Networking section, set your team number. +5. Change your IP to Static. +6. Set your coprocessor's IP address to “10.TE.AM.11”. More information on IP format can be found [here](https://docs.wpilib.org/en/stable/docs/networking/networking-introduction/ip-configurations.html#on-the-field-static-configuration). +7. Click the “Save” button. +8. Set your roboRIO to the following static IP address: “10.TE.AM.2”. This can be done via the [roboRIO web dashboard](https://docs.wpilib.org/en/stable/docs/software/roborio-info/roborio-web-dashboard.html#roborio-web-dashboard). + +Power-cycle your robot and then you will now be access the PhotonVision dashboard at `10.TE.AM.11:5800`. + +```{image} images/static.png +:alt: Correctly set static IP +``` + +## Port Forwarding + +If you would like to access your Ethernet-connected vision device from a computer when tethered to the USB port on the roboRIO, you can use [WPILib's](https://docs.wpilib.org/en/stable/docs/networking/networking-utilities/portforwarding.html) `PortForwarder`. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + PortForwarder.add(5800, "photonvision.local", 5800); + + .. code-block:: C++ + + wpi::PortForwarder::GetInstance().Add(5800, "photonvision.local", 5800); + + .. code-block:: Python + + # Coming Soon! +``` + +:::{note} +The address in the code above (`photonvision.local`) is the hostname of the coprocessor. This can be different depending on your hardware, and can be checked in the settings tab under "hostname". +::: + +## Camera Stream Ports + +The camera streams start at 1181 with two ports for each camera (ex. 1181 and 1182 for camera one, 1183 and 1184 for camera two, etc.). The easiest way to identify the port of the camera that you want is by double clicking on the stream, which opens it in a separate page. The port will be listed below the stream. diff --git a/docs/source/docs/quick-start/quick-configure.md b/docs/source/docs/quick-start/quick-configure.md new file mode 100644 index 0000000..f2bafb7 --- /dev/null +++ b/docs/source/docs/quick-start/quick-configure.md @@ -0,0 +1,57 @@ +# Quick Configure + +## Settings to configure + +### Team number + +In order for photonvision to connect to the roborio it needs to know your team number. + +### Camera Nickname + +You **must** nickname your cameras in PhotonVision to ensure that every camera has a unique name. This is how you will identify cameras in robot code. The camera can be nicknamed using the edit button next to the camera name in the upper right of the Dashboard tab. + +```{image} images/editCameraName.png +:align: center +``` + +## Pipeline Settings + +### AprilTag + +When using an Orange Pi 5 with an Arducam OV9281 teams will usually change the following settings. For more info on AprilTag settings please review {ref}`this`. + +- Resolution: + - 1280x800 +- Decimate: + - 2 +- Mode: + - 3D +- Exposure and Gain: + - Adjust these to achieve good brightness without flicker and low motion blur. This may vary based on lighting conditions in your competition environment. +- Enable MultiTag +- Set arducam specific camera type selector to OV9281 + +#### AprilTags and Motion Blur and Rolling Shutter + +When detecting AprilTags, it's important to minimize 'motion blur' as much as possible. Motion blur appears as visual streaking or smearing in the camera feed, resulting from the movement of either the camera or the object in focus. Reducing this effect is essential, as the robot is often in motion, and a clearer image allows for detecting as many tags as possible. This is not to be confused with {ref}`rolling shutter`. + +- Fixes + - Lower your exposure as low as possible. Using gain and brightness to account for lack of brightness. +- Other Options: + - Don't use/rely on vision measurements while moving. + +```{image} images/motionblur.png +:align: center +``` + +### Object Detection + +When using an Orange Pi 5 with an OV9782 teams will usually change the following settings. For more info on object detection settings please review {ref}`this`. + +- Resolution: + - Resolutions higher than 640x640 may not result in any higher detection accuracy and may lower {ref}`performance`. +- Confidence: + - 0.75 - 0.95 Lower values are for detecting worn game pieces or less ideal game pieces. Higher for less worn, more ideal game pieces. +- White Balance Temperature: + - Adjust this to achieve better color accuracy. This may be needed to increase confidence. +- Set arducam specific camera type selector to OV9782 diff --git a/docs/source/docs/quick-start/quick-install.md b/docs/source/docs/quick-start/quick-install.md new file mode 100644 index 0000000..1b8e9ca --- /dev/null +++ b/docs/source/docs/quick-start/quick-install.md @@ -0,0 +1,38 @@ +# Quick Install + +## Install the latest image of photonvision for your coprocessor + +- For the supported coprocessors + - RPI 3,4,5 + - Orange Pi 5 + - Limelight + +For installing on non-supported devices {ref}`see. ` + +[Download the latest preconfigured image of photonvision for your coprocessor](https://github.com/PhotonVision/photonvision/releases/latest) + +| Coprocessor | Image filename | Jar | +| -------------------- | ---------------------------------------------------- | ------------------------------------- | +| OrangePi 5 | photonvision-{version}-linuxarm64_orangepi5.img.xz | photonvision-{version}-linuxarm64.jar | +| Raspberry Pi 3, 4, 5 | photonvision-{version}-linuxarm64_RaspberryPi.img.xz | photonvision-{version}-linuxarm64.jar | +| Limelight 2 | photonvision-{version}-linuxarm64_limelight2.img.xz | photonvision-{version}-linuxarm64.jar | +| Limelight 3 | photonvision-{version}-linuxarm64_limelight3.img.xz | photonvision-{version}-linuxarm64.jar | + +:::{warning} +Balena Etcher 1.18.11 is a known working version. Other versions may cause issues such as bootlooping (the system will repeatedly boot and restart) when imaging your device. +::: + +Use the 1.18.11 version of [Balena Etcher](https://github.com/balena-io/etcher/releases/tag/v1.18.11) to flash the image onto the coprocessors micro sd card. Select the downloaded `.img.xz` file, select your microSD card, and flash. + +Limelights have a different installation processes. Simply connect the limelight to your computer using the proper usb cable. Select the compute module. If it doesn’t show up after 30s try using another USB port, initialization may take a while. If prompted, install the recommended missing drivers. Select the image, and flash. + +Unless otherwise noted in release notes or if updating from the prior years version, to update PhotonVision after the initial installation, use the offline update option in the settings page with the downloaded jar file from the latest release. + +:::{note} +Limelight 2, 2+, and 3 will need a [custom hardware config file](https://github.com/PhotonVision/photonvision/tree/master/docs/source/docs/advanced-installation/sw_install/files) for lighting to work. Currently only limelight 2 and 2+ files are available. +::: + +:::{note} +Raspberry Pi installations may also use the [Raspberry Pi Imager](https://www.raspberrypi.com/software/) to flash the image. + +::: diff --git a/docs/source/docs/quick-start/wiring.md b/docs/source/docs/quick-start/wiring.md new file mode 100644 index 0000000..001e6bf --- /dev/null +++ b/docs/source/docs/quick-start/wiring.md @@ -0,0 +1,93 @@ +# Wiring + +## Coprocessor with regulator + +1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot. We recommend wiring the regulator directly to the power header pins or using a locking USB C cable. In any case we recommend hot gluing the connector. + +2. Run an ethernet cable from your Pi to your network switch / radio. + +This diagram shows how to use the recommended regulator to power a coprocessor. + +::::{tab-set} + +:::{tab-item} Orange Pi Zinc V USB C + +```{image} images/OrangePiZincUSBC.png +:alt: Wiring the opi5 to the pdp using the Redux Robotics Zinc V and usb c +``` + +::: + +:::{tab-item} Orange Pi 5 Zinc V + +```{image} images/OrangePiZinc.png +:alt: Wiring the opi5 to the pdp using the Redux Robotics Zinc V +``` + +::: + +:::{tab-item} Orange Pi 5 Pololu S13V30F5 + +```{image} images/OrangePiPololu.png +:alt: Wiring the opi5 to the pdp using the Pololu S13V30F5 +``` + +::: + +:::{tab-item} Orange Pi 5 Pololu S13V30F5 Pigtail + +```{image} images/OrangePiPololuPigtail.png +:alt: Wiring the opi5 to the pdp using the Pololu S13V30F5 and a usb c pigtail +``` + +::: + +:::{tab-item} Raspberry Pi 5 Zinc V USB C + +```{image} images/RPiZincUSBC.png +:alt: Wiring the RPI5 to the pdp using the Redux Robotics Zinc V and usb c +``` + +::: + +:::{tab-item} Raspberry Pi 5 Zinc V + +```{image} images/RPiZinc.png +:alt: Wiring the RPI5 to the pdp using the Redux Robotics Zinc V +``` + +::: + +:::{tab-item} Raspberry Pi 5 Pololu S13V30F5 + +```{image} images/RPiPololu.png +:alt: Wiring the RPI5 to the pdp using the Pololu S13V30F5 +``` + +::: + +:::{tab-item} Raspberry Pi 5 Pololu S13V30F5 Pigtail + +```{image} images/RPiPololuPigtail.png +:alt: Wiring the RPI5 to the pdp using the Pololu S13V30F5 and a usb c pigtail +``` + +::: + +:::: + +Pigtails can be purchased from many sources we recommend [(USB C)](https://ctr-electronics.com/products/usb-type-c-wire-breakout?_pos=19&_sid=bf06b6a6b&_ss=r) [(Micro USB)](https://ctr-electronics.com/products/usb-micro-power-wire-breakout?pr_prod_strat=e5_desc&pr_rec_id=10bf36ce7&pr_rec_pid=7863771070637&pr_ref_pid=7863771103405&pr_seq=uniform) + +## Coprocessor with Passive POE (Pi with SnakeEyes and Limelight) + +1. Plug the [passive POE injector](https://www.revrobotics.com/rev-11-1210/) into the coprocessor and wire it to PDP/PDH (NOT the VRM). +2. Add a breaker to relevant slot in your PDP/PDH +3. Run an ethernet cable from the passive POE injector to your network switch / radio. + +## Off-Robot Wiring + +Plugging your coprocessor into the wall via a power brick will suffice for off robot wiring. + +:::{note} +Please make sure your chosen power supply can provide enough power for your coprocessor. Undervolting (where enough power isn't being supplied) can cause many issues. +::: diff --git a/docs/source/docs/reflectiveAndShape/3D.md b/docs/source/docs/reflectiveAndShape/3D.md new file mode 100644 index 0000000..e3b7180 --- /dev/null +++ b/docs/source/docs/reflectiveAndShape/3D.md @@ -0,0 +1,22 @@ +# 3D Tuning + +In 3D mode, the SolvePNP algorithm is used to compute the position and rotation of the AprilTag or other target relative to the robot. This requires your {ref}`camera to be calibrated ` which can be done through the cameras tab. + +The target model dropdown is used to select the target model used to compute target position. This should match the target your camera will be tracking. + +If solvePNP is working correctly, the target should be displayed as a small rectangle within the "Target Location" minimap. The X/Y/Angle reading will also be displayed in the "Target Info" card. + +```{raw} html + + +``` + +## Contour Simplification (Non-AprilTag) + +3D mode internally computes a polygon that approximates the target contour being tracked. This polygon is used to detect the extreme corners of the target. The contour simplification slider changes how far from the original contour the approximation is allowed to deviate. Note that the approximate polygon is drawn on the output image for tuning. diff --git a/docs/source/docs/reflectiveAndShape/contour-filtering.md b/docs/source/docs/reflectiveAndShape/contour-filtering.md new file mode 100644 index 0000000..75783ac --- /dev/null +++ b/docs/source/docs/reflectiveAndShape/contour-filtering.md @@ -0,0 +1,72 @@ +# Contour Filtering and Grouping + +Contours that make it past thresholding are filtered and grouped so that only likely targets remain. + +## Filtering Options + +### Reflective + +Contours can be filtered by area, width/height ratio, "fullness", and "speckle rejection" percentage. + +Area filtering adjusts the percentage of overall image area that contours are allowed to occupy. The area of valid contours is shown in the "target info" card on the right. + +Ratio adjusts the width to height ratio of allowable contours. For example, a width to height filtering range of \[2, 3\] would allow targets that are 250 x 100 pixels in size through. + +Fullness is a measurement of the ratio between the contour's area and the area of its bounding rectangle. This can be used to reject contours that are for example solid blobs. + +Finally, speckle rejection is an algorithm that can discard contours whose area are below a certain percentage of the average area of all visible contours. This might be useful in rejecting stray lights or image noise. + +```{raw} html + +``` + +### Colored Shape + +The contours tab has new options for specifying the properties of your colored shape. The target shape types are: + +- Circle - No edges +- Triangle - 3 edges +- Quadrilateral - 4 edges +- Polygon - Any number of edges + +```{image} images/triangle.png +:alt: Dropdown to select the colored shape pipeline type. +:width: 600 +``` + +Only the settings used for the current target shape are available. + +- Shape Simplification - This is the only setting available for polygon, triangle, and quadrilateral target shapes. If you are having issues with edges being "noisy" or "unclean", adjust this setting to be higher (>75). This high setting helps prevent imperfections in the edge from being counted as a separate edge. +- Circle Match Distance - How close the centroid of a contour must be to the center of the circle in order for them to be matched. This value is usually pretty small (\<25) as you usually only want to identify circles that are nearly centered in the contour. +- Radius - Percentage of the frame that the radius of the circle represents. +- Max Canny Threshold - This sets the amount of change between pixels needed to be considered an edge. The smaller it is, the more false circles may be detected. Circles with more points along their ring having high contrast values will be returned first. +- Circle Accuracy - This determines how perfect the circle contour must be in order to be considered a circle. Low values (\<40) are required to detect things that aren't perfect circles. + +```{image} images/pumpkin.png +:alt: Dropdown to select the colored shape pipeline type. +:width: 600 +``` + +## Contour Grouping and Sorting + +These options change how contours are grouped together and sorted. Target grouping can pair adjacent contours, such as the targets found in 2019. Target intersection defines where the targets would intersect if you extended them infinitely, for example, to only group targets tipped "towards" each other in 2019. + +Finally, target sort defines how targets are ranked, from "best" to "worst." The available options are: + +- Largest +- Smallest +- Highest (towards the top of the image) +- Lowest +- Rightmost (Best target on the right, worst on left) +- Leftmost +- Centermost + +```{raw} html + +``` diff --git a/docs/source/docs/reflectiveAndShape/images/hsl_top.png b/docs/source/docs/reflectiveAndShape/images/hsl_top.png new file mode 100644 index 0000000..b203b56 Binary files /dev/null and b/docs/source/docs/reflectiveAndShape/images/hsl_top.png differ diff --git a/docs/source/docs/reflectiveAndShape/images/pumpkin.png b/docs/source/docs/reflectiveAndShape/images/pumpkin.png new file mode 100644 index 0000000..f3979b6 Binary files /dev/null and b/docs/source/docs/reflectiveAndShape/images/pumpkin.png differ diff --git a/docs/source/docs/reflectiveAndShape/images/triangle.png b/docs/source/docs/reflectiveAndShape/images/triangle.png new file mode 100644 index 0000000..e54e0da Binary files /dev/null and b/docs/source/docs/reflectiveAndShape/images/triangle.png differ diff --git a/docs/source/docs/reflectiveAndShape/index.md b/docs/source/docs/reflectiveAndShape/index.md new file mode 100644 index 0000000..a9e293f --- /dev/null +++ b/docs/source/docs/reflectiveAndShape/index.md @@ -0,0 +1,10 @@ +# Colored Shape & Reflective + +```{toctree} +:maxdepth: 0 +:titlesonly: true + +thresholding +contour-filtering +3D +``` diff --git a/docs/source/docs/reflectiveAndShape/thresholding.md b/docs/source/docs/reflectiveAndShape/thresholding.md new file mode 100644 index 0000000..93debb8 --- /dev/null +++ b/docs/source/docs/reflectiveAndShape/thresholding.md @@ -0,0 +1,36 @@ +# Thresholding + +For colored shape detection, we want to tune our HSV thresholds such that only the goal color remains after the thresholding. The [HSV color representation](https://en.wikipedia.org/wiki/HSL_and_HSV) is similar to RGB in that it represents colors. However, HSV represents colors with hue, saturation and value components. Hue refers to the color, while saturation and value describe its richness and brightness. + +In PhotonVision, HSV thresholds is available in the "Threshold" tab. + +```{raw} html + +``` + +## Color Picker + +The color picker can be used to quickly adjust HSV values. "Set to average" will set the HSV range to the color of the pixel selected, while "shrink range" and "expand range" will change the HSV threshold to include or exclude the selected pixel, respectively. + +```{raw} html + +``` + +## Tuning Steps + +The following steps were derived from FRC 254's 2016 Championship presentation on computer vision and allows you to accurately tune PhotonVision to track your target. + +In order to properly capture the colors that you want, first turn your exposure low until you have a mostly dark image with the target still showing. A darker image ensures that you don't see things that aren't your target (ex. overhead lights). Be careful not to overexpose your image (you will be able to tell this if a target looks more cyan/white or equivalent instead of green when looking at it through the video feed) since that can give you poor results. + +For HSV tuning, start with Hue, as it is the most important/differentiating factor when it comes to detecting color. You want to make the range for Hue as small as possible in order to get accurate tracking. Feel free to reference the chart below to help. After you have properly tuned Hue, tune for high saturation/color intensity (S), and then brightness (V). Using this method will decrease the likelihood that you need to calibrate on the field. Saturation and Value's upper bounds will often end up needing to be the maximum (255). + +```{image} images/hsl_top.png +:alt: HSV chart +:width: 600 +``` diff --git a/docs/source/docs/settings.md b/docs/source/docs/settings.md new file mode 100644 index 0000000..d35657c --- /dev/null +++ b/docs/source/docs/settings.md @@ -0,0 +1,23 @@ +# Settings + +```{image} assets/settings.png +``` + +## General + +Here, you can view general data on your system, including version, hardware, your platform, and performance statistics. You can also export/import the settings in a .zip file or restart PhotonVision/your coprocessor. + +## Networking + +Here, you can set your team number, switch your IP between DHCP and static, and specify your host name. For more information about on-robot networking, click [here.](https://docs.wpilib.org/en/latest/docs/networking/networking-introduction/networking-basics.html) + +The "team number" field will accept (in addition to a team number) an IP address or hostname. This is useful for testing PhotonVision on the same computer as a simulated robot program; +you can set the team number to "localhost", and PhotonVision will send data to the network tables in the simulated robot. + +:::{note} +Something must be entered into the team number field if using PhotonVision on a robot. Using a team number is recommended (as opposed to an IP address or hostname). +::: + +## LEDs + +If your coprocessor electronics support hardware-controlled LED's and has the proper hardware configuration set up, here you can adjust the brightness of your LEDs. diff --git a/docs/source/docs/simulation/diagrams/SimArchitecture-deprecated.drawio.svg b/docs/source/docs/simulation/diagrams/SimArchitecture-deprecated.drawio.svg new file mode 100644 index 0000000..6eff0a7 --- /dev/null +++ b/docs/source/docs/simulation/diagrams/SimArchitecture-deprecated.drawio.svg @@ -0,0 +1,3 @@ + + +
User's PC
User's PC
SimVisionSystem
SimVisionSystem
Co-Processor
Co-Processor
RoboRIO
RoboRIO
Network Tables
Network Tables
PhotonLib
PhotonLib
User Code
User Code
PhotonVision
PhotonVision
Camera
Camera
Physical Environment
Physical Environ...
SimPhotonCam
SimPhotonCam
Geometry Calculations
Geometry Calcula...
Robot Pose
Robot Pose
Target Pose
Target Pose
System Config
System Con...
Network Tables
Network Tables
PhotonLib
PhotonLib
User Code
User Code
Same for Sim & Real Robot
Same for Sim & Re...
Modeled Physics
Modeled P...
Real Physics
Real Phys...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/source/docs/simulation/diagrams/SimArchitecture.drawio.svg b/docs/source/docs/simulation/diagrams/SimArchitecture.drawio.svg new file mode 100644 index 0000000..69d941b --- /dev/null +++ b/docs/source/docs/simulation/diagrams/SimArchitecture.drawio.svg @@ -0,0 +1,4 @@ + + + +
User's PC
User's PC
VisionSystemSim
VisionSystemSim
Co-Processor
Co-Processor
RoboRIO
RoboRIO
Network Tables
Network Tables
PhotonLib
PhotonLib
User Code
User Code
PhotonVision
PhotonVision
Camera
Camera
Physical Environment
Physical Environ...

Robot Pose

Robot Pose
Network Tables
Network Tables
PhotonLib
PhotonLib
User Code
User Code
Same for Sim & Real Robot
Same for Sim & Re...
Simulated World
Simulated W...
Real World
Real World
PhotonCameraSim
PhotonCameraSim
Frame Generation
Frame Generation
VisionTargetSim
VisionTargetSim
Target Calculations
Target Calculation...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/source/docs/simulation/hardware-in-the-loop-sim.md b/docs/source/docs/simulation/hardware-in-the-loop-sim.md new file mode 100644 index 0000000..d3735f4 --- /dev/null +++ b/docs/source/docs/simulation/hardware-in-the-loop-sim.md @@ -0,0 +1,42 @@ +# Hardware In The Loop Simulation + +Hardware in the loop simulation is using a physical device, such as a supported co-processor running PhotonVision, to enhance simulation capabilities. This is useful for developing and validating code before the camera is attached to a robot, as well as reducing the work required to use WPILib simulation with PhotonVision. + +Before continuing, ensure PhotonVision is installed on your device. Instructions can be found {ref}`here ` for all devices. + +Your coprocessor and computer running simulation will have to be connected to the same network, like a home router. Connecting the coprocessor directly to the computer will not work. + +To simulate with hardware in the loop, a one-line change is required. From the PhotonVision UI, go to the sidebar and select the Settings option. Within the Networking settings, find "Team Number/NetworkTables Server Address". + +During normal robot operation, a team's number would be entered into this field so that the PhotonVision coprocessor connects to the roboRIO as a NT client. Instead, enter the IP address of your computer running the simulation here. + +:::{note} +To find the IP address of your Windows computer, open command prompt and run `ipconfig`. + +```console +C:/Users/you>ipconfig + +Windows IP Configuration + +Ethernet adapter Ethernet: + + Connection-specific DNS Suffix . : home + Link-local IPv6 Address . . . . . : fe80::b41d:e861:ef01:9dba%10 + IPv4 Address. . . . . . . . . . . : 192.168.254.13 + Subnet Mask . . . . . . . . . . . : 255.255.255.0 + Default Gateway . . . . . . . . . : 192.168.254.254 +``` + +::: + +```{image} images/coproc-client-to-desktop-sim.png + +``` + +No code changes are required, PhotonLib should function similarly to normal operation. + +Now launch simulation, and you should be able to see the PhotonVision table on your simulation's NetworkTables dashboard. + +```{image} images/hardware-in-the-loop-sim.png + +``` diff --git a/docs/source/docs/simulation/images/SimArchitecture.svg b/docs/source/docs/simulation/images/SimArchitecture.svg new file mode 100644 index 0000000..6eff0a7 --- /dev/null +++ b/docs/source/docs/simulation/images/SimArchitecture.svg @@ -0,0 +1,3 @@ + + +
User's PC
User's PC
SimVisionSystem
SimVisionSystem
Co-Processor
Co-Processor
RoboRIO
RoboRIO
Network Tables
Network Tables
PhotonLib
PhotonLib
User Code
User Code
PhotonVision
PhotonVision
Camera
Camera
Physical Environment
Physical Environ...
SimPhotonCam
SimPhotonCam
Geometry Calculations
Geometry Calcula...
Robot Pose
Robot Pose
Target Pose
Target Pose
System Config
System Con...
Network Tables
Network Tables
PhotonLib
PhotonLib
User Code
User Code
Same for Sim & Real Robot
Same for Sim & Re...
Modeled Physics
Modeled P...
Real Physics
Real Phys...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/source/docs/simulation/images/SimExampleField.png b/docs/source/docs/simulation/images/SimExampleField.png new file mode 100644 index 0000000..833f1b8 Binary files /dev/null and b/docs/source/docs/simulation/images/SimExampleField.png differ diff --git a/docs/source/docs/simulation/images/SimExampleFrame.png b/docs/source/docs/simulation/images/SimExampleFrame.png new file mode 100644 index 0000000..6fb76a1 Binary files /dev/null and b/docs/source/docs/simulation/images/SimExampleFrame.png differ diff --git a/docs/source/docs/simulation/images/coproc-client-to-desktop-sim.png b/docs/source/docs/simulation/images/coproc-client-to-desktop-sim.png new file mode 100644 index 0000000..6d619b7 Binary files /dev/null and b/docs/source/docs/simulation/images/coproc-client-to-desktop-sim.png differ diff --git a/docs/source/docs/simulation/images/hardware-in-the-loop-sim.png b/docs/source/docs/simulation/images/hardware-in-the-loop-sim.png new file mode 100644 index 0000000..46af75f Binary files /dev/null and b/docs/source/docs/simulation/images/hardware-in-the-loop-sim.png differ diff --git a/docs/source/docs/simulation/index.md b/docs/source/docs/simulation/index.md new file mode 100644 index 0000000..a4f007c --- /dev/null +++ b/docs/source/docs/simulation/index.md @@ -0,0 +1,11 @@ +# Simulation + +```{toctree} +:maxdepth: 0 +:titlesonly: true + +simulation-java +simulation-cpp +simulation-python +hardware-in-the-loop-sim +``` diff --git a/docs/source/docs/simulation/simulation-cpp.md b/docs/source/docs/simulation/simulation-cpp.md new file mode 100644 index 0000000..ccb28de --- /dev/null +++ b/docs/source/docs/simulation/simulation-cpp.md @@ -0,0 +1,5 @@ +# Simulation Support in PhotonLib in C++ + +## What Is Supported? + +Nothing yet. diff --git a/docs/source/docs/simulation/simulation-java.md b/docs/source/docs/simulation/simulation-java.md new file mode 100644 index 0000000..3e1a612 --- /dev/null +++ b/docs/source/docs/simulation/simulation-java.md @@ -0,0 +1,251 @@ +# Simulation Support in PhotonLib in Java + +## What Is Simulated? + +Simulation is a powerful tool for validating robot code without access to a physical robot. Read more about [simulation in WPILib](https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/introduction.html). + +In Java, PhotonLib can simulate cameras on the field and generate target data approximating what would be seen in reality. This simulation attempts to include the following: + +- Camera Properties + - Field of Vision + - Lens distortion + - Image noise + - Framerate + - Latency +- Target Data + - Detected / minimum-area-rectangle corners + - Center yaw/pitch + - Contour image area percentage + - Fiducial ID + - Fiducial ambiguity + - Fiducial solvePNP transform estimation +- Camera Raw/Processed Streams (grayscale) + +:::{note} +Simulation does NOT include the following: + +- Full physical camera/world simulation (targets are automatically thresholded) +- Image Thresholding Process (camera gain, brightness, etc) +- Pipeline switching +- Snapshots + ::: + +This scope was chosen to balance fidelity of the simulation with the ease of setup, in a way that would best benefit most teams. + +```{image} diagrams/SimArchitecture.drawio.svg +:alt: A diagram comparing the architecture of a real PhotonVision process to a simulated +: one. +``` + +## Drivetrain Simulation Prerequisite + +A prerequisite for simulating vision frames is knowing where the camera is on the field-- to utilize PhotonVision simulation, you'll need to supply the simulated robot pose periodically. This requires drivetrain simulation for your robot project if you want to generate camera frames as your robot moves around the field. + +References for using PhotonVision simulation with drivetrain simulation can be found in the [PhotonLib Java Examples](https://github.com/PhotonVision/photonvision/blob/2a6fa1b6ac81f239c59d724da5339f608897c510/photonlib-java-examples/README.md) for both a differential drivetrain and a swerve drive. + +:::{important} +The simulated drivetrain pose must be separate from the drivetrain estimated pose if a pose estimator is utilized. +::: + +## Vision System Simulation + +A `VisionSystemSim` represents the simulated world for one or more cameras, and contains the vision targets they can see. It is constructed with a unique label: + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // A vision system sim labelled as "main" in NetworkTables + VisionSystemSim visionSim = new VisionSystemSim("main"); +``` + +PhotonLib will use this label to put a `Field2d` widget on NetworkTables at `/VisionSystemSim-[label]/Sim Field`. This label does not need to match any camera name or pipeline name in PhotonVision. + +Vision targets require a `TargetModel`, which describes the shape of the target. For AprilTags, PhotonLib provides `TargetModel.kAprilTag16h5` for the tags used in 2023, and `TargetModel.kAprilTag36h11` for the tags used starting in 2024. For other target shapes, convenience constructors exist for spheres, cuboids, and planar rectangles. For example, a planar rectangle can be created with: + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // A 0.5 x 0.25 meter rectangular target + TargetModel targetModel = new TargetModel(0.5, 0.25); +``` + +These `TargetModel` are paired with a target pose to create a `VisionTargetSim`. A `VisionTargetSim` is added to the `VisionSystemSim` to become visible to all of its cameras. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // The pose of where the target is on the field. + // Its rotation determines where "forward" or the target x-axis points. + // Let's say this target is flat against the far wall center, facing the blue driver stations. + Pose3d targetPose = new Pose3d(16, 4, 2, new Rotation3d(0, 0, Math.PI)); + // The given target model at the given pose + VisionTargetSim visionTarget = new VisionTargetSim(targetPose, targetModel); + + // Add this vision target to the vision system simulation to make it visible + visionSim.addVisionTargets(visionTarget); +``` + +:::{note} +The pose of a `VisionTargetSim` object can be updated to simulate moving targets. Note, however, that this will break latency simulation for that target. +::: + +For convenience, an `AprilTagFieldLayout` can also be added to automatically create a target for each of its AprilTags. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // The layout of AprilTags which we want to add to the vision system + AprilTagFieldLayout tagLayout = AprilTagFieldLayout.loadFromResource(AprilTagFields.k2024Crescendo.m_resourceFile); + + visionSim.addAprilTags(tagLayout); +``` + +:::{note} +The poses of the AprilTags from this layout depend on its current alliance origin (e.g. blue or red). If this origin is changed later, the targets will have to be cleared from the `VisionSystemSim` and re-added. +::: + +## Camera Simulation + +Now that we have a simulation world with vision targets, we can add simulated cameras to view it. + +Before adding a simulated camera, we need to define its properties. This is done with the `SimCameraProperties` class: + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // The simulated camera properties + SimCameraProperties cameraProp = new SimCameraProperties(); +``` + +By default, this will create a 960 x 720 resolution camera with a 90 degree diagonal FOV(field-of-view) and no noise, distortion, or latency. If we want to change these properties, we can do so: + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // A 640 x 480 camera with a 100 degree diagonal FOV. + cameraProp.setCalibration(640, 480, Rotation2d.fromDegrees(100)); + // Approximate detection noise with average and standard deviation error in pixels. + cameraProp.setCalibError(0.25, 0.08); + // Set the camera image capture framerate (Note: this is limited by robot loop rate). + cameraProp.setFPS(20); + // The average and standard deviation in milliseconds of image data latency. + cameraProp.setAvgLatencyMs(35); + cameraProp.setLatencyStdDevMs(5); +``` + +These properties are used in a `PhotonCameraSim`, which handles generating captured frames of the field from the simulated camera's perspective, and calculating the target data which is sent to the `PhotonCamera` being simulated. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // The PhotonCamera used in the real robot code. + PhotonCamera camera = new PhotonCamera("cameraName"); + + // The simulation of this camera. Its values used in real robot code will be updated. + PhotonCameraSim cameraSim = new PhotonCameraSim(camera, cameraProp); +``` + +The `PhotonCameraSim` can now be added to the `VisionSystemSim`. We have to define a robot-to-camera transform, which describes where the camera is relative to the robot pose (this can be measured in CAD or by hand). + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // Our camera is mounted 0.1 meters forward and 0.5 meters up from the robot pose, + // (Robot pose is considered the center of rotation at the floor level, or Z = 0) + Translation3d robotToCameraTrl = new Translation3d(0.1, 0, 0.5); + // and pitched 15 degrees up. + Rotation3d robotToCameraRot = new Rotation3d(0, Math.toRadians(-15), 0); + Transform3d robotToCamera = new Transform3d(robotToCameraTrl, robotToCameraRot); + + // Add this camera to the vision system simulation with the given robot-to-camera transform. + visionSim.addCamera(cameraSim, robotToCamera); +``` + +:::{important} +You may add multiple cameras to one `VisionSystemSim`, but not one camera to multiple `VisionSystemSim`. All targets in the `VisionSystemSim` will be visible to all its cameras. +::: + +If the camera is mounted on a mobile mechanism (like a turret) this transform can be updated in a periodic loop. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // The turret the camera is mounted on is rotated 5 degrees + Rotation3d turretRotation = new Rotation3d(0, 0, Math.toRadians(5)); + robotToCamera = new Transform3d( + robotToCameraTrl.rotateBy(turretRotation), + robotToCameraRot.rotateBy(turretRotation)); + visionSim.adjustCamera(cameraSim, robotToCamera); +``` + +## Updating The Simulation World + +To update the `VisionSystemSim`, we simply have to pass in the simulated robot pose periodically (in `simulationPeriodic()`). + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // Update with the simulated drivetrain pose. This should be called every loop in simulation. + visionSim.update(robotPoseMeters); +``` + +Targets and cameras can be added and removed, and camera properties can be changed at any time. + +## Visualizing Results + +Each `VisionSystemSim` has its own built-in `Field2d` for displaying object poses in the simulation world such as the robot, simulated cameras, and actual/measured target poses. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // Get the built-in Field2d used by this VisionSystemSim + visionSim.getDebugField(); +``` + +:::{figure} images/SimExampleField.png +_A_ `VisionSystemSim`_'s internal_ `Field2d` _customized with target images and colors_ +::: + +A `PhotonCameraSim` can also draw and publish generated camera frames to a MJPEG stream similar to an actual PhotonVision process. + +```{eval-rst} +.. tab-set-code:: + + .. code-block:: Java + + // Enable the raw and processed streams. These are enabled by default. + cameraSim.enableRawStream(true); + cameraSim.enableProcessedStream(true); + + // Enable drawing a wireframe visualization of the field to the camera streams. + // This is extremely resource-intensive and is disabled by default. + cameraSim.enableDrawWireframe(true); +``` + +These streams follow the port order mentioned in {ref}`docs/quick-start/networking:Camera Stream Ports`. For example, a single simulated camera will have its raw stream at `localhost:1181` and processed stream at `localhost:1182`, which can also be found in the CameraServer tab of Shuffleboard like a normal camera stream. + +:::{figure} images/SimExampleFrame.png +_A frame from the processed stream of a simulated camera viewing some 2023 AprilTags with the field wireframe enabled_ +::: diff --git a/docs/source/docs/simulation/simulation-python.md b/docs/source/docs/simulation/simulation-python.md new file mode 100644 index 0000000..eb871c6 --- /dev/null +++ b/docs/source/docs/simulation/simulation-python.md @@ -0,0 +1,5 @@ +# Simulation Support in PhotonLib in Python + +## What Is Supported? + +Nothing Yet diff --git a/docs/source/docs/troubleshooting/camera-troubleshooting.md b/docs/source/docs/troubleshooting/camera-troubleshooting.md new file mode 100644 index 0000000..a55ff29 --- /dev/null +++ b/docs/source/docs/troubleshooting/camera-troubleshooting.md @@ -0,0 +1,108 @@ +# Camera Troubleshooting + +## Pi Cameras + +If you haven't yet, please refer to {ref}`the Pi CSI Camera Configuration page ` for information on updating {code}`config.txt` for your use case. If you've tried that, and things still aren't working, restart PhotonVision using the restart button in the settings tab, and press tilde (\`) in the web UI once connection is restored. This should show the most recent boot log. + +| | Expected output | Bad | +| ------------------------------- | ----------------------------------------------------- | ---------------------------------- | +| LibCamera driver initialization | Successfully loaded libpicam shared object | Failed to load native libraries! | +| Camera detected | Adding local video device - "unicam" at "/dev/video0" | No output from VisionSourceManager | +| VisionSource created | Adding 1 configs to VMM. | No output from VisionSourceManager | + +If the driver isn't loaded, you may be using a non-official Pi image, or an image not new enough. Try updating to the most recent image available (one released for 2023) -- if that doesn't resolve the problem, {ref}`contact us` with your settings ZIP file and Pi version/camera version/config.txt file used. + +If the camera is not detected, the most likely cause is either a config.txt file incorrectly set-up, or a ribbon cable attached backwards. Review the {ref}`picam configuration page `, and verify the ribbon cable is properly oriented at both ends, and that it is \_fully\_ inserted into the FFC connector. Then, {ref}`contact us` with your settings ZIP file and Pi version/camera version/config.txt file used. + +## USB cameras + +USB cameras supported by CSCore require no libcamera driver initialization to work -- however, similar troubleshooting steps apply. Restart PhotonVision using the restart button in the settings tab, and press tilde on your keyboard (\`) when you're in the web UI once connection is restored. We expect to see the following output: + +| | Expected output | Bad | +| -------------------- | ----------------------------------------------------- | ---------------------------------- | +| Camera detected | Adding local video device - "foobar" at "/dev/foobar" | No output from VisionSourceManager | +| VisionSource created | Adding 1 configs to VMM. | No output from VisionSourceManager | + +## Determining detected cameras in Video4Linux (v4l2) + +On Linux devices (including Raspberry Pi), PhotonVision uses WPILib's CSCore to interact with video devices, which internally uses Video4Linux (v4l2). CSCore, and therefore Photon, requires that cameras attached have good v4l drivers for proper functionality. These should be built into the Linux kernel, and do not need to be installed manually. Valid picamera setup (from /boot/config.txt) can also be determined using these steps. The list-devices command will show all valid video devices detected, and list-formats the list of "video modes" each camera can be in. + +- For picams: edit the config.txt file as described in the {ref}`picam configuration page ` +- SSH into your Pi: {code}`ssh pi@photonvision.local` and enter the username "pi" & password "raspberry" +- run {code}`v4l2-ctl --list-devices` and {code}`v4l2-ctl --list-formats` + +We expect an output similar to the following. For picameras, note the "unicam" entry with path {code}`platform:3f801000.csi` (if we don't see this, that's bad), and a huge list of valid video formats. USB cameras should show up similarly in the output of these commands. + +```{eval-rst} +.. tab-set:: + .. tab-item:: Working + + .. code-block:: + + pi@photonvision:~ $ v4l2-ctl --list-devices + unicam (platform:3f801000.csi): + /dev/video0 + /dev/media3 + + bcm2835-codec-decode (platform:bcm2835-codec): + /dev/video10 + /dev/video11 + /dev/video12 + /dev/video18 + /dev/video31 + /dev/media2 + + bcm2835-isp (platform:bcm2835-isp): + /dev/video13 + /dev/video14 + /dev/video15 + /dev/video16 + /dev/video20 + /dev/video21 + /dev/video22 + /dev/video23 + /dev/media0 + /dev/media1 + + pi@photonvision:~ $ v4l2-ctl --list-formats + ioctl: VIDIOC_ENUM_FMT + Type: Video Capture + + [0]: 'YUYV' (YUYV 4:2:2) + [1]: 'UYVY' (UYVY 4:2:2) + [2]: 'YVYU' (YVYU 4:2:2) + [3]: 'VYUY' (VYUY 4:2:2) + + [42]: 'Y12P' (12-bit Greyscale (MIPI Packed)) + [43]: 'Y12 ' (12-bit Greyscale) + [44]: 'Y14P' (14-bit Greyscale (MIPI Packed)) + [45]: 'Y14 ' (14-bit Greyscale) + + .. tab-item:: Not Working + + .. code-block:: + + pi@photonvision:~ $ v4l2-ctl --list-devices + bcm2835-codec-decode (platform:bcm2835-codec): + /dev/video10 + /dev/video11 + /dev/video12 + /dev/video18 + /dev/video31 + /dev/media3 + bcm2835-isp (platform:bcm2835-isp): + /dev/video13 + /dev/video14 + /dev/video15 + /dev/video16 + /dev/video20 + /dev/video21 + /dev/video22 + /dev/video23 + /dev/media0 + /dev/media1 + rpivid (platform:rpivid): + /dev/video19 + /dev/media2 + Cannot open device /dev/video0, exiting. +``` diff --git a/docs/source/docs/troubleshooting/common-errors.md b/docs/source/docs/troubleshooting/common-errors.md new file mode 100644 index 0000000..217d4a6 --- /dev/null +++ b/docs/source/docs/troubleshooting/common-errors.md @@ -0,0 +1,63 @@ +# Common Issues / Questions + +This page will grow as needed in order to cover commonly seen issues by teams. If this page doesn't help you and you need further assistance, feel free to {ref}`Contact Us`. + +## Known Issues + +All known issues can be found on our [GitHub page](https://github.com/PhotonVision/photonvision/issues). + +### PS3Eye + +Due to an issue with Linux kernels, the drivers for the PS3Eye are no longer supported. If you would still like to use the PS3Eye, you can downgrade your kernel with the following command: `sudo CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt rpi-update 866751bfd023e72bd96a8225cf567e03c334ecc4`. Note: You must be connected to the internet to run the command. + +### LED Control + +The logic for controlling LED mode when `multiple cameras are connected` is not fully fleshed out. In its current state, LED control is only enabled when a Pi Camera Module is not in driver mode—meaning a USB camera on its own is unable to control the LEDs. + +For now, if you are using multiple cameras, it is recommended that teams set the value of the NetworkTables entry {code}`photonvision/ledMode` from the robot code to control LED state. + +## Commonly Seen Issues + +### Networking Issues + +Please refer to our comprehensive {ref}`networking troubleshooting tips ` for debugging suggestions and possible causes. + +### Camera won't show up + +Try these steps to {ref}`troubleshoot your camera connection `. + +If you are using a USB camera, it is possible your USB Camera isn't supported by CSCore and therefore won't work with PhotonVision. + +### Camera is consistently returning incorrect values when in 3D mode + +Read the tips on the {ref}`camera calibration page`, follow the advice there, and redo the calibration. + +### Not getting data from PhotonLib + +1. Ensure your coprocessor version and PhotonLib version match. This can be checked by the settings tab and examining the .json itself (respectively). +2. Ensure that you have your team number set properly. +3. Use Glass to verify that PhotonVision has connected to the NetworkTables server served by your robot. With Glass connected in client mode to your RoboRIO, we expect to see "photonvision" listed under the Clients tab of the NetworkTables Info pane. + +```{image} images/glass-connections.png +:alt: Using Glass to check NT connections +:width: 600 +``` + +4. When creating a `PhotonCamera` in code, does the `cameraName` provided match the name in the upper-right card of the web interface? Glass can be used to verify the RoboRIO is receiving NetworkTables data by inspecting the `photonvision` subtable for your camera nickname. + +```{image} images/camera-subtable.png +:alt: Using Glass to check camera publishing +:width: 600 +``` + +### Unable to download PhotonLib + +Ensure all of your network firewalls are disabled and you aren't on a school-network. + +### PhotonVision prompts for login on startup + +This is normal. You don't need to connect a display to your Raspberry Pi to use PhotonVision, just navigate to the relevant webpage (ex. `photonvision.local:5800`) in order to see the dashboard. + +### Raspberry Pi enters into boot looping state when using PhotonVision + +This is most commonly seen when your Pi doesn't have adequate power / is being undervolted. Ensure that your power supply is functioning properly. diff --git a/docs/source/docs/troubleshooting/images/camera-subtable.png b/docs/source/docs/troubleshooting/images/camera-subtable.png new file mode 100644 index 0000000..6014f96 Binary files /dev/null and b/docs/source/docs/troubleshooting/images/camera-subtable.png differ diff --git a/docs/source/docs/troubleshooting/images/glass-connections.png b/docs/source/docs/troubleshooting/images/glass-connections.png new file mode 100644 index 0000000..2cccf1f Binary files /dev/null and b/docs/source/docs/troubleshooting/images/glass-connections.png differ diff --git a/docs/source/docs/troubleshooting/index.md b/docs/source/docs/troubleshooting/index.md new file mode 100644 index 0000000..0b8f838 --- /dev/null +++ b/docs/source/docs/troubleshooting/index.md @@ -0,0 +1,10 @@ +# Troubleshooting + +```{toctree} +:maxdepth: 1 + +common-errors +logging +camera-troubleshooting +networking-troubleshooting +``` diff --git a/docs/source/docs/troubleshooting/logging.md b/docs/source/docs/troubleshooting/logging.md new file mode 100644 index 0000000..ddf275a --- /dev/null +++ b/docs/source/docs/troubleshooting/logging.md @@ -0,0 +1,22 @@ +# Logging + +:::{note} +Logging is very helpful when trying to debug issues within PhotonVision, as it allows us to see what is happening within the program after it is ran. Whenever reporting an issue to PhotonVision, we request that you include logs whenever possible. +::: + +In addition to storing logs in timestamped files in the config directory, PhotonVision streams logs to the web dashboard. These logs can be viewed later by pressing the \` key. In this view, logs can be filtered by level or downloaded. + +:::{note} +When the program first starts, it sends logs from startup to the client that first connects. This does not happen on subsequent connections. +::: + +:::{note} +Logs are stored inside the {code}`photonvision_config/logs` directory. Exporting the settings ZIP will also download all old logs for further review. +::: + +```{raw} html + +``` diff --git a/docs/source/docs/troubleshooting/networking-troubleshooting.md b/docs/source/docs/troubleshooting/networking-troubleshooting.md new file mode 100644 index 0000000..2600015 --- /dev/null +++ b/docs/source/docs/troubleshooting/networking-troubleshooting.md @@ -0,0 +1,47 @@ +# Networking Troubleshooting + +Before reading further, ensure that you follow all the recommendations {ref}`in our networking section `. You should follow these guidelines in order for PhotonVision to work properly; other networking setups are not officially supported. + +## Checklist + +A few issues make up the majority of support requests. Run through this checklist quickly to catch some common mistakes. + +- Is your camera connected to the robot's radio through a {ref}`network switch `? + - Ethernet straight from a laptop to a coprocessor will not work (most likely), due to the unreliability of link-local connections. + - Even if there's a switch between your laptop and coprocessor, you'll still want a radio or router in the loop somehow. + - The FRC radio is the _only_ router we will officially support due to the innumerable variations between routers. +- (Raspberry Pi, Orange Pi & Limelight only) have you flashed the correct image, and is it up to date? + - Limelights 2/2+ should be flashed using the Limelight 2 image (eg, `photonvision-v2024.2.8-linuxarm64_limelight2.img.xz`). + - Limelights 3 should be flashed using the Limelight 3 image (eg, `photonvision-v2024.2.8-linuxarm64_limelight3.img.xz`). + - Raspberry Pi devices (including Pi 3, Pi 4, CM3 and CM4) should be flashed using the Raspberry Pi image (eg, `photonvision-v2024.2.8-linuxarm64_RaspberryPi.img.xz`). + - Orange Pi 5 devices should be flashed using the Orange Pi 5 image (eg, `photonvision-v2024.2.8-linuxarm64_orangepi5.img.xz`). + - Orange Pi 5+ devices should be flashed using the Orange Pi 5+ image (eg, `photonvision-v2024.2.8-linuxarm64_orangepi5plus.img.xz`). +- Is your robot code using a **2024** version of WPILib, and is your coprocessor using the most up to date **2024** release? + - 2022, 2023 and 2024 versions of either cannot be mix-and-matched! + - Your PhotonVision version can be checked on the {ref}`settings tab`. +- Is your team number correctly set on the {ref}`settings tab`? + +### photonvision.local Not Found + +Use [Angry IP Scanner](https://angryip.org/) and look for an IP that has port 5800 open. Then go to your web browser and do \:5800. + +Alternatively, you can plug your coprocessor into a display, plug in a keyboard, and run `hostname -I` in the terminal. This should give you the IP Address of your coprocessor, then go to your web browser and do \:5800. + +If nothing shows up, ensure your coprocessor has power, and you are following all of our networking recommendations, feel free to {ref}`contact us ` and we will help you. + +### Can't Connect To Robot + +Please check that: +1\. You don't have the NetworkTables Server on (toggleable in the settings tab). Turn this off when doing work on a robot. +2\. You have your team number set properly in the settings tab. +3\. Your camera name in the `PhotonCamera` constructor matches the name in the UI. +4\. You are using the 2024 version of WPILib and RoboRIO image. +5\. Your robot is on. + +If all of the above are met and you still have issues, feel free to {ref}`contact us ` and provide the following information: + +- The WPILib version used by your robot code +- PhotonLib vendor dependency version +- PhotonVision version (from the UI) +- Your settings exported from your coprocessor (if you're able to access it) +- How your RoboRIO/coprocessor are networked together diff --git a/docs/source/index.md b/docs/source/index.md new file mode 100644 index 0000000..2db9cc0 --- /dev/null +++ b/docs/source/index.md @@ -0,0 +1,141 @@ +```{image} assets/PhotonVision-Header-onWhite.png +:alt: PhotonVision +``` + +Welcome to the official documentation of PhotonVision! PhotonVision is the free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition. PhotonVision is designed to get vision working on your robot _quickly_, without the significant cost of other similar solutions. PhotonVision supports a variety of COTS hardware, including the Raspberry Pi 3, 4, and 5, the [SnakeEyes Pi hat](https://www.playingwithfusion.com/productview.php?pdid=133), and the Orange Pi 5. + +# Content + +```{eval-rst} +.. grid:: 2 + + .. grid-item-card:: Quick Start + :link: docs/quick-start/index + :link-type: doc + + Quick start to using Photonvision. + + .. grid-item-card:: Advanced Installation + :link: docs/advanced-installation/index + :link-type: doc + + Get started with installing PhotonVision on non-supported hardware. + +``` + +```{eval-rst} +.. grid:: 2 + + .. grid-item-card:: Programming Reference and PhotonLib + :link: docs/programming/index + :link-type: doc + + Learn more about PhotonLib, our vendor dependency which makes it easier for teams to retrieve vision data, make various calculations, and more. + + .. grid-item-card:: Integration + :link: docs/integration/index + :link-type: doc + + Pick how to use vision processing results to control a physical robot. + +``` + +```{eval-rst} +.. grid:: 2 + + .. grid-item-card:: Code Examples + :link: docs/examples/index + :link-type: doc + + View various step by step guides on how to use data from PhotonVision in your code, along with game-specific examples. + + .. grid-item-card:: Hardware + :link: docs/hardware/index + :link-type: doc + + Select appropriate hardware for high-quality and easy vision target detection. +``` + +```{eval-rst} +.. grid:: 2 + + .. grid-item-card:: Contributing + :link: docs/contributing/index + :link-type: doc + + Interested in helping with PhotonVision? Learn more about how to contribute to our main code base, documentation, and more. +``` + +# Source Code + +The source code for all PhotonVision projects is available through our [GitHub organization](https://github.com/PhotonVision). + +- [PhotonVision](https://github.com/PhotonVision/photonvision) +- [PhotonVision ReadTheDocs](https://github.com/PhotonVision/photonvision-docs/) + +# Contact Us + +To report a bug or submit a feature request in PhotonVision, please [submit an issue on the PhotonVision GitHub](https://github.com/PhotonVision/photonvision) or [contact the developers on Discord](https://discord.com/invite/KS76FrX). + +If you find a problem in this documentation, please submit an issue on the [PhotonVision Documentation GitHub](https://github.com/PhotonVision/photonvision-docs). + +# License + +PhotonVision is licensed under the [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). + +```{toctree} +:caption: Getting Started +:hidden: true +:maxdepth: 0 + +docs/description +docs/quick-start/index +docs/hardware/index +docs/advanced-installation/index +docs/settings +``` + +```{toctree} +:caption: Pipeline Tuning and Calibration +:hidden: true +:maxdepth: 0 + +docs/pipelines/index +docs/apriltag-pipelines/index +docs/reflectiveAndShape/index +docs/objectDetection/index +docs/calibration/calibration +``` + +```{toctree} +:caption: Programming Reference +:hidden: true +:maxdepth: 1 + +docs/programming/photonlib/index +docs/simulation/index +docs/integration/index +docs/examples/index +``` + +```{toctree} +:caption: Additional Resources +:hidden: true +:maxdepth: 1 + +docs/troubleshooting/index +docs/additional-resources/best-practices +docs/additional-resources/config +docs/additional-resources/nt-api +docs/contributing/index +``` + +```{toctree} +:caption: API Documentation +:hidden: true +:maxdepth: 1 + + Java + + C++ +``` diff --git a/docs/source/make.bat b/docs/source/make.bat new file mode 100644 index 0000000..9534b01 --- /dev/null +++ b/docs/source/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1f15643 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,9 @@ +# The --add-exports flags work around a bug with spotless and JDK 17 +# https://github.com/diffplug/spotless/issues/834 +org.gradle.jvmargs= \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +org.ysb33r.gradle.doxygen.download.url=https://frcmaven.wpi.edu/artifactory/generic-release-mirror/doxygen diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..34bd9ce --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=permwrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=permwrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/photon-client/.eslintrc.json b/photon-client/.eslintrc.json new file mode 100644 index 0000000..ca944a2 --- /dev/null +++ b/photon-client/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "root": true, + "extends": [ + "plugin:vue/vue3-recommended", + "eslint:recommended", + "@vue/eslint-config-typescript", + "@vue/eslint-config-prettier/skip-formatting" + ], + "rules": { + "quotes": ["error", "double"], + "comma-dangle": ["error", "never"], + "comma-spacing": ["error", { "before": false, "after": true }], + "semi": ["error", "always"], + "eol-last": "error", + "object-curly-spacing": ["error", "always"], + "quote-props": ["error", "as-needed"], + "no-case-declarations": "off", + "vue/require-default-prop": "off", + "vue/v-on-event-hyphenation": "off" + } +} diff --git a/photon-client/.gitignore b/photon-client/.gitignore new file mode 100644 index 0000000..fe2c4f9 --- /dev/null +++ b/photon-client/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +components.d.ts diff --git a/photon-client/.prettierignore b/photon-client/.prettierignore new file mode 100644 index 0000000..13fa349 --- /dev/null +++ b/photon-client/.prettierignore @@ -0,0 +1 @@ +src/assets/fonts/PromptRegular.ts diff --git a/photon-client/.prettierrc.json b/photon-client/.prettierrc.json new file mode 100644 index 0000000..544983f --- /dev/null +++ b/photon-client/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": true, + "tabWidth": 2, + "singleQuote": false, + "printWidth": 120, + "trailingComma": "none" +} diff --git a/photon-client/env.d.ts b/photon-client/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/photon-client/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/photon-client/index.html b/photon-client/index.html new file mode 100644 index 0000000..533f161 --- /dev/null +++ b/photon-client/index.html @@ -0,0 +1,13 @@ + + + + + + + Photon Client + + +
+ + + diff --git a/photon-client/package-lock.json b/photon-client/package-lock.json new file mode 100644 index 0000000..53c926e --- /dev/null +++ b/photon-client/package-lock.json @@ -0,0 +1,5523 @@ +{ + "name": "photonclient", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "photonclient", + "version": "0.0.0", + "dependencies": { + "@fontsource/prompt": "^5.0.9", + "@mdi/font": "^7.4.47", + "@msgpack/msgpack": "^3.0.0-beta2", + "axios": "^1.6.3", + "jspdf": "^2.5.1", + "pinia": "^2.1.4", + "three": "^0.160.0", + "vue": "^2.7.14", + "vue-router": "^3.6.5", + "vue-virtual-scroll-list": "^2.3.5", + "vuetify": "^2.7.1" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.3.2", + "@types/node": "^18.19.45", + "@types/three": "^0.160.0", + "@vitejs/plugin-vue2": "^2.3.1", + "@vue/eslint-config-prettier": "^9.0.0", + "@vue/eslint-config-typescript": "^12.0.0", + "@vue/tsconfig": "^0.5.1", + "deepmerge": "^4.3.1", + "eslint": "^8.56.0", + "eslint-plugin-vue": "^9.19.2", + "npm-run-all": "^4.1.5", + "prettier": "3.2.2", + "sass": "~1.32", + "sass-loader": "^13.3.2", + "terser": "^5.14.2", + "typescript": "^5.3.3", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.4.2" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", + "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", + "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fontsource/prompt": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@fontsource/prompt/-/prompt-5.0.9.tgz", + "integrity": "sha512-fbqRSdiqANQ2NiIWz5Fpj8xQ1exN9LmhDAaE6lU52InDYPABM2JQF0WObhv8tkwE/WZ4EzNZAGNJYSo5XUqUYA==" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mdi/font": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", + "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==" + }, + "node_modules/@msgpack/msgpack": { + "version": "3.0.0-beta2", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz", + "integrity": "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.0.tgz", + "integrity": "sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", + "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz", + "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz", + "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz", + "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz", + "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz", + "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz", + "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz", + "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz", + "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz", + "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz", + "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz", + "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz", + "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz", + "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz", + "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz", + "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz", + "integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-FlsN0p4FhuYRjIxpbdXovvHQhtlG05O1GG/RNWvdAxTboR438IOTwmrY/vLA+Xfgg06BTkP045M3vpFwTMv1dg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.19.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.45.tgz", + "integrity": "sha512-VZxPKNNhjKmaC1SUYowuXSRSMGyQGmQjvvA1xE4QZ0xce2kLtEhPDS+kqpCPBZYgqblCLQ2DAjSzmgCM5auvhA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "optional": true + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@types/stats.js": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", + "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", + "dev": true + }, + "node_modules/@types/three": { + "version": "0.160.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.160.0.tgz", + "integrity": "sha512-jWlbUBovicUKaOYxzgkLlhkiEQJkhCVvg4W2IYD2trqD2om3VK4DGLpHH5zQHNr7RweZK/5re/4IVhbhvxbV9w==", + "dev": true, + "dependencies": { + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.10", + "meshoptimizer": "~0.18.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.10.tgz", + "integrity": "sha512-n3u5sqXQJhf1CS68mw3Wf16FQ4cRPNBBwdYLFzq3UddiADOim1Pn3Y6PBdDilz1vOJF3ybLxJ8ZEDlLIzrOQZg==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", + "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/type-utils": "6.17.0", + "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", + "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", + "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", + "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/utils": "6.17.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", + "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", + "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", + "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", + "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.17.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue2/-/plugin-vue2-2.3.1.tgz", + "integrity": "sha512-/ksaaz2SRLN11JQhLdEUhDzOn909WEk99q9t9w+N12GjQCljzv7GyvAbD/p20aBUjHkvpGOoQ+FCOkG+mjDF4A==", + "dev": true, + "engines": { + "node": "^14.18.0 || >= 16.0.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0", + "vue": "^2.7.0-0" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", + "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", + "dependencies": { + "@babel/parser": "^7.23.5", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + }, + "optionalDependencies": { + "prettier": "^1.18.2 || ^2.0.0" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", + "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" + }, + "node_modules/@vue/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-z1ZIAAUS9pKzo/ANEfd2sO+v2IUalz7cM/cTLOZ7vRFOPk5/xuRKQteOu1DErFLAh/lYGXMVZ0IfYKlyInuDVg==", + "dev": true, + "dependencies": { + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0" + }, + "peerDependencies": { + "eslint": ">= 8.0.0", + "prettier": ">= 3.0.0" + } + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz", + "integrity": "sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "^6.7.0", + "@typescript-eslint/parser": "^6.7.0", + "vue-eslint-parser": "^9.3.1" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0", + "eslint-plugin-vue": "^9.0.0", + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/tsconfig": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.5.1.tgz", + "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", + "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001572", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz", + "integrity": "sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "optional": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/core-js": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", + "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==", + "hasInstallScript": true, + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dompurify": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz", + "integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==", + "optional": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.616", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz", + "integrity": "sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==", + "dev": true, + "peer": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true, + "peer": true + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.2.tgz", + "integrity": "sha512-dhlpWc9vOwohcWmClFcA+HjlvUpuyynYs0Rf+L/P6/0iQE6vlHW9l5bkfzN62/Stm9fbq8ku46qzde76T1xlSg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.19.2.tgz", + "integrity": "sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.13", + "semver": "^7.5.4", + "vue-eslint-parser": "^9.3.1", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jspdf/node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "peer": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true, + "peer": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pinia": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz", + "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.2.tgz", + "integrity": "sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz", + "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.0", + "@rollup/rollup-android-arm64": "4.21.0", + "@rollup/rollup-darwin-arm64": "4.21.0", + "@rollup/rollup-darwin-x64": "4.21.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.0", + "@rollup/rollup-linux-arm-musleabihf": "4.21.0", + "@rollup/rollup-linux-arm64-gnu": "4.21.0", + "@rollup/rollup-linux-arm64-musl": "4.21.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", + "@rollup/rollup-linux-riscv64-gnu": "4.21.0", + "@rollup/rollup-linux-s390x-gnu": "4.21.0", + "@rollup/rollup-linux-x64-gnu": "4.21.0", + "@rollup/rollup-linux-x64-musl": "4.21.0", + "@rollup/rollup-win32-arm64-msvc": "4.21.0", + "@rollup/rollup-win32-ia32-msvc": "4.21.0", + "@rollup/rollup-win32-x64-msvc": "4.21.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sass": { + "version": "1.32.13", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.13.tgz", + "integrity": "sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/sass-loader": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", + "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, + "node_modules/stackblur-canvas": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz", + "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.5.tgz", + "integrity": "sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/three": { + "version": "0.160.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.160.0.tgz", + "integrity": "sha512-DLU8lc0zNIPkM7rH5/e1Ks1Z8tWCGRq6g8mPowdDJpw1CFBJMU7UoJjC6PefXW7z//SSl0b2+GCw14LB+uDhng==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unplugin": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.6.0.tgz", + "integrity": "sha512-BfJEpWBu3aE/AyHx8VaNE/WgouoQxgH9baAiH82JjX8cqVyi3uJQstqwD5J+SZxIK326SZIhsSZlALXVBCknTQ==", + "dev": true, + "dependencies": { + "acorn": "^8.11.2", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.6.1" + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz", + "integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.6", + "@rollup/pluginutils": "^5.0.4", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.3", + "minimatch": "^9.0.3", + "resolve": "^1.22.4", + "unplugin": "^1.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/unplugin-vue-components/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vite": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", + "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.41", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz", + "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", + "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", + "dependencies": { + "@vue/compiler-sfc": "2.7.16", + "csstype": "^3.1.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz", + "integrity": "sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vue-router": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.6.5.tgz", + "integrity": "sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==" + }, + "node_modules/vue-virtual-scroll-list": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/vue-virtual-scroll-list/-/vue-virtual-scroll-list-2.3.5.tgz", + "integrity": "sha512-YFK6u5yltqtAOfTBcij/KGAS2SoZvzbNIAf9qTULauPObEp53xj22tDuohrrM2vNkgoD5kejXICIUBt2Q4ZDqQ==" + }, + "node_modules/vuetify": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.7.1.tgz", + "integrity": "sha512-DVFmRsDtYrITw9yuGLwpFWngFYzEgk0KwloDCIV3+vhZw+NBFJOSzdbttbYmOwtqvQlhDxUyIRQolrRbSFAKlg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/johnleider" + }, + "peerDependencies": { + "vue": "^2.6.4" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", + "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/photon-client/package.json b/photon-client/package.json new file mode 100644 index 0000000..69a40a2 --- /dev/null +++ b/photon-client/package.json @@ -0,0 +1,50 @@ +{ + "name": "photonclient", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p build-only", + "preview": "vite preview --port 4173", + "build-only": "vite build", + "build-demo": "vite build --mode demo", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/", + "lint-ci": "eslint . --max-warnings 0 --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format-ci": "prettier --check src/" + }, + "dependencies": { + "@fontsource/prompt": "^5.0.9", + "@mdi/font": "^7.4.47", + "@msgpack/msgpack": "^3.0.0-beta2", + "axios": "^1.6.3", + "jspdf": "^2.5.1", + "pinia": "^2.1.4", + "three": "^0.160.0", + "vue": "^2.7.14", + "vue-router": "^3.6.5", + "vue-virtual-scroll-list": "^2.3.5", + "vuetify": "^2.7.1" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.3.2", + "@types/node": "^18.19.45", + "@types/three": "^0.160.0", + "@vitejs/plugin-vue2": "^2.3.1", + "@vue/eslint-config-prettier": "^9.0.0", + "@vue/eslint-config-typescript": "^12.0.0", + "@vue/tsconfig": "^0.5.1", + "deepmerge": "^4.3.1", + "eslint": "^8.56.0", + "eslint-plugin-vue": "^9.19.2", + "npm-run-all": "^4.1.5", + "prettier": "3.2.2", + "sass": "~1.32", + "sass-loader": "^13.3.2", + "terser": "^5.14.2", + "typescript": "^5.3.3", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.4.2" + } +} diff --git a/photon-client/public/favicon.ico b/photon-client/public/favicon.ico new file mode 100644 index 0000000..461dfbe Binary files /dev/null and b/photon-client/public/favicon.ico differ diff --git a/photon-client/src/App.vue b/photon-client/src/App.vue new file mode 100644 index 0000000..a9abc26 --- /dev/null +++ b/photon-client/src/App.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/photon-client/src/assets/fonts/PromptRegular.ts b/photon-client/src/assets/fonts/PromptRegular.ts new file mode 100644 index 0000000..231e71b --- /dev/null +++ b/photon-client/src/assets/fonts/PromptRegular.ts @@ -0,0 +1 @@ +export const font = "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S7AmUFhAHwMBAQQBAAUBAGEABQUCWQACAhpLAAYGB1kABwcYB0wbQB0DAQEEAQAFAQBhAAIABQYCBWEABgYHWQAHBxgHTFlACxEREREREREQCAccKxMjNTM1MxUzFSMVIwchFSGjdnZSd3dSdgE//sEBClN2dlN3QVIAAQBB/wMCngLKAAcABrMCAAEwKxMhESMRIREjQQJdZv5vZgLK/DkDcfyPAAABAC0AAAK1A0AACAAGswcFATArEyM1MxMBMwEjnG+2cAECYP7NYAFuTP7AAsb8wAABAC3/aQJKArsACwAGswoDATArFwEBNSEVIQEBIRUhLQEV/usCHf5mAQX++wGa/eNWAWgBaEFW/q3+rFUAAAEALf+pAdkCdQAIAAazBwMBMCsTByc3FwcnESPcejXW1jZ7TAHYji3+/i2Q/c8AAAEALQA5AvkB5gAIAAazCAYBMCslNyE1ISc3FwcBzo790QIxkC3+/m96S3s319YAAAEALf+pAdkCdQAIAAazCAMBMCs3NxcRMxE3FwctNXpMezbWpy2OAi/9z5At/gABAC0AOQL5AeYACAAGswgBATArEzcXByEVIRcHLf4tkAIx/dGOLQEP1zd7S3o2AAABAC0ATwIjAkUAAwAGswMBATArEzcXBy37+/sBSvv7+wAAAgAtAAACHwLPAAUACQAItQkHBAECMCsTEzMTAyMTJwcXLd043dw6s5aVlQFnAWj+mP6ZAWfx8fIAAAEALQCZAY8B+wADAAazAgABMCsTIREhLQFi/p4B+/6eAAABAC0AAAJlAiYAAgAGswEAATArAQEhAUkBHP3IAib92gAAAQAtAAECUwI5AAIABrMCAAEwKxMBAS0CJv3aAjn+5P7kAAEALQAAAmUCJgACAAazAgABMCsTIQEtAjj+5AIm/doAAQAtAAECUwI5AAIABrMCAQEwKxMBES0CJgEdARz9yAACAC0AAAJlAiYAAgAFAAi1BAMBAAIwKwEBISUDAwFJARz9yAHHq6wCJv3aQgFP/rEAAAIALQABAlMCOQACAAUACLUFBAIAAjArEwkCJREtAib92gGR/rICOf7k/uQBHKv+qQAAAgAtAAACZQImAAIABQAItQUDAgACMCsTIQETIRMtAjj+5Kv+qawCJv3aAeL+swAAAgAtAAECUwI5AAIABQAItQUDAgECMCsTARED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AjsAQQI4AEECOwBBAkUAHgI+AEECOwBBAjsAQQI7AEECOwBBAjsAQQI7AEECOwBBAjsAQQI7AEECJwBBAswALQLMAC0CzAAtAswALQLMAC0CzAAtAswALQK7AEEC0AAZArsAQQK7AEECuwBBAOkAQQJlAEIA7ABCAOz/3wD3/8EA7P+7AOz/yQDsADwA7AA8AOz/9ADsACMA7P/WAOz//gDs/7cBdQAZAXcAGQKeAEECngBBAiIAQQIiAEECIgBBAiIAQQIiAEECIgBBAioARgIiAEECVgAPA0AAQQNAAEECrgBBAq4AQQKuAEECrgBBAq4AQQKuAEECrgBBAq4AQQKuAEEDOgAtAzoALQM6AC0DOgAtAzoALQM6AC0DOgAtAzoALQM6AC0DOgAtAzoALQM6AC0DOgAtAzoALQNeAC0DYQAtA2EALQNhAC0DYQAtA2EALQM6AC0DOgAtAzoALQM6AC0DOgAtA3sALQJbAEECWwBBAzEALQJvAEECbwBBAm8AQQJvAEECbwBBAm8AQQJvAEECMQAtAjEALQIxAC0CMQAtAjEALQIxAC0CMQAtAjEALQJyADwDNwAtAksAGQJLABkCSwAZAksAGQJLABkCSwAZAksAGQK5ADwCuQA8ArkAPAK5ADwCuQA8ArkAPAK5ADwCuQA8ArkAPAK5ADwCuQA8ArkAPAK5ADwDRgA8A0gAPANIADwDSAA8A0gAPANIADwCuQA8ArkAPAK5ADwCuQA8ArkAPALqAC0D6QAtA+kALQPvADAD6QAtA+kALQLcAC4CvAAtArwALQLCADACvAAtArwALQK8AC0CvAAtArwALQK8AC0CagAtAmoALQJqAC0CagAtAmoALQI5AC0COQAtAjkALQI5AC0COQAtAjkALQI5AC0COQAtAjkALQI5AC0COAAtAjkALQI5AB4CNQAtAjkALQI5AC0COQAtAjkALQI5AC0ClAAtAjkALQI5AC0COQAtAjkALQI5AC0DzAAtA8wALQKUAEECIwAtAiMALQIjAC0CIwAtAiMALQIjAC0ClAAtAnEALQMvAC0CowAtApQALQKUAC0CYAAtAmAALQJgAC0CYAAtAmAALQJfAC0CYAAtAmAAIwJdAC0CYAAtAmAALQJgAC0CYAAtAmAALQJgAC0CYAAtAmAALQJgAC0BYQAZApQALQKUAC0ClAAtApQALQKUAC0ClAAtApQALQJYAEECXgAZAlgAQQJYAEECWABBAPoAQQDoAEEA6ABBAOj/3QD2/8AA6P+6AOj/yAD7AEEA6P/zAOgAIgG+AEEA6P/UAPoABADo/7YA+v+6APH/ugDs/7UCOQBBAjkAQQI3AEEA5wBBAOcAQQGzAEEA+P//AWQAQQDsADwA6P/YAOr/2AE8/+cDYwBBA2MAQQJYAEECWABBAsIAHgJYAEECWABBAlgAQQJYAEECWABBAlgAQQJYAEECfQAtAn0ALQJ9AC0CjwA/Ao8APwJ9AC0CfQAtAn0ALQJ9AC0CfQAtAoYANgJ9AC0CfQAtAn0ALQKlAC0CqAAtAqgALQKoAC0CqAAtAqgALQKGADYCfQAtAn0ALQJ9AC0CiwA7BBcALQKUAEEClABBApQALQGPAEEBjwBBAbkADAGP/+0BnABBAZsAQQGiADIB6QAtAekALQHpAC0B6QAtAekALQHpAC0B6QAtAekALQJ5ADwCYAAtAXYAGQF3ABkBtgAZAXkAGQF2ABkBnwAUAXYAGQF5ABkCWAA8AlgAPAJYADwCWAA8AlgAPAJYADwCWAA8AlgAPAJYADwCWAA8AlgAPAJYADwCWAA8As4APALRADwC0QA8AtEAPALRADwC0QA8AlkAPAJYADwCSQA8AlgAPAJYADwCSgAtAzIALQMyAC0DMgAtAzIALQMyAC0CaQAtAksALQJLAC0CSwAtAksALQJLAC0CSwAtAksALQJLAC0CSwAtAfMALQHzAC0B8wAtAfMALQHzAC0CVQAZAk4AGQGnAC0B4gAtAb4AMgKvAC0DNAAtAl0AQQK5ACMCgAAtAoAALQKTAC0CgAAtAoAALQKTAC0CkwAtApMALQKTAC0CkwAtApMALQNtAC0DfwAtA+0ALQPsAC0CRgAtAk0ALQJIAC0CTwAtAn8AQQJ/AEECgwBBAqMALQKjAC0CdABBAnIALQJ0AEECdABBA+QALQPkAC0EDgAtAmwALQIuAC0B/wAtAksALQJAAC0CawAtAl0ALQJnAC0CYAAtAl8ALQJuAC0CgwBBAoMAQQKQAEECdwAtAoQAQQKEAEEC9QAjAwoAIwJjAEECjwBBAo8AQQJhAC0CagAtAaQADwGo/xEBpAAPAUsALQEpAEECKwBBAUoAAAEu//sBSgAAApQALQKgAC0BawAt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AqYCpwKpAq0CrgKrAqUCpAKvAqwCqAKqAa4BvQG+AcEBwgHNAdIB0AHVAb8BwAHKAbsBtQG3AdMBxwHMAcsBxAHFAa8BxgHOAcgB2AHZAdwB3QHfAd4BsAHJAdsBzwGxAdYBswHRAcMB2gHXAeAB4QHjAeQCUgHoAs4B5QHmAuAC4gLkAuYC7wLxAu0CVQHpAeoB7QHsAesB5wJRAt0C0ALSAtUC2ALaAugC3wJPAk4CUAApAPgAKgD5AEQBEwBJARgARwEWAGEBMgBiATMAYwE0AGYBNwBrAT0AbAE+AG4BQACRAWMAkgFkAJMBZQCaAWwAmwFtAKMBdgCkAXcAwgGVAL8BkgDBAZQAyAGbANEBpAAUAOIAFgDkAA4A3AAQAN4AEQDfABIA4AAPAN0ABwDVAAkA1wAKANgACwDZAAgA1gA3AQYAOQEIADwBCwAwAP8AMgEBADMBAgA0AQMAMQEAAFQBIgBSASAAewFNAH0BTwB1AUcAdwFJAHgBSgB5AUsAdgFIAH8BUQCBAVMAggFUAIMBVQCAAVIArwGCALEBhACzAYYAtQGIALYBiQC3AYoAtAGHAMoBnQDJAZwAywGeAMwBnwI/Aj4CPQJAAkkCSgJIAp8CoAIkAjsCPAGpAmMCXgJlAmAChAKBAoICgwKAAnYCawJ/AnQCcAKIAowCiQKNAooCjgKLAo+wACwgsABVWEVZICBLuAAOUUuwBlNaWLA0G7AoWWBmIIpVWLACJWG5CAAIAGNjI2IbISGwAFmwAEMjRLIAAQBDYEItsAEssCBgZi2wAiwgZCCwwFCwBCZasigBCkNFY0WwBkVYIbADJVlSW1ghIyEbilggsFBQWCGwQFkbILA4UFghsDhZWSCxAQpDRWNFYWSwKFBYIbEBCkNFY0UgsDBQWCGwMFkbILDAUFggZiCKimEgsApQWGAbILAgUFghsApgGyCwNlBYIbA2YBtgWVlZG7ABK1lZI7AAUFhlWVktsAMsIEUgsAQlYWQgsAVDUFiwBSNCsAYjQhshIVmwAWAtsAQsIyEjISBksQViQiCwBiNCsAZFWBuxAQpDRWOxAQpDsARgRWOwAyohILAGQyCKIIqwASuxMAUlsAQmUVhgUBthUllYI1khWSCwQFNYsAErGyGwQFkjsABQWGVZLbAFLLAHQyuyAAIAQ2BCLbAGLLAHI0IjILAA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sAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xLgEUKy2wQSywOCsjIC5GsAIlRlJYIDxZLrEuARQrLbBCLLA5K4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrEuARQrsARDLrAuKy2wQyywABawBCWwBCYgLkcjRyNhsAlDKyMgPCAuIzixLgEUKy2wRCyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbEuARQrLbBFLLA4Ky6xLgEUKy2wRiywOSshIyAgPLAEI0IjOLEuARQrsARDLrAuKy2wRyywABUgR7AAI0KyAAEBFRQTLrA0Ki2wSCywABUgR7AAI0KyAAEBFRQTLrA0Ki2wSSyxAAEUE7A1Ki2wSiywNyotsEsssAAWRSMgLiBGiiNhOLEuARQrLbBMLLAII0KwSystsE0ssgAARCstsE4ssgABRCstsE8ssgEARCstsFAssgEBRCstsFEssgAARSstsFIssgABRSstsFMssgEARSstsFQssgEBRSstsFUssgAAQSstsFYssgABQSstsFcssgEAQSstsFgssgEBQSstsFkssgAAQystsFossgABQystsFsssgEAQystsFwssgEBQystsF0ssgAARistsF4ssgABRistsF8ssgEARistsGAssgEBRistsGEssgAAQistsGIssgABQistsGMssgEAQistsGQssgEBQistsGUssDorLrEuARQrLbBmLLA6K7A+Ky2wZyywOiuwPystsGgssAAWsDorsEArLbBpLLA7Ky6xLgEUKy2waiywOyuwPistsGsssDsrsD8rLbBsLLA7K7BAKy2wbSywPCsusS4BFCstsG4ssDwrsD4rLbBvLLA8K7A/Ky2wcCywPCuwQCstsHEssD0rLrEuARQrLbByLLA9K7A+Ky2wcyywPSuwPystsHQssD0rsEArLbB1LLMJBAIDRVghGyMhWUIrsAhlsAMkUHixBQEVRVgwWS0AAABLuADIUlixAQGOWbABuQgACABjcLEAB0K1TAAAIgQAKrEAB0JACkEJNQQpBBUIBAgqsQAHQkAKTAc7Ai8CHwYECCqxAAtCvRCADYAKgAWAAAQACSqxAA9CvQBAAEAAQABAAAQACSqxAwBEsSQBiFFYsECIWLEDZESxJgGIUVi6CIAAAQRAiGNUWLEDAERZWVlZQApDCTcEKwQXCAQMKrgB/4WwBI2xAgBEswVkBgBERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGcAZwBQAFACuwAAAssB4AAA/yUEQv5aAsr/8QLLAe//8f8aBEL+WgBFAEUAQABAAKn/YARC/loAs/9WBEL+WgBFAEUAQABAArsBcgRC/loCxQFoBEL+WgBmAGYAUQBRAhcAAALKAvD/PP+e/80EQv5aAib/8QLKAvr/IP+e/80EQv5aAAAAAAAOAK4AAwABBAkAAABuAAAAAwABBAkAAQAMAG4AAwABBAkAAgAOAHoAAwABBAkAAwAyAIgAAwABBAkABAAcALoAAwABBAkABQAaANYAAwABBAkABgAcALoAAwABBAkACAAWAPAAAwABBAkACQAaAQYAAwABBAkACgBuAAAAAwABBAkACwAmASAAAwABBAkADAAgAUYAAwABBAkADSIMAWYAAwABBAkADgA0I3IAQwBvAHAAeQByAGkAZwBoAHQAIAAoAGMAKQAgADIAMAAxADUALAAgAEMAYQBkAHMAbwBuACAARABlAG0AYQBrACAAKABpAG4AZgBvAEAAYwBhAGQAcwBvAG4AZABlAG0AYQBrAC4AYwBvAG0AKQBQAHIAbwBtAHAAdABSAGUAZwB1AGwAYQByADEALgAwADAAMAA7AEMARABLACAAOwBQAHIAbwBtAHAAdAAtAFIAZQBnAHUAbABhAHIAUAByAG8AbQBwAHQALQBSAGUAZwB1AGwAYQByAFYAZQByAHMAaQBvAG4AIAAxAC4AMAAwADAAQwBhAGQAcwBvAG4ARABlAG0AYQBrAEsAYQB0AGEAdAByAGEAZAAgAFQAZQBhAG0AdwB3AHcALgBjAGEAZABzAG8AbgBkAGUAbQBhAGsALgBjAG8AbQB3AHcAdwAuAGsAYQB0AGEAdAByAGEAZAAuAGMAbwBtAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABjACkAIAAyADAAMQA1ACwAIABDAGEAZABzAG8AbgAgAEQAZQBtAGEAawAgACgAaQBuAGYAbwBAAGMAYQBkAHMAbwBuAGQAZQBtAGEAawAuAGMAbwBtACkACgAKAFQAaABpAHMAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAaQBzACAAbABpAGMAZQBuAHMAZQBkACAAdQBuAGQAZQByACAAdABoAGUAIABTAEkATAAgAE8AcABlAG4AIABGAG8AbgB0ACAATABpAGMAZQBuAHMAZQAsACAAVgBlAHIAcwBpAG8AbgAgADEALgAxAC4ACgBUAGgAaQBzACAAbABpAGMAZQBuAHMAZQAgAGkAcwAgAGMAbwBwAGkAZQBkACAAYgBlAGwAbwB3ACwAIABhAG4AZAAgAGkAcwAgAGEAbABzAG8AIABhAHYAYQBpAGwAYQBiAGwAZQAgAHcAaQB0AGgAIABhACAARgBBAFEAIABhAHQAOgAKAGgAdAB0AHAAOgAvAC8AcwBjAHIAaQBwAHQAcwAuAHMAaQBsAC4AbwByAGcALwBPAEYATAAKAAoACgAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ACgBTAEkATAAgAE8AUABFAE4AIABGAE8ATgBUACAATABJAEMARQBOAFMARQAgAFYAZQByAHMAaQBvAG4AIAAxAC4AMQAgAC0AIAAyADYAIABGAGUAYgByAHUAYQByAHkAIAAyADAAMAA3AAoALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAAoACgBQAFIARQBBAE0AQgBMAEUACgBUAGgAZQAgAGcAbwBhAGwAcwAgAG8AZgAgAHQAaABlACAATwBwAGUAbgAgAEYAbwBuAHQAIABMAGkAYwBlAG4AcwBlACAAKABPAEYATAApACAAYQByAGUAIAB0AG8AIABzAHQAaQBtAHUAbABhAHQAZQAgAHcAbwByAGwAZAB3AGkAZABlAAoAZABlAHYAZQBsAG8AcABtAGUAbgB0ACAAbwBmACAAYwBvAGwAbABhAGIAbwByAGEAdABpAHYAZQAgAGYAbwBuAHQAIABwAHIAbwBqAGUAYwB0AHMALAAgAHQAbwAgAHMAdQBwAHAAbwByAHQAIAB0AGgAZQAgAGYAbwBuAHQAIABjAHIAZQBhAHQAaQBvAG4ACgBlAGYAZgBvAHIAdABzACAAbwBmACAAYQBjAGEAZABlAG0AaQBjACAAYQBuAGQAIABsAGkAbgBnAHUAaQBzAHQAaQBjACAAYwBvAG0AbQB1AG4AaQB0AGkAZQBzACwAIABhAG4AZAAgAHQAbwAgAHAAcgBvAHYAaQBkAGUAIABhACAAZgByAGUAZQAgAGEAbgBkAAoAbwBwAGUAbgAgAGYAcgBhAG0AZQB3AG8AcgBrACAAaQBuACAAdwBoAGkAYwBoACAAZgBvAG4AdABzACAAbQBhAHkAIABiAGUAIABzAGgAYQByAGUAZAAgAGEAbgBkACAAaQBtAHAAcgBvAHYAZQBkACAAaQBuACAAcABhAHIAdABuAGUAcgBzAGgAaQBwAAoAdwBpAHQAaAAgAG8AdABoAGUAcgBzAC4ACgAKAFQAaABlACAATwBGAEwAIABhAGwAbABvAHcAcwAgAHQAaABlACAAbABpAGMAZQBuAHMAZQBkACAAZgBvAG4AdABzACAAdABvACAAYgBlACAAdQBzAGUAZAAsACAAcwB0AHUAZABpAGUAZAAsACAAbQBvAGQAaQBmAGkAZQBkACAAYQBuAGQACgByAGUAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAZgByAGUAZQBsAHkAIABhAHMAIABsAG8AbgBnACAAYQBzACAAdABoAGUAeQAgAGEAcgBlACAAbgBvAHQAIABzAG8AbABkACAAYgB5ACAAdABoAGUAbQBzAGUAbAB2AGUAcwAuACAAVABoAGUACgBmAG8AbgB0AHMALAAgAGkAbgBjAGwAdQBkAGkAbgBnACAAYQBuAHkAIABkAGUAcgBpAHYAYQB0AGkAdgBlACAAdwBvAHIAawBzACwAIABjAGEAbgAgAGIAZQAgAGIAdQBuAGQAbABlAGQALAAgAGUAbQBiAGUAZABkAGUAZAAsACAACgByAGUAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAYQBuAGQALwBvAHIAIABzAG8AbABkACAAdwBpAHQAaAAgAGEAbgB5ACAAcwBvAGYAdAB3AGEAcgBlACAAcAByAG8AdgBpAGQAZQBkACAAdABoAGEAdAAgAGEAbgB5ACAAcgBlAHMAZQByAHYAZQBkAAoAbgBhAG0AZQBzACAAYQByAGUAIABuAG8AdAAgAHUAcwBlAGQAIABiAHkAIABkAGUAcgBpAHYAYQB0AGkAdgBlACAAdwBvAHIAawBzAC4AIABUAGgAZQAgAGYAbwBuAHQAcwAgAGEAbgBkACAAZABlAHIAaQB2AGEAdABpAHYAZQBzACwACgBoAG8AdwBlAHYAZQByACwAIABjAGEAbgBuAG8AdAAgAGIAZQAgAHIAZQBsAGUAYQBzAGUAZAAgAHUAbgBkAGUAcgAgAGEAbgB5ACAAbwB0AGgAZQByACAAdAB5AHAAZQAgAG8AZgAgAGwAaQBjAGUAbgBzAGUALgAgAFQAaABlAAoAcgBlAHEAdQBpAHIAZQBtAGUAbgB0ACAAZgBvAHIAIABmAG8AbgB0AHMAIAB0AG8AIAByAGUAbQBhAGkAbgAgAHUAbgBkAGUAcgAgAHQAaABpAHMAIABsAGkAYwBlAG4AcwBlACAAZABvAGUAcwAgAG4AbwB0ACAAYQBwAHAAbAB5AAoAdABvACAAYQBuAHkAIABkAG8AYwB1AG0AZQBuAHQAIABjAHIAZQBhAHQAZQBkACAAdQBzAGkAbgBnACAAdABoAGUAIABmAG8AbgB0AHMAIABvAHIAIAB0AGgAZQBpAHIAIABkAGUAcgBpAHYAYQB0AGkAdgBlAHMALgAKAAoARABFAEYASQBOAEkAVABJAE8ATgBTAAoAIgBGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACIAIAByAGUAZgBlAHIAcwAgAHQAbwAgAHQAaABlACAAcwBlAHQAIABvAGYAIABmAGkAbABlAHMAIAByAGUAbABlAGEAcwBlAGQAIABiAHkAIAB0AGgAZQAgAEMAbwBwAHkAcgBpAGcAaAB0AAoASABvAGwAZABlAHIAKABzACkAIAB1AG4AZABlAHIAIAB0AGgAaQBzACAAbABpAGMAZQBuAHMAZQAgAGEAbgBkACAAYwBsAGUAYQByAGwAeQAgAG0AYQByAGsAZQBkACAAYQBzACAAcwB1AGMAaAAuACAAVABoAGkAcwAgAG0AYQB5AAoAaQBuAGMAbAB1AGQAZQAgAHMAbwB1AHIAYwBlACAAZgBpAGwAZQBzACwAIABiAHUAaQBsAGQAIABzAGMAcgBpAHAAdABzACAAYQBuAGQAIABkAG8AYwB1AG0AZQBuAHQAYQB0AGkAbwBuAC4ACgAKACIAUgBlAHMAZQByAHYAZQBkACAARgBvAG4AdAAgAE4AYQBtAGUAIgAgAHIAZQBmAGUAcgBzACAAdABvACAAYQBuAHkAIABuAGEAbQBlAHMAIABzAHAAZQBjAGkAZgBpAGUAZAAgAGEAcwAgAHMAdQBjAGgAIABhAGYAdABlAHIAIAB0AGgAZQAKAGMAbwBwAHkAcgBpAGcAaAB0ACAAcwB0AGEAdABlAG0AZQBuAHQAKABzACkALgAKAAoAIgBPAHIAaQBnAGkAbgBhAGwAIABWAGUAcgBzAGkAbwBuACIAIAByAGUAZgBlAHIAcwAgAHQAbwAgAHQAaABlACAAYwBvAGwAbABlAGMAdABpAG8AbgAgAG8AZgAgAEYAbwBuAHQAIABTAG8AZgB0AHcAYQByAGUAIABjAG8AbQBwAG8AbgBlAG4AdABzACAAYQBzAAoAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAYgB5ACAAdABoAGUAIABDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByACgAcwApAC4ACgAKACIATQBvAGQAaQBmAGkAZQBkACAAVgBlAHIAcwBpAG8AbgAiACAAcgBlAGYAZQByAHMAIAB0AG8AIABhAG4AeQAgAGQAZQByAGkAdgBhAHQAaQB2AGUAIABtAGEAZABlACAAYgB5ACAAYQBkAGQAaQBuAGcAIAB0AG8ALAAgAGQAZQBsAGUAdABpAG4AZwAsAAoAbwByACAAcwB1AGIAcwB0AGkAdAB1AHQAaQBuAGcAIAAtAC0AIABpAG4AIABwAGEAcgB0ACAAbwByACAAaQBuACAAdwBoAG8AbABlACAALQAtACAAYQBuAHkAIABvAGYAIAB0AGgAZQAgAGMAbwBtAHAAbwBuAGUAbgB0AHMAIABvAGYAIAB0AGgAZQAKAE8AcgBpAGcAaQBuAGEAbAAgAFYAZQByAHMAaQBvAG4ALAAgAGIAeQAgAGMAaABhAG4AZwBpAG4AZwAgAGYAbwByAG0AYQB0AHMAIABvAHIAIABiAHkAIABwAG8AcgB0AGkAbgBnACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAdABvACAAYQAKAG4AZQB3ACAAZQBuAHYAaQByAG8AbgBtAGUAbgB0AC4ACgAKACIAQQB1AHQAaABvAHIAIgAgAHIAZQBmAGUAcgBzACAAdABvACAAYQBuAHkAIABkAGUAcwBpAGcAbgBlAHIALAAgAGUAbgBnAGkAbgBlAGUAcgAsACAAcAByAG8AZwByAGEAbQBtAGUAcgAsACAAdABlAGMAaABuAGkAYwBhAGwACgB3AHIAaQB0AGUAcgAgAG8AcgAgAG8AdABoAGUAcgAgAHAAZQByAHMAbwBuACAAdwBoAG8AIABjAG8AbgB0AHIAaQBiAHUAdABlAGQAIAB0AG8AIAB0AGgAZQAgAEYAbwBuAHQAIABTAG8AZgB0AHcAYQByAGUALgAKAAoAUABFAFIATQBJAFMAUwBJAE8ATgAgACYAIABDAE8ATgBEAEkAVABJAE8ATgBTAAoAUABlAHIAbQBpAHMAcwBpAG8AbgAgAGkAcwAgAGgAZQByAGUAYgB5ACAAZwByAGEAbgB0AGUAZAAsACAAZgByAGUAZQAgAG8AZgAgAGMAaABhAHIAZwBlACwAIAB0AG8AIABhAG4AeQAgAHAAZQByAHMAbwBuACAAbwBiAHQAYQBpAG4AaQBuAGcACgBhACAAYwBvAHAAeQAgAG8AZgAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAsACAAdABvACAAdQBzAGUALAAgAHMAdAB1AGQAeQAsACAAYwBvAHAAeQAsACAAbQBlAHIAZwBlACwAIABlAG0AYgBlAGQALAAgAG0AbwBkAGkAZgB5ACwACgByAGUAZABpAHMAdAByAGkAYgB1AHQAZQAsACAAYQBuAGQAIABzAGUAbABsACAAbQBvAGQAaQBmAGkAZQBkACAAYQBuAGQAIAB1AG4AbQBvAGQAaQBmAGkAZQBkACAAYwBvAHAAaQBlAHMAIABvAGYAIAB0AGgAZQAgAEYAbwBuAHQACgBTAG8AZgB0AHcAYQByAGUALAAgAHMAdQBiAGoAZQBjAHQAIAB0AG8AIAB0AGgAZQAgAGYAbwBsAGwAbwB3AGkAbgBnACAAYwBvAG4AZABpAHQAaQBvAG4AcwA6AAoACgAxACkAIABOAGUAaQB0AGgAZQByACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAbgBvAHIAIABhAG4AeQAgAG8AZgAgAGkAdABzACAAaQBuAGQAaQB2AGkAZAB1AGEAbAAgAGMAbwBtAHAAbwBuAGUAbgB0AHMALAAKAGkAbgAgAE8AcgBpAGcAaQBuAGEAbAAgAG8AcgAgAE0AbwBkAGkAZgBpAGUAZAAgAFYAZQByAHMAaQBvAG4AcwAsACAAbQBhAHkAIABiAGUAIABzAG8AbABkACAAYgB5ACAAaQB0AHMAZQBsAGYALgAKAAoAMgApACAATwByAGkAZwBpAG4AYQBsACAAbwByACAATQBvAGQAaQBmAGkAZQBkACAAVgBlAHIAcwBpAG8AbgBzACAAbwBmACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAbQBhAHkAIABiAGUAIABiAHUAbgBkAGwAZQBkACwACgByAGUAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAYQBuAGQALwBvAHIAIABzAG8AbABkACAAdwBpAHQAaAAgAGEAbgB5ACAAcwBvAGYAdAB3AGEAcgBlACwAIABwAHIAbwB2AGkAZABlAGQAIAB0AGgAYQB0ACAAZQBhAGMAaAAgAGMAbwBwAHkACgBjAG8AbgB0AGEAaQBuAHMAIAB0AGgAZQAgAGEAYgBvAHYAZQAgAGMAbwBwAHkAcgBpAGcAaAB0ACAAbgBvAHQAaQBjAGUAIABhAG4AZAAgAHQAaABpAHMAIABsAGkAYwBlAG4AcwBlAC4AIABUAGgAZQBzAGUAIABjAGEAbgAgAGIAZQAKAGkAbgBjAGwAdQBkAGUAZAAgAGUAaQB0AGgAZQByACAAYQBzACAAcwB0AGEAbgBkAC0AYQBsAG8AbgBlACAAdABlAHgAdAAgAGYAaQBsAGUAcwAsACAAaAB1AG0AYQBuAC0AcgBlAGEAZABhAGIAbABlACAAaABlAGEAZABlAHIAcwAgAG8AcgAKAGkAbgAgAHQAaABlACAAYQBwAHAAcgBvAHAAcgBpAGEAdABlACAAbQBhAGMAaABpAG4AZQAtAHIAZQBhAGQAYQBiAGwAZQAgAG0AZQB0AGEAZABhAHQAYQAgAGYAaQBlAGwAZABzACAAdwBpAHQAaABpAG4AIAB0AGUAeAB0ACAAbwByAAoAYgBpAG4AYQByAHkAIABmAGkAbABlAHMAIABhAHMAIABsAG8AbgBnACAAYQBzACAAdABoAG8AcwBlACAAZgBpAGUAbABkAHMAIABjAGEAbgAgAGIAZQAgAGUAYQBzAGkAbAB5ACAAdgBpAGUAdwBlAGQAIABiAHkAIAB0AGgAZQAgAHUAcwBlAHIALgAKAAoAMwApACAATgBvACAATQBvAGQAaQBmAGkAZQBkACAAVgBlAHIAcwBpAG8AbgAgAG8AZgAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAgAG0AYQB5ACAAdQBzAGUAIAB0AGgAZQAgAFIAZQBzAGUAcgB2AGUAZAAgAEYAbwBuAHQACgBOAGEAbQBlACgAcwApACAAdQBuAGwAZQBzAHMAIABlAHgAcABsAGkAYwBpAHQAIAB3AHIAaQB0AHQAZQBuACAAcABlAHIAbQBpAHMAcwBpAG8AbgAgAGkAcwAgAGcAcgBhAG4AdABlAGQAIABiAHkAIAB0AGgAZQAgAGMAbwByAHIAZQBzAHAAbwBuAGQAaQBuAGcACgBDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByAC4AIABUAGgAaQBzACAAcgBlAHMAdAByAGkAYwB0AGkAbwBuACAAbwBuAGwAeQAgAGEAcABwAGwAaQBlAHMAIAB0AG8AIAB0AGgAZQAgAHAAcgBpAG0AYQByAHkAIABmAG8AbgB0ACAAbgBhAG0AZQAgAGEAcwAKAHAAcgBlAHMAZQBuAHQAZQBkACAAdABvACAAdABoAGUAIAB1AHMAZQByAHMALgAKAAoANAApACAAVABoAGUAIABuAGEAbQBlACgAcwApACAAbwBmACAAdABoAGUAIABDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByACgAcwApACAAbwByACAAdABoAGUAIABBAHUAdABoAG8AcgAoAHMAKQAgAG8AZgAgAHQAaABlACAARgBvAG4AdAAKAFMAbwBmAHQAdwBhAHIAZQAgAHMAaABhAGwAbAAgAG4AbwB0ACAAYgBlACAAdQBzAGUAZAAgAHQAbwAgAHAAcgBvAG0AbwB0AGUALAAgAGUAbgBkAG8AcgBzAGUAIABvAHIAIABhAGQAdgBlAHIAdABpAHMAZQAgAGEAbgB5AAoATQBvAGQAaQBmAGkAZQBkACAAVgBlAHIAcwBpAG8AbgAsACAAZQB4AGMAZQBwAHQAIAB0AG8AIABhAGMAawBuAG8AdwBsAGUAZABnAGUAIAB0AGgAZQAgAGMAbwBuAHQAcgBpAGIAdQB0AGkAbwBuACgAcwApACAAbwBmACAAdABoAGUACgBDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByACgAcwApACAAYQBuAGQAIAB0AGgAZQAgAEEAdQB0AGgAbwByACgAcwApACAAbwByACAAdwBpAHQAaAAgAHQAaABlAGkAcgAgAGUAeABwAGwAaQBjAGkAdAAgAHcAcgBpAHQAdABlAG4ACgBwAGUAcgBtAGkAcwBzAGkAbwBuAC4ACgAKADUAKQAgAFQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAsACAAbQBvAGQAaQBmAGkAZQBkACAAbwByACAAdQBuAG0AbwBkAGkAZgBpAGUAZAAsACAAaQBuACAAcABhAHIAdAAgAG8AcgAgAGkAbgAgAHcAaABvAGwAZQAsAAoAbQB1AHMAdAAgAGIAZQAgAGQAaQBzAHQAcgBpAGIAdQB0AGUAZAAgAGUAbgB0AGkAcgBlAGwAeQAgAHUAbgBkAGUAcgAgAHQAaABpAHMAIABsAGkAYwBlAG4AcwBlACwAIABhAG4AZAAgAG0AdQBzAHQAIABuAG8AdAAgAGIAZQAKAGQAaQBzAHQAcgBpAGIAdQB0AGUAZAAgAHUAbgBkAGUAcgAgAGEAbgB5ACAAbwB0AGgAZQByACAAbABpAGMAZQBuAHMAZQAuACAAVABoAGUAIAByAGUAcQB1AGkAcgBlAG0AZQBuAHQAIABmAG8AcgAgAGYAbwBuAHQAcwAgAHQAbwAKAHIAZQBtAGEAaQBuACAAdQBuAGQAZQByACAAdABoAGkAcwAgAGwAaQBjAGUAbgBzAGUAIABkAG8AZQBzACAAbgBvAHQAIABhAHAAcABsAHkAIAB0AG8AIABhAG4AeQAgAGQAbwBjAHUAbQBlAG4AdAAgAGMAcgBlAGEAdABlAGQACgB1AHMAaQBuAGcAIAB0AGgAZQAgAEYAbwBuAHQAIABTAG8AZgB0AHcAYQByAGUALgAKAAoAVABFAFIATQBJAE4AQQBUAEkATwBOAAoAVABoAGkAcwAgAGwAaQBjAGUAbgBzAGUAIABiAGUAYwBvAG0AZQBzACAAbgB1AGwAbAAgAGEAbgBkACAAdgBvAGkAZAAgAGkAZgAgAGEAbgB5ACAAbwBmACAAdABoAGUAIABhAGIAbwB2AGUAIABjAG8AbgBkAGkAdABpAG8AbgBzACAAYQByAGUACgBuAG8AdAAgAG0AZQB0AC4ACgAKAEQASQBTAEMATABBAEkATQBFAFIACgBUAEgARQAgAEYATwBOAFQAIABTAE8ARgBUAFcAQQBSAEUAIABJAFMAIABQAFIATwBWAEkARABFAEQAIAAiAEEAUwAgAEkAUwAiACwAIABXAEkAVABIAE8AVQBUACAAVwBBAFIAUgBBAE4AVABZACAATwBGACAAQQBOAFkAIABLAEkATgBEACwACgBFAFgAUABSAEUAUwBTACAATwBSACAASQBNAFAATABJAEUARAAsACAASQBOAEMATABVAEQASQBOAEcAIABCAFUAVAAgAE4ATwBUACAATABJAE0ASQBUAEUARAAgAFQATwAgAEEATgBZACAAVwBBAFIAUgBBAE4AVABJAEUAUwAgAE8ARgAKAE0ARQBSAEMASABBAE4AVABBAEIASQBMAEkAVABZACwAIABGAEkAVABOAEUAUwBTACAARgBPAFIAIABBACAAUABBAFIAVABJAEMAVQBMAEEAUgAgAFAAVQBSAFAATwBTAEUAIABBAE4ARAAgAE4ATwBOAEkATgBGAFIASQBOAEcARQBNAEUATgBUAAoATwBGACAAQwBPAFAAWQBSAEkARwBIAFQALAAgAFAAQQBUAEUATgBUACwAIABUAFIAQQBEAEUATQBBAFIASwAsACAATwBSACAATwBUAEgARQBSACAAUgBJAEcASABUAC4AIABJAE4AIABOAE8AIABFAFYARQBOAFQAIABTAEgAQQBMAEwAIABUAEgARQAKAEMATwBQAFkAUgBJAEcASABUACAASABPAEwARABFAFIAIABCAEUAIABMAEkAQQBCAEwARQAgAEYATwBSACAAQQBOAFkAIABDAEwAQQBJAE0ALAAgAEQAQQBNAEEARwBFAFMAIABPAFIAIABPAFQASABFAFIAIABMAEkAQQBCAEkATABJAFQAWQAsAAoASQBOAEMATABVAEQASQBOAEcAIABBAE4AWQAgAEcARQBOAEUAUgBBAEwALAAgAFMAUABFAEMASQBBAEwALAAgAEkATgBEAEkAUgBFAEMAVAAsACAASQBOAEMASQBEAEUATgBUAEEATAAsACAATwBSACAAQwBPAE4AUwBFAFEAVQBFAE4AVABJAEEATAAKAEQAQQBNAEEARwBFAFMALAAgAFcASABFAFQASABFAFIAIABJAE4AIABBAE4AIABBAEMAVABJAE8ATgAgAE8ARgAgAEMATwBOAFQAUgBBAEMAVAAsACAAVABPAFIAVAAgAE8AUgAgAE8AVABIAEUAUgBXAEkAUwBFACwAIABBAFIASQBTAEkATgBHAAoARgBSAE8ATQAsACAATwBVAFQAIABPAEYAIABUAEgARQAgAFUAUwBFACAATwBSACAASQBOAEEAQgBJAEwASQBUAFkAIABUAE8AIABVAFMARQAgAFQASABFACAARgBPAE4AVAAgAFMATwBGAFQAVwBBAFIARQAgAE8AUgAgAEYAUgBPAE0ACgBPAFQASABFAFIAIABEAEUAQQBMAEkATgBHAFMAIABJAE4AIABUAEgARQAgAEYATwBOAFQAIABTAE8ARgBUAFcAQQBSAEUALgBoAHQAdABwADoALwAvAHMAYwByAGkAcAB0AHMALgBzAGkAbAAuAG8AcgBnAC8ATwBGAEwAAgAAAAAAAP+1ADIAAAAAAAAAAAAAAAAAAAAAAAAAAAL8AAAAAQACAAMAJADJAQIBAwEEAQUBBgEHAQgAxwEJAQoBCwEMAQ0AYgEOAK0BDwEQAREAYwESAK4AkAETACUAJgD9AP8AZAEUARUAJwDpARYBFwEYARkAKABlARoBGwDIARwBHQEeAR8BIADKASEBIgDLASMBJAElASYAKQAqAPgBJwEoASkBKgErACsBLAEtAS4BLwAsATAAzAExATIAzQDOAPoBMwDPATQBNQE2ATcALQE4AC4BOQAvAToBOwE8AT0BPgE/AUAA4gAwAUEAMQFCAUMBRAFFAUYBRwFIAGYAMgDQAUkBSgDRAUsBTAFNAU4BTwBnAVAA0wFRAVIBUwFUAVUBVgFXAVgBWQCRAVoArwCwADMA7QA0ADUBWwFcAV0BXgFfAWAANgFhAOQA+wFiAWMBZAFlAWYBZwA3AWgBaQFqAWsBbAFtADgA1AFuAW8A1QBoAXABcQFyAXMBdADWAXUBdgF3AXgBeQF6AXsBfAF9AX4BfwGAADkAOgGBAYIBgwGEADsAPADrAYUAuwGGAYcBiAGJAYoAPQGLAOYBjAGNAEQAaQGOAY8BkAGRAZIBkwGUAGsBlQGWAZcBmAGZAGwBmgBqAZsBnAGdAZ4AbgGfAG0AoAGgAEUARgD+AQAAbwGhAaIARwDqAaMBAQGkAaUASABwAaYBpwByAagBqQGqAasBrABzAa0BrgBxAa8BsAGxAbIASQBKAPkBswG0AbUBtgG3AEsBuAG5AboBuwBMANcAdAG8Ab0AdgB3Ab4AdQG/AcABwQHCAcMATQHEAcUATgHGAccATwHIAckBygHLAcwBzQHOAOMAUAHPAFEB0AHRAdIB0wHUAdUB1gHXAHgAUgB5AdgB2QB7AdoB2wHcAd0B3gB8Ad8AegHgAeEB4gHjAeQB5QHmAecB6AChAekAfQCxAFMA7gBUAFUB6gHrAewB7QHuAe8AVgHwAOUA/AHxAfIB8wH0AIkB9QBXAfYB9wH4AfkB+gH7AfwAWAB+Af0B/gCAAIEB/wIAAgECAgIDAH8CBAIFAgYCBwIIAgkCCgILAgwCDQIOAg8AWQBaAhACEQISAhMAWwBcAOwCFAC6AhUCFgIXAhgCGQBdAhoA5wIbAhwAwADBAJ0AngIdAh4CHwIgAJsCIQIiAiMCJAIlAiYCJwIoAikCKgIrAiwCLQIuAi8CMAIxAjICMwI0AjUCNgI3AjgCOQI6AjsCPAI9Aj4CPwJAAkECQgJDAkQCRQJGAkcCSAJJAkoCSwJMAk0CTgJPAlACUQJSAlMCVAJVAlYCVwJYAlkCWgJbAlwCXQJeAl8CYAJhABMAFAAVABYAFwAYABkAGgAbABwAvAD0AmICYwD1APYCZAJlAmYCZwJoAmkCagJrAmwCbQJuAm8CcAJxAnICcwJ0AnUCdgJ3AngCeQJ6AnsCfAJ9An4CfwKAAoECggKDAoQChQANAD8AwwCHAB0ADwCrAAQAowAGABEAIgCiAAUACgAeABIAQgKGAocAXgBgAD4AQAALAAwCiAKJALMAsgKKAosAEAKMAo0AqQCqAL4AvwDFALQAtQC2ALcAxAKOAo8CkAKRApICkwKUApUClgCEApcAvQAHApgCmQCmApoCmwKcAp0CngKfAqACoQCFAJYCogCnAGECowC4AqQAIAAhAJUAkgCcAB8AlACkAO8A8ACPAJgACADGAA4AkwCaAKUAmQKlAqYCpwKoAqkAuQKqAqsCrAKtAq4CrwKwArECsgKzAF8A6AAjAAkAiACLAIoCtACGAIwAgwK1ArYAQQCCAMICtwK4ArkCugK7ArwCvQK+Ar8CwALBAsICwwLEAsUCxgLHAsgCyQLKAssCzALNAs4CzwLQAtEC0gLTAtQC1QLWAI0A2wDhAN4A2ACOANwAQwDfANoA4ADdANkC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AwADAQMCAwMDBAZBYnJldmUHdW5pMUVBRQd1bmkxRUI2B3VuaTFFQjAHdW5pMUVCMgd1bmkxRUI0B3VuaTAxQ0QHdW5pMUVBNAd1bmkxRUFDB3VuaTFFQTYHdW5pMUVBOAd1bmkxRUFBB3VuaTFFQTAHdW5pMUVBMgdBbWFjcm9uB0FvZ29uZWsKQXJpbmdhY3V0ZQdBRWFjdXRlC0NjaXJjdW1mbGV4CkNkb3RhY2NlbnQGRGNhcm9uBkRjcm9hdAd1bmkxRTBDB3VuaTFFMEUGRWJyZXZlBkVjYXJvbgd1bmkxRUJFB3VuaTFFQzYHdW5pMUVDMAd1bmkxRUMyB3VuaTFFQzQKRWRvdGFjY2VudAd1bmkxRUI4B3VuaTFFQkEHRW1hY3JvbgdFb2dvbmVrB3VuaTFFQkMGR2Nhcm9uC0djaXJjdW1mbGV4DEdjb21tYWFjY2VudApHZG90YWNjZW50B3VuaTFFMjAESGJhcgd1bmkxRTJBC0hjaXJjdW1mbGV4B3VuaTFFMjQCSUoGSWJyZXZlB3VuaTAxQ0YHdW5pMUVDQQd1bmkxRUM4B0ltYWNyb24HSW9nb25lawZJdGlsZGULSmNpcmN1bWZsZXgMS2NvbW1hYWNjZW50BkxhY3V0ZQZMY2Fyb24MTGNvbW1hYWNjZW50BExkb3QHdW5pMUUzNgd1bmkxRTM4B3VuaTFFM0EHdW5pMUU0MgZOYWN1dGUGTmNhcm9uDE5jb21tYWFjY2VudAd1bmkxRTQ0B3VuaTFFNDYDRW5nB3VuaTFFNDgGT2JyZXZlB3VuaTAxRDEHdW5pMUVEMAd1bmkxRUQ4B3VuaTFFRDIHdW5pMUVENAd1bmkxRUQ2B3VuaTFFQ0MHdW5pMUVDRQVPaG9ybgd1bmkxRURBB3VuaTFFRTIHdW5pMUVEQwd1bmkxRURFB3VuaTFFRTANT2h1bmdhcnVtbGF1dAdPbWFjcm9uC09zbGFzaGFjdXRlBlJhY3V0ZQZSY2Fyb24MUmNvbW1hYWNjZW50B3VuaTFFNUEHdW5pMUU1Qwd1bmkxRTVFBlNhY3V0ZQtTY2lyY3VtZmxleAxTY29tbWFhY2NlbnQHdW5pMUU2MAd1bmkxRTYyB3VuaTFFOUUHdW5pMDE4RgRUYmFyBlRjYXJvbgd1bmkwMTYyB3VuaTAyMUEHdW5pMUU2Qwd1bmkxRTZFBlVicmV2ZQd1bmkwMUQzB3VuaTAxRDcHdW5pMDFEOQd1bmkwMURCB3VuaTAxRDUHdW5pMUVFNAd1bmkxRUU2BVVob3JuB3VuaTFFRTgHdW5pMUVGMAd1bmkxRUVBB3VuaTFFRUMHdW5pMUVFRQ1VaHVuZ2FydW1sYXV0B1VtYWNyb24HVW9nb25lawVVcmluZwZVdGlsZGUGV2FjdXRlC1djaXJjdW1mbGV4CVdkaWVyZXNpcwZXZ3JhdmULWWNpcmN1bWZsZXgHdW5pMUU4RQd1bmkxRUY0BllncmF2ZQd1bmkxRUY2B3VuaTFFRjgGWmFjdXRlClpkb3RhY2NlbnQHdW5pMUU5MgZhYnJldmUHdW5pMUVBRgd1bmkxRUI3B3VuaTFFQjEHdW5pMUVCMwd1bmkxRUI1B3VuaTAxQ0UHdW5pMUVBNQd1bmkxRUFEB3VuaTFFQTcHdW5pMUVBOQd1bmkxRUFCB3VuaTFFQTEHdW5pMUVBMwd1bmkwMjUxB2FtYWNyb24HYW9nb25lawphcmluZ2FjdXRlB2FlYWN1dGULY2NpcmN1bWZsZXgKY2RvdGFjY2VudAZkY2Fyb24HdW5pMUUwRAd1bmkxRTBGBmVicmV2ZQZlY2Fyb24HdW5pMUVCRgd1bmkxRUM3B3VuaTFFQzEHdW5pMUVDMwd1bmkxRUM1CmVkb3RhY2NlbnQHdW5pMUVCOQd1bmkxRUJCB2VtYWNyb24HZW9nb25lawd1bmkxRUJEBmdjYXJvbgtnY2lyY3VtZmxleAxnY29tbWFhY2NlbnQKZ2RvdGFjY2VudAd1bmkxRTIxBGhiYXIHdW5pMUUyQgtoY2lyY3VtZmxleAd1bmkxRTI1BmlicmV2ZQd1bmkwMUQwB3VuaTFFQ0IHdW5pMUVDOQJpagdpbWFjcm9uB2lvZ29uZWsGaXRpbGRlB3VuaTAyMzcLamNpcmN1bWZsZXgMa2NvbW1hYWNjZW50DGtncmVlbmxhbmRpYwZsYWN1dGUGbGNhcm9uDGxjb21tYWFjY2VudARsZG90B3VuaTFFMzcHdW5pMUUzOQd1bmkxRTNCB3VuaTFFNDMGbmFjdXRlC25hcG9zdHJvcGhlBm5jYXJvbgxuY29tbWFhY2NlbnQHdW5pMUU0NQd1bmkxRTQ3A2VuZwd1bmkxRTQ5Bm9icmV2ZQd1bmkwMUQyB3VuaTFFRDEHdW5pMUVEOQd1bmkxRUQzB3VuaTFFRDUHdW5pMUVENwd1bmkxRUNEB3VuaTFFQ0YFb2hvcm4HdW5pMUVEQgd1bmkxRUUzB3VuaTFFREQHdW5pMUVERgd1bmkxRUUxDW9odW5nYXJ1bWxhdXQHb21hY3Jvbgtvc2xhc2hhY3V0ZQZyYWN1dGUGcmNhcm9uDHJjb21tYWFjY2VudAd1bmkxRTVCB3VuaTFFNUQHdW5pMUU1RgZzYWN1dGULc2NpcmN1bWZsZXgMc2NvbW1hYWNjZW50B3VuaTFFNjEHdW5pMUU2Mwd1bmkwMjU5BHRiYXIGdGNhcm9uB3VuaTAxNjMHdW5pMDIxQgd1bmkxRTk3B3VuaTFFNkQHdW5pMUU2RgZ1YnJldmUHdW5pMDFENAd1bmkwMUQ4B3VuaTAxREEHdW5pMDFEQwd1bmkwMUQ2B3VuaTFFRTUHdW5pMUVFNwV1aG9ybgd1bmkxRUU5B3VuaTFFRjEHdW5pMUVFQgd1bmkxRUVEB3VuaTFFRUYNdWh1bmdhcnVtbGF1dAd1bWFjcm9uB3VvZ29uZWsFdXJpbmcGdXRpbGRlBndhY3V0ZQt3Y2lyY3VtZmxleAl3ZGllcmVzaXMGd2dyYXZlC3ljaXJjdW1mbGV4B3VuaTFFOEYHdW5pMUVGNQZ5Z3JhdmUHdW5pMUVGNwd1bmkxRUY5BnphY3V0ZQp6ZG90YWNjZW50B3VuaTFFOTMHdW5pMjA3Rgd1bmkwMzk0B3VuaTAzQTkHdW5pMDNCQwd1bmkwRTAxB3VuaTBFMTYHdW5pMEUyMAd1bmkwRTI0DXVuaTBFMjQuc2hvcnQHdW5pMEUyNg11bmkwRTI2LnNob3J0B3VuaTBFMEURZG9DaGFkYXRoYWkuc2hvcnQHdW5pMEUwRhF0b1BhdGFrdGhhaS5zaG9ydBJydV9sYWtraGFuZ3lhb3RoYWkSbHVfbGFra2hhbmd5YW90aGFpB3VuaTBFMEQPeW9ZaW5ndGhhaS5sZXNzB3VuaTBFMDIHdW5pMEUwMwd1bmkwRTBBB3VuaTBFMEIHdW5pMEUwNAd1bmkwRTA1B3VuaTBFMjgHdW5pMEUxNAd1bmkwRTE1B3VuaTBFMTcHdW5pMEUxMQd1bmkwRTE5B3VuaTBFMjEHdW5pMEUwQwd1bmkwRTEzB3VuaTBFMTIHdW5pMEUwNgd1bmkwRTE4B3VuaTBFMjMHdW5pMEUwOAd1bmkwRTI3B3VuaTBFMDcHdW5pMEUxMBB0aG9UaGFudGhhaS5sZXNzB3VuaTBFMDkHdW5pMEUyNQd1bmkwRTJBB3VuaTBFMUEHdW5pMEUxQgd1bmkwRTI5B3VuaTBFMjIHdW5pMEUxQwd1bmkwRTFEB3VuaTBFMUYHdW5pMEUxRQd1bmkwRTJCB3VuaTBFMkMRbG9DaHVsYXRoYWkuc2hvcnQHdW5pMEUyRAd1bmkwRTJFB3VuaTBFMzIHdW5pMEUzMwd1bmkwRTQ1B3VuaTBFMzAHdW5pMEU0MAd1bmkwRTQxB3VuaTBFNDQHdW5pMEU0Mwd1bmkwRTQyB3VuaTIxMEEHdW5pMjE1Mwd1bmkyMTU0CW9uZWVpZ2h0aAx0aHJlZWVpZ2h0aHMLZml2ZWVpZ2h0aHMMc2V2ZW5laWdodGhzB3VuaTIwODAHdW5pMjA4MQd1bmkyMDgyB3VuaTIwODMHdW5pMjA4NAd1bmkyMDg1B3VuaTIwODYHdW5pMjA4Nwd1bmkyMDg4B3VuaTIwODkHdW5pMjA3MAd1bmkwMEI5B3VuaTAwQjIHdW5pMDBCMwd1bmkyMDc0B3VuaTIwNzUHdW5pMjA3Ngd1bmkyMDc3B3VuaTIwNzgHdW5pMjA3OQd1bmkwRTUwB3VuaTBFNTEHdW5pMEU1Mgd1bmkwRTUzB3VuaTBFNTQHdW5pMEU1NQd1bmkwRTU2B3VuaTBFNTcHdW5pMEU1OAd1bmkwRTU5B3VuaTIwOEQHdW5pMjA4RQd1bmkyMDdEB3VuaTIwN0UKZmlndXJlZGFzaAd1bmkyMDE1B3VuaTIwMTAHdW5pMDBBRAd1bmkwRTVBB3VuaTBFNEYHdW5pMEU1Qgd1bmkwRTQ2B3VuaTBFMkYHdW5pMjAwNwd1bmkwMEEwB3VuaTBFM0YHdW5pMjBCNQ1jb2xvbm1vbmV0YXJ5BGRvbmcERXVybwd1bmkyMEIyBGxpcmEHdW5pMjBCQQd1bmkyMEE2BnBlc2V0YQd1bmkyMEIxB3VuaTIwQkQHdW5pMjBCOQd1bmkyMTI2B3VuaTIyMTkHdW5pMjIxNQdhcnJvd3VwCmFycm93cmlnaHQJYXJyb3dkb3duCWFycm93bGVmdAd1bmkyNUM2CWZpbGxlZGJveAd0cmlhZ3VwB3VuaTI1QjYHdHJpYWdkbgd1bmkyNUMwB3VuaTI1QjMHdW5pMjVCNwd1bmkyNUJEB3VuaTI1QzEHdW5pRjhGRgd1bmkyMTE3CWVzdGltYXRlZAd1bmkyMTEzBm1pbnV0ZQZzZWNvbmQHdW5pMjEyMAd1bmkwMzA4B3VuaTAzMDcJZ3JhdmVjb21iCWFjdXRlY29tYgd1bmkwMzBCB3VuaTAzMDIHdW5pMDMwQwd1bmkwMzA2B3VuaTAzMEEJdGlsZGVjb21iB3VuaTAzMDQNaG9va2Fib3ZlY29tYgd1bmkwMzFCDGRvdGJlbG93Y29tYgd1bmkwMzI0B3VuaTAzMjYHdW5pMDMyNwd1bmkwMzI4B3VuaTAzMkUHdW5pMDMzMQd1bmkwMkJDB3VuaTAyQkIHdW5pMDJDOQd1bmkwMkNCB3VuaTAyQkYHdW5pMDJCRQd1bmkwMkNBB3VuaTAyQ0MHdW5pMDJDOAd1bmkwRTMxDnVuaTBFMzEubmFycm93B3VuaTBFNDgNdW5pMEU0OC5zbWFsbAd1bmkwRTQ5DXVuaTBFNDkuc21hbGwOdW5pMEU0OS5uYXJyb3cHdW5pMEU0QQ11bmkwRTRBLnNtYWxsDnVuaTBFNEEubmFycm93B3VuaTBFNEINdW5pMEU0Qi5zbWFsbAd1bmkw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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5f/v/+//9P/8QAA//H/3f/n/7X/tf+1/7X/v/+//8n/v/+//7//3f/d/93/v/+//6v/v//TAAD/tf/d/93/3QAAAAAAAP+/AAD/tf+1AAD/3QAAAAAAAP95/6v/q/+//+cAAP/d/93/3f+D/3n/g/9v/4P/v/+X/4P/l/+X/93/3f/d/7//g/+D/6H/oQAA/4P/0//J/7X/0wAAAAD/lwAA/3n/eQAA/+f/3f/nAAD/5//T/93/+//x/93/3f/n//H/0//T/9P/0//n/78AAP+//7UAAAAA/+f/3f/T/7//0/+/AAAAAAAAAAAAAAAA/90AAAAAAAAAAP/TAAAAAAAAAAAAAAAA/7//8f/n/9P/5wAA/+f/tf/sAAAAAAAAAAAAAAAAAAD/3f/T/90AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+X//H/5wAAAAAAAAAAAAAAAP+c/7X/tf+1/7X/3f+r/6H/of+/AAAAAAAAAAAAAAAA/83/0wAAAAAAAP/d/78AAAAAAAAAAAAA/7r/ugAAAAAAAAAAAAAAAAAAAAAAAP/dAAAAAP/TAAAAAAAAAAAAAAAAAAAAAAAA/90AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/nAAAAAAAAAAAAAAAAAAAAAP/dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+f/3QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/5//dAAAAAP/xAAD/3f/dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/0wAAAAAAAAAAAAAAAAAAAAD/0//TAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AEwAUgBYAF4AZABqAHAAdgB8AIIAiAACAg0CAwACAg4CBAACAg8CBQACAhACBgACAhECBwACAhICCAACAhMCCQACAhQCCgACAhUCCwACAhYCDAACAjsCMwACAjwCNAACAvMC0QACAtQC0wACAtcC1gACAvQC2QACAtwC2wABABEB7wHwAfEB8gHzAfQB9QH2AfcB+AI5AjoC0ALSAtUC2ALaAAYAAAACAAoAHAADAAAAAQAmAAEAPgABAAAADgADAAAAAQAUAAIAHAAsAAEAAAAOAAEAAgEZAScAAgACArACsgAAArQCtwADAAIAAQKkAq8AAAACAAAAAQAIAAEACAABAA4AAQABAeYAAgLoAeUABAAAAAEACAABADYABAAOABgAIgAsAAEABALpAAIC6AABAAQC6gACAugAAQAEAusAAgLoAAEABALsAAIC6AABAAQC0ALSAtUC2AAGAAAACQAYADoAWACWAMwA+gEWATYBXgADAAAAAQASAAEBMgABAAAADgABAAYBsQGzAbUBtwG7AdMAAwABABIAAQEQAAAAAQAAAA4AAQAEAbIBtAG2AbgAAwABABIAAQOkAAAAAQAAAA4AAQAUAs4CzwLQAtIC1QLYAtoC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC9QADAAAAAQASAAEAGAABAAAADgABAAEB4QABAA0CzgLQAtIC1QLYAtoC3QLfAuAC4gLkAuYC6AADAAEAiAABABIAAAABAAAADwABAAwCzgLQAtIC1QLYAtoC3QLgAuIC5ALmAugAAwABAFoAAQASAAAAAQAAAA8AAgABAukC7AAAAAMAAQASAAEC5gAAAAEAAAAQAAEABQLUAtcC3ALzAvQAAwACABQAHgABAsYAAAABAAAAEQABAAMC7QLvAvEAAQADAdkB3QHeAAMAAQASAAEAIgAAAAEAAAARAAEABgLPAt4C4QLjAuUC5wABAAYCzgLdAuAC4gLkAuYAAQAAAAEACAACAA4ABACZAKIBawF0AAEABACXAKEBaQFzAAYAAAACAAoAJAADAAAAAgAUAC4AAQAUAAEAAAASAAEAAQEtAAMAAAACABoAFAABABoAAQAAABIAAQABAiMAAQABAFwAAQAAAAEACAACAEQADAIDAgQCBQIGAgcCCAIJAgoCCwIMAjMCNAABAAAAAQAIAAIAHgAMAg0CDgIPAhACEQISAhMCFAIVAhYCOwI8AAIAAgHvAfgAAAI5AjoACgAEAAAAAQAIAAEAdAAFABAAOgBGAFwAaAAEAAoAEgAaACIB+gADAjEB8QH7AAMCMQHyAf0AAwIxAfMB/wADAjEB9wABAAQB/AADAjEB8gACAAYADgH+AAMCMQHzAgAAAwIxAfcAAQAEAgEAAwIxAfcAAQAEAgIAAwIxAfcAAQAFAfAB8QHyAfQB9gAGAAAAAgAKACQAAwABACwAAQASAAAAAQAAABMAAQACAAQA0gADAAEAEgABABwAAAABAAAAEwACAAEB7wH4AAAAAQACAHABQgAEAAAAAQAIAAEAMgADAAwAHgAoAAIABgAMAaUAAgEZAaYAAgEtAAEABAG5AAIB5wABAAQBugACAecAAQADAQwBsQGzAAEAAAABAAgAAQAGAAEAAQARARkBJwGxAbMBtQG3AbsB0wHhAtAC0gLVAtgC2gLtAu8C8QABAAAAAQAIAAIAJgAQAs8C8wLUAtcC9ALcAt4C4QLjAuUC5wL1AvYC9wL4AvkAAQAQAs4C0ALSAtUC2ALaAt0C4ALiAuQC5gLoAukC6gLrAuwAAQAAAAEACAABAAYAAQABAAUC0ALSAtUC2ALaAAEAAAABAAgAAgAcAAsCzwLzAtQC1wL0AtwC3gLhAuMC5QLnAAEACwLOAtAC0gLVAtgC2gLdAuAC4gLkAuYABAAAAAEACAABAB4AAgAKABQAAQAEAGAAAgIjAAEABAExAAICIwABAAIAXAEtAAEAAAABAAgAAgAOAAQBpwGoAacBqAABAAQABABwANIBQgAA"; diff --git a/photon-client/src/assets/images/ChArUco_Marker8x8.png b/photon-client/src/assets/images/ChArUco_Marker8x8.png new file mode 100644 index 0000000..d092715 Binary files /dev/null and b/photon-client/src/assets/images/ChArUco_Marker8x8.png differ diff --git a/photon-client/src/assets/images/loading.svg b/photon-client/src/assets/images/loading.svg new file mode 100644 index 0000000..a471c1d --- /dev/null +++ b/photon-client/src/assets/images/loading.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/photon-client/src/assets/images/logoLarge.svg b/photon-client/src/assets/images/logoLarge.svg new file mode 100644 index 0000000..d6c2247 --- /dev/null +++ b/photon-client/src/assets/images/logoLarge.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/photon-client/src/assets/images/logoMono.png b/photon-client/src/assets/images/logoMono.png new file mode 100644 index 0000000..dc95b7e Binary files /dev/null and b/photon-client/src/assets/images/logoMono.png differ diff --git a/photon-client/src/assets/images/logoSmall.svg b/photon-client/src/assets/images/logoSmall.svg new file mode 100644 index 0000000..f528a70 --- /dev/null +++ b/photon-client/src/assets/images/logoSmall.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/photon-client/src/assets/images/notfound.webp b/photon-client/src/assets/images/notfound.webp new file mode 100644 index 0000000..6de4690 Binary files /dev/null and b/photon-client/src/assets/images/notfound.webp differ diff --git a/photon-client/src/assets/styles/variables.scss b/photon-client/src/assets/styles/variables.scss new file mode 100644 index 0000000..a3130f8 --- /dev/null +++ b/photon-client/src/assets/styles/variables.scss @@ -0,0 +1,24 @@ +@import "@fontsource/prompt"; + +$default-font: "Prompt", sans-serif !default; +$body-font-family: $default-font; +$heading-font-family: $default-font; + +.v-application { + font-family: $default-font !important; +} + +.v-row-group__header { + background: #005281 !important; +} +.theme--dark.v-data-table + > .v-data-table__wrapper + > table + > tbody + > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) { + background: #005281 !important; +} + +.v-card__title { + word-break: break-word !important; +} diff --git a/photon-client/src/components/app/photon-3d-visualizer.vue b/photon-client/src/components/app/photon-3d-visualizer.vue new file mode 100644 index 0000000..caa3070 --- /dev/null +++ b/photon-client/src/components/app/photon-3d-visualizer.vue @@ -0,0 +1,206 @@ + + + diff --git a/photon-client/src/components/app/photon-camera-stream.vue b/photon-client/src/components/app/photon-camera-stream.vue new file mode 100644 index 0000000..03cca11 --- /dev/null +++ b/photon-client/src/components/app/photon-camera-stream.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/photon-client/src/components/app/photon-error-snackbar.vue b/photon-client/src/components/app/photon-error-snackbar.vue new file mode 100644 index 0000000..950f49b --- /dev/null +++ b/photon-client/src/components/app/photon-error-snackbar.vue @@ -0,0 +1,16 @@ + + + diff --git a/photon-client/src/components/app/photon-log-entry.vue b/photon-client/src/components/app/photon-log-entry.vue new file mode 100644 index 0000000..0f2a74e --- /dev/null +++ b/photon-client/src/components/app/photon-log-entry.vue @@ -0,0 +1,24 @@ + + + diff --git a/photon-client/src/components/app/photon-log-view.vue b/photon-client/src/components/app/photon-log-view.vue new file mode 100644 index 0000000..d178181 --- /dev/null +++ b/photon-client/src/components/app/photon-log-view.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/photon-client/src/components/app/photon-sidebar.vue b/photon-client/src/components/app/photon-sidebar.vue new file mode 100644 index 0000000..ac8636d --- /dev/null +++ b/photon-client/src/components/app/photon-sidebar.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/photon-client/src/components/cameras/CameraCalibrationCard.vue b/photon-client/src/components/cameras/CameraCalibrationCard.vue new file mode 100644 index 0000000..5febbbb --- /dev/null +++ b/photon-client/src/components/cameras/CameraCalibrationCard.vue @@ -0,0 +1,582 @@ + + + + + diff --git a/photon-client/src/components/cameras/CameraCalibrationInfoCard.vue b/photon-client/src/components/cameras/CameraCalibrationInfoCard.vue new file mode 100644 index 0000000..52f5eb9 --- /dev/null +++ b/photon-client/src/components/cameras/CameraCalibrationInfoCard.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/photon-client/src/components/cameras/CameraControlCard.vue b/photon-client/src/components/cameras/CameraControlCard.vue new file mode 100644 index 0000000..e94bdf3 --- /dev/null +++ b/photon-client/src/components/cameras/CameraControlCard.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/photon-client/src/components/cameras/CameraSettingsCard.vue b/photon-client/src/components/cameras/CameraSettingsCard.vue new file mode 100644 index 0000000..5d72dc9 --- /dev/null +++ b/photon-client/src/components/cameras/CameraSettingsCard.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/photon-client/src/components/cameras/CamerasView.vue b/photon-client/src/components/cameras/CamerasView.vue new file mode 100644 index 0000000..1e1b897 --- /dev/null +++ b/photon-client/src/components/cameras/CamerasView.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/photon-client/src/components/common/pv-icon.vue b/photon-client/src/components/common/pv-icon.vue new file mode 100644 index 0000000..6122a1c --- /dev/null +++ b/photon-client/src/components/common/pv-icon.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/photon-client/src/components/common/pv-input.vue b/photon-client/src/components/common/pv-input.vue new file mode 100644 index 0000000..282e95e --- /dev/null +++ b/photon-client/src/components/common/pv-input.vue @@ -0,0 +1,73 @@ + + + diff --git a/photon-client/src/components/common/pv-number-input.vue b/photon-client/src/components/common/pv-number-input.vue new file mode 100644 index 0000000..8b359a7 --- /dev/null +++ b/photon-client/src/components/common/pv-number-input.vue @@ -0,0 +1,56 @@ + + + diff --git a/photon-client/src/components/common/pv-radio.vue b/photon-client/src/components/common/pv-radio.vue new file mode 100644 index 0000000..8777f71 --- /dev/null +++ b/photon-client/src/components/common/pv-radio.vue @@ -0,0 +1,51 @@ + + + diff --git a/photon-client/src/components/common/pv-range-slider.vue b/photon-client/src/components/common/pv-range-slider.vue new file mode 100644 index 0000000..cffdce4 --- /dev/null +++ b/photon-client/src/components/common/pv-range-slider.vue @@ -0,0 +1,118 @@ + + + diff --git a/photon-client/src/components/common/pv-select.vue b/photon-client/src/components/common/pv-select.vue new file mode 100644 index 0000000..a681914 --- /dev/null +++ b/photon-client/src/components/common/pv-select.vue @@ -0,0 +1,72 @@ + + + diff --git a/photon-client/src/components/common/pv-slider.vue b/photon-client/src/components/common/pv-slider.vue new file mode 100644 index 0000000..21a5374 --- /dev/null +++ b/photon-client/src/components/common/pv-slider.vue @@ -0,0 +1,85 @@ + + + diff --git a/photon-client/src/components/common/pv-switch.vue b/photon-client/src/components/common/pv-switch.vue new file mode 100644 index 0000000..d311580 --- /dev/null +++ b/photon-client/src/components/common/pv-switch.vue @@ -0,0 +1,42 @@ + + + diff --git a/photon-client/src/components/common/pv-tooltipped-label.vue b/photon-client/src/components/common/pv-tooltipped-label.vue new file mode 100644 index 0000000..d59f9a2 --- /dev/null +++ b/photon-client/src/components/common/pv-tooltipped-label.vue @@ -0,0 +1,17 @@ + + + diff --git a/photon-client/src/components/dashboard/CameraAndPipelineSelectCard.vue b/photon-client/src/components/dashboard/CameraAndPipelineSelectCard.vue new file mode 100644 index 0000000..8f5f3b3 --- /dev/null +++ b/photon-client/src/components/dashboard/CameraAndPipelineSelectCard.vue @@ -0,0 +1,423 @@ + + + diff --git a/photon-client/src/components/dashboard/CamerasCard.vue b/photon-client/src/components/dashboard/CamerasCard.vue new file mode 100644 index 0000000..eed2e75 --- /dev/null +++ b/photon-client/src/components/dashboard/CamerasCard.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/photon-client/src/components/dashboard/ConfigOptions.vue b/photon-client/src/components/dashboard/ConfigOptions.vue new file mode 100644 index 0000000..c1023f2 --- /dev/null +++ b/photon-client/src/components/dashboard/ConfigOptions.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/photon-client/src/components/dashboard/StreamConfigCard.vue b/photon-client/src/components/dashboard/StreamConfigCard.vue new file mode 100644 index 0000000..307a380 --- /dev/null +++ b/photon-client/src/components/dashboard/StreamConfigCard.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/photon-client/src/components/dashboard/tabs/AprilTagTab.vue b/photon-client/src/components/dashboard/tabs/AprilTagTab.vue new file mode 100644 index 0000000..a7b017d --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/AprilTagTab.vue @@ -0,0 +1,93 @@ + + + diff --git a/photon-client/src/components/dashboard/tabs/ArucoTab.vue b/photon-client/src/components/dashboard/tabs/ArucoTab.vue new file mode 100644 index 0000000..97573d3 --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/ArucoTab.vue @@ -0,0 +1,83 @@ + + + diff --git a/photon-client/src/components/dashboard/tabs/ContoursTab.vue b/photon-client/src/components/dashboard/tabs/ContoursTab.vue new file mode 100644 index 0000000..ffc9616 --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/ContoursTab.vue @@ -0,0 +1,232 @@ + + + diff --git a/photon-client/src/components/dashboard/tabs/InputTab.vue b/photon-client/src/components/dashboard/tabs/InputTab.vue new file mode 100644 index 0000000..1f99ebc --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/InputTab.vue @@ -0,0 +1,177 @@ + + + diff --git a/photon-client/src/components/dashboard/tabs/Map3DTab.vue b/photon-client/src/components/dashboard/tabs/Map3DTab.vue new file mode 100644 index 0000000..5ed12bf --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/Map3DTab.vue @@ -0,0 +1,23 @@ + + + diff --git a/photon-client/src/components/dashboard/tabs/ObjectDetectionTab.vue b/photon-client/src/components/dashboard/tabs/ObjectDetectionTab.vue new file mode 100644 index 0000000..5128f1d --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/ObjectDetectionTab.vue @@ -0,0 +1,104 @@ + + + diff --git a/photon-client/src/components/dashboard/tabs/OutputTab.vue b/photon-client/src/components/dashboard/tabs/OutputTab.vue new file mode 100644 index 0000000..3c3c841 --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/OutputTab.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/photon-client/src/components/dashboard/tabs/PnPTab.vue b/photon-client/src/components/dashboard/tabs/PnPTab.vue new file mode 100644 index 0000000..e61179a --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/PnPTab.vue @@ -0,0 +1,47 @@ + + + diff --git a/photon-client/src/components/dashboard/tabs/TargetsTab.vue b/photon-client/src/components/dashboard/tabs/TargetsTab.vue new file mode 100644 index 0000000..f37bcc7 --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/TargetsTab.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/photon-client/src/components/dashboard/tabs/ThresholdTab.vue b/photon-client/src/components/dashboard/tabs/ThresholdTab.vue new file mode 100644 index 0000000..81afbb3 --- /dev/null +++ b/photon-client/src/components/dashboard/tabs/ThresholdTab.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/photon-client/src/components/settings/ApriltagControlCard.vue b/photon-client/src/components/settings/ApriltagControlCard.vue new file mode 100644 index 0000000..65e1a58 --- /dev/null +++ b/photon-client/src/components/settings/ApriltagControlCard.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/photon-client/src/components/settings/DeviceControlCard.vue b/photon-client/src/components/settings/DeviceControlCard.vue new file mode 100644 index 0000000..31c25df --- /dev/null +++ b/photon-client/src/components/settings/DeviceControlCard.vue @@ -0,0 +1,451 @@ + + + + + diff --git a/photon-client/src/components/settings/LEDControlCard.vue b/photon-client/src/components/settings/LEDControlCard.vue new file mode 100644 index 0000000..646c809 --- /dev/null +++ b/photon-client/src/components/settings/LEDControlCard.vue @@ -0,0 +1,21 @@ + + + diff --git a/photon-client/src/components/settings/MetricsCard.vue b/photon-client/src/components/settings/MetricsCard.vue new file mode 100644 index 0000000..53e14d2 --- /dev/null +++ b/photon-client/src/components/settings/MetricsCard.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/photon-client/src/components/settings/NetworkingCard.vue b/photon-client/src/components/settings/NetworkingCard.vue new file mode 100644 index 0000000..c957116 --- /dev/null +++ b/photon-client/src/components/settings/NetworkingCard.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/photon-client/src/lib/AutoReconnectingWebsocket.ts b/photon-client/src/lib/AutoReconnectingWebsocket.ts new file mode 100644 index 0000000..fdd0abb --- /dev/null +++ b/photon-client/src/lib/AutoReconnectingWebsocket.ts @@ -0,0 +1,97 @@ +import { decode, encode } from "@msgpack/msgpack"; +import type { IncomingWebsocketData } from "@/types/WebsocketDataTypes"; + +/** + * {@link WebSocket} wrapper class that automatically reconnects to the provided host address if the connection was closed by the remote host or a connection failure. + * Data sent and received by the Websocket is automatically encoded and decoded using msgpack. + */ +export class AutoReconnectingWebsocket { + private readonly serverAddress: string | URL; + private websocket: WebSocket | null | undefined; + + private readonly onConnect: () => void; + private readonly onData: (data: IncomingWebsocketData) => void; + private readonly onDisconnect: () => void; + + /** + * Create an AutoReconnectingWebsocket + * + * @param serverAddress address of the websocket + * @param onConnect action to run on websocket connection (when the websocket changes to the OPEN state) + * @param onData decoded websocket message data consumer. The data is automatically decoded by msgpack. + * @param onDisconnect action to run on websocket disconnection (when the websocket changes to the CLOSED state) + */ + constructor( + serverAddress: string | URL, + onConnect: () => void, + onData: (data: IncomingWebsocketData) => void, + onDisconnect: () => void + ) { + this.serverAddress = serverAddress; + + this.onConnect = onConnect; + this.onData = onData; + this.onDisconnect = onDisconnect; + + this.initializeWebsocket(); + } + + /** + * Send data over the websocket. This is a no-op if the websocket is not in the OPEN state. + * + * @param data data to send + * @param encodeData whether or not to encode the data using msgpack (defaults to true) + * @see isConnected + * + */ + send(data, encodeData = true) { + // Only send data if the websocket is open + if (this.isConnected()) { + if (encodeData) { + this.websocket?.send(encode(data)); + } else { + this.websocket?.send(data); + } + } + } + + /** + * Check if the WebSocket is OPEN and connected + */ + isConnected(): boolean { + return this.websocket === null || this.websocket === undefined + ? false + : this.websocket.readyState === WebSocket.OPEN; + } + + /** + * Handles the creation of the websocket and the binding of the action consumers. + * + * @private + */ + private initializeWebsocket() { + this.websocket = new WebSocket(this.serverAddress); + this.websocket.binaryType = "arraybuffer"; + + this.websocket.onopen = () => { + console.debug("[WebSocket] Websocket Open"); + this.onConnect(); + }; + this.websocket.onmessage = (event: MessageEvent) => { + this.onData(decode(event.data) as IncomingWebsocketData); + }; + this.websocket.onclose = (event: CloseEvent) => { + this.onDisconnect(); + + this.websocket = null; + + console.info("[WebSocket] The WebSocket was closed. Will reattempt in 500 milliseconds.", event.reason); + setTimeout(this.initializeWebsocket.bind(this), 500); + }; + this.websocket.onerror = () => { + this.websocket?.close(); + }; + + console.debug(`[WebSocket] Attempting to initialize Websocket connection to ${this.serverAddress}`); + } +} diff --git a/photon-client/src/lib/ColorPicker.ts b/photon-client/src/lib/ColorPicker.ts new file mode 100644 index 0000000..2ee1449 --- /dev/null +++ b/photon-client/src/lib/ColorPicker.ts @@ -0,0 +1,101 @@ +export type HSV = [number, number, number]; +export type RGBA = [number, number, number, number] | Uint8ClampedArray; + +export class ColorPicker { + public hsvData: HSV; + + constructor(pixelData: RGBA) { + this.hsvData = this.RGBtoHSV(pixelData); + } + + public selectedColorRange() { + return this.widenRange([[...this.hsvData], [...this.hsvData]]); + } + + public expandColorRange(currentRange: [HSV, HSV]) { + const widenedHSV = this.widenRange([[...this.hsvData], [...this.hsvData]]); + return this.createRange(currentRange.concat(widenedHSV)); + } + + public shrinkColorRange(currentRange: [HSV, HSV]) { + const widenedHSV = this.widenRange([[...this.hsvData], [...this.hsvData]]); + + //Tries to shrink the lower part of to widened HSV + if (!this.shrinkRange(currentRange, widenedHSV[0])) { + //If the prev attempt failed, try to shrink the higher part of to widened HSV + this.shrinkRange(currentRange, widenedHSV[1]); + } + + return currentRange; + } + + private createRange(range: HSV[]): [HSV, HSV] { + const newRange: [HSV, HSV] = [ + [0, 0, 0], + [0, 0, 0] + ]; + for (let i = 0; i < 3; i++) { + newRange[0][i] = range[0][i]; + newRange[1][i] = range[0][i]; + for (let j = range.length - 1; j >= 0; j--) { + newRange[0][i] = Math.min(range[j][i], newRange[0][i]); + newRange[1][i] = Math.max(range[j][i], newRange[1][i]); + } + } + return newRange; + } + + private widenRange(range: [HSV, HSV]): [HSV, HSV] { + const expanded: [HSV, HSV] = [ + [0, 0, 0], + [0, 0, 0] + ]; + for (let i = 0; i < 3; i++) { + //Expanding the range by 10 + expanded[0][i] = Math.max(0, range[0][i] - 10); + expanded[1][i] = Math.min(255, range[1][i] + 10); + } + expanded[1][0] = Math.min(180, expanded[1][0]); //h is up to 180 + return expanded; + } + + private shrinkRange(range: [HSV, HSV], color: HSV): boolean { + for (let i = 0; i < color.length; i++) { + if (!(range[0][i] <= color[i] && color[i] <= range[1][i])) return false; + } + + for (let i = 0; i < color.length; i++) { + if (color[i] - range[0][i] < range[1][i] - color[i]) { + //shrink from min side + range[0][i] = Math.min(range[0][i] + 10, range[1][i]); + } else { + //shrink from max side + range[1][i] = Math.max(range[1][i] - 10, range[0][i]); + } + } + + return true; + } + + private RGBtoHSV(rgba: RGBA): HSV { + // Normalize RGB ranges + let r = rgba[0], + g = rgba[1], + b = rgba[2]; + r = r / 255; + g = g / 255; + b = b / 255; + + const minRGB = Math.min(r, Math.min(g, b)); + const maxRGB = Math.max(r, Math.max(g, b)); + const d = r === minRGB ? g - b : b === minRGB ? r - g : b - r; + const h = r === minRGB ? 3 : b === minRGB ? 1 : 5; + let H = 30 * (h - d / (maxRGB - minRGB)); + let S = (255 * (maxRGB - minRGB)) / maxRGB; + let V = 255 * maxRGB; + if (isNaN(H)) H = 0; + if (isNaN(S)) S = 0; + if (isNaN(V)) V = 0; + return [Math.round(H), Math.round(S), Math.round(V)]; + } +} diff --git a/photon-client/src/lib/MathUtils.ts b/photon-client/src/lib/MathUtils.ts new file mode 100644 index 0000000..7252faa --- /dev/null +++ b/photon-client/src/lib/MathUtils.ts @@ -0,0 +1,13 @@ +export const mean = (values: number[]): number | undefined => { + if (values.length === 0) return undefined; + return values.reduce((acc, num) => acc + num, 0) / values.length; +}; + +export const angleModulus = (valueRad: number): number => { + while (valueRad < -Math.PI) valueRad += Math.PI * 2; + while (valueRad > Math.PI) valueRad -= Math.PI * 2; + return valueRad; +}; + +export const toDeg = (val: number) => val * (180.0 / Math.PI); +export const toRad = (val: number) => val * (Math.PI / 180.0); diff --git a/photon-client/src/lib/PhotonUtils.ts b/photon-client/src/lib/PhotonUtils.ts new file mode 100644 index 0000000..9b31aa0 --- /dev/null +++ b/photon-client/src/lib/PhotonUtils.ts @@ -0,0 +1,20 @@ +import type { Resolution } from "@/types/SettingTypes"; + +export const resolutionsAreEqual = (a: Resolution, b: Resolution) => { + return a.height === b.height && a.width === b.width; +}; + +export const getResolutionString = (resolution: Resolution): string => `${resolution.width}x${resolution.height}`; + +export const parseJsonFile = async >(file: File): Promise => { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = (event) => { + const target: FileReader | null = event.target; + if (target === null) reject(); + else resolve(JSON.parse(target.result as string) as T); + }; + fileReader.onerror = (error) => reject(error); + fileReader.readAsText(file); + }); +}; diff --git a/photon-client/src/main.ts b/photon-client/src/main.ts new file mode 100644 index 0000000..5706c96 --- /dev/null +++ b/photon-client/src/main.ts @@ -0,0 +1,43 @@ +import Vue from "vue"; +import App from "@/App.vue"; + +import { createPinia, PiniaVuePlugin } from "pinia"; +import router from "@/router"; +import vuetify from "@/plugins/vuetify"; +import axios from "axios"; + +type PhotonClientRuntimeMode = "production" | "development" | "local-network-development"; +const runtimeMode: PhotonClientRuntimeMode = process.env.NODE_ENV as PhotonClientRuntimeMode; + +let backendHost: string; +let backendHostname: string; +switch (runtimeMode as PhotonClientRuntimeMode) { + case "development": + backendHost = `${location.hostname}:5800`; + backendHostname = location.hostname; + break; + case "local-network-development": + backendHost = "photonvision.local:5800"; + backendHostname = "photonvision.local"; + break; + case "production": + backendHost = location.host; + backendHostname = location.hostname; + break; +} + +axios.defaults.baseURL = `http://${backendHost}/api`; + +// Handle Plugins +Vue.use(PiniaVuePlugin); + +new Vue({ + router, + vuetify, + pinia: createPinia(), + provide: { + backendHost: backendHost, + backendHostname: backendHostname + }, + render: (h) => h(App) +}).$mount("#app"); diff --git a/photon-client/src/plugins/vuetify.ts b/photon-client/src/plugins/vuetify.ts new file mode 100644 index 0000000..8a358c0 --- /dev/null +++ b/photon-client/src/plugins/vuetify.ts @@ -0,0 +1,44 @@ +import Vue from "vue"; +import Vuetify from "vuetify"; +import "vuetify/dist/vuetify.min.css"; +import "@mdi/font/css/materialdesignicons.css"; +import type { VuetifyThemeVariant } from "vuetify/types/services/theme"; + +Vue.use(Vuetify); + +const darkTheme: VuetifyThemeVariant = Object.freeze({ + primary: "#006492", + secondary: "#39A4D5", + accent: "#FFD843", + background: "#232C37", + error: "#FF5252", + info: "#2196F3", + success: "#4CAF50", + warning: "#FFC107" +}); + +const lightTheme: VuetifyThemeVariant = Object.freeze({ + primary: "#006492", + secondary: "#39A4D5", + accent: "#FFD843", + background: "#232C37", + error: "#FF5252", + info: "#2196F3", + success: "#4CAF50", + warning: "#FFC107" +}); + +export default new Vuetify({ + theme: { + themes: { + light: lightTheme, + dark: darkTheme + } + }, + breakpoint: { + thresholds: { + md: 1460, + lg: 2000 + } + } +}); diff --git a/photon-client/src/router/index.ts b/photon-client/src/router/index.ts new file mode 100644 index 0000000..9e3e78f --- /dev/null +++ b/photon-client/src/router/index.ts @@ -0,0 +1,49 @@ +import Vue from "vue"; +import VueRouter from "vue-router"; + +import DashboardView from "@/views/DashboardView.vue"; +import CameraSettingsView from "@/views/CameraSettingsView.vue"; +import GeneralSettingsView from "@/views/GeneralSettingsView.vue"; +import DocsView from "@/views/DocsView.vue"; +import NotFoundView from "@/views/NotFoundView.vue"; + +Vue.use(VueRouter); + +const router = new VueRouter({ + // Using HTML5 History Mode is problematic with Javalin because each route is treated as a server endpoint which causes Javalin to return a 404 error before being redirected to the UI. + // mode: "history", + base: import.meta.env.BASE_URL, + routes: [ + { + path: "/", + redirect: "/dashboard" + }, + { + path: "/dashboard", + name: "Dashboard", + component: DashboardView + }, + { + path: "/cameras", + name: "Cameras", + component: CameraSettingsView + }, + { + path: "/settings", + name: "Settings", + component: GeneralSettingsView + }, + { + path: "/docs", + name: "Docs", + component: DocsView + }, + { + path: "*", + name: "NotFound", + component: NotFoundView + } + ] +}); + +export default router; diff --git a/photon-client/src/stores/StateStore.ts b/photon-client/src/stores/StateStore.ts new file mode 100644 index 0000000..5062187 --- /dev/null +++ b/photon-client/src/stores/StateStore.ts @@ -0,0 +1,148 @@ +import { defineStore } from "pinia"; +import type { LogMessage } from "@/types/SettingTypes"; +import type { AutoReconnectingWebsocket } from "@/lib/AutoReconnectingWebsocket"; +import type { MultitagResult, PipelineResult } from "@/types/PhotonTrackingTypes"; +import type { + WebsocketCalibrationData, + WebsocketLogMessage, + WebsocketNTUpdate, + WebsocketPipelineResultUpdate +} from "@/types/WebsocketDataTypes"; + +export interface NTConnectionStatus { + connected: boolean; + address?: string; + clients?: number; +} + +interface StateStore { + backendConnected: boolean; + websocket?: AutoReconnectingWebsocket; + ntConnectionStatus: NTConnectionStatus; + showLogModal: boolean; + sidebarFolded: boolean; + logMessages: LogMessage[]; + currentCameraIndex: number; + + backendResults: Record; + multitagResultBuffer: Record; + + colorPickingMode: boolean; + + calibrationData: { + imageCount: number; + videoFormatIndex: number; + minimumImageCount: number; + hasEnoughImages: boolean; + }; + + snackbarData: { + show: boolean; + message: string; + color: string; + timeout: number; + }; +} + +export const useStateStore = defineStore("state", { + state: (): StateStore => { + return { + backendConnected: false, + websocket: undefined, + ntConnectionStatus: { + connected: false + }, + showLogModal: false, + // Ignored if the display is too small + sidebarFolded: + localStorage.getItem("sidebarFolded") === null ? false : localStorage.getItem("sidebarFolded") === "true", + logMessages: [], + currentCameraIndex: 0, + + backendResults: {}, + multitagResultBuffer: {}, + + colorPickingMode: false, + + calibrationData: { + imageCount: 0, + videoFormatIndex: 0, + minimumImageCount: 12, + hasEnoughImages: false + }, + + snackbarData: { + show: false, + message: "No Message", + color: "info", + timeout: 2000 + } + }; + }, + getters: { + currentPipelineResults(): PipelineResult | undefined { + return this.backendResults[this.currentCameraIndex.toString()]; + }, + currentMultitagBuffer(): MultitagResult[] | undefined { + if (!this.multitagResultBuffer[this.currentCameraIndex]) this.multitagResultBuffer[this.currentCameraIndex] = []; + return this.multitagResultBuffer[this.currentCameraIndex]; + } + }, + actions: { + setSidebarFolded(value: boolean) { + this.sidebarFolded = value; + localStorage.setItem("sidebarFolded", Boolean(value).toString()); + }, + addLogFromWebsocket(data: WebsocketLogMessage) { + this.logMessages.push({ + level: data.logMessage.logLevel, + message: data.logMessage.logMessage, + timestamp: new Date() + }); + }, + updateNTConnectionStatusFromWebsocket(data: WebsocketNTUpdate) { + this.ntConnectionStatus = { + connected: data.connected, + address: data.address, + clients: data.clients + }; + }, + updateBackendResultsFromWebsocket(data: WebsocketPipelineResultUpdate) { + this.backendResults = { + ...this.backendResults, + ...data + }; + + for (const key in data) { + const multitagRes = data[key].multitagResult; + + if (multitagRes) { + if (!this.multitagResultBuffer[key]) { + this.multitagResultBuffer[key] = []; + } + + this.multitagResultBuffer[key].push(multitagRes); + if (this.multitagResultBuffer[key].length > 100) { + this.multitagResultBuffer[key].shift(); + } + } + } + }, + updateCalibrationStateValuesFromWebsocket(data: WebsocketCalibrationData) { + this.calibrationData = { + imageCount: data.count, + videoFormatIndex: data.videoModeIndex, + minimumImageCount: data.minCount, + hasEnoughImages: data.hasEnough + }; + }, + showSnackbarMessage(data: { message: string; color: string; timeout?: number }) { + this.snackbarData = { + show: true, + message: data.message, + color: data.color, + timeout: data.timeout || 2000 + }; + } + } +}); diff --git a/photon-client/src/stores/settings/CameraSettingsStore.ts b/photon-client/src/stores/settings/CameraSettingsStore.ts new file mode 100644 index 0000000..5ab00ea --- /dev/null +++ b/photon-client/src/stores/settings/CameraSettingsStore.ts @@ -0,0 +1,453 @@ +import { defineStore } from "pinia"; +import type { + CalibrationTagFamilies, + CalibrationBoardTypes, + CameraCalibrationResult, + CameraSettings, + CameraSettingsChangeRequest, + Resolution, + RobotOffsetType, + VideoFormat +} from "@/types/SettingTypes"; +import { PlaceholderCameraSettings } from "@/types/SettingTypes"; +import { useStateStore } from "@/stores/StateStore"; +import type { WebsocketCameraSettingsUpdate } from "@/types/WebsocketDataTypes"; +import { WebsocketPipelineType } from "@/types/WebsocketDataTypes"; +import type { ActiveConfigurablePipelineSettings, ActivePipelineSettings, PipelineType } from "@/types/PipelineTypes"; +import axios from "axios"; +import { resolutionsAreEqual } from "@/lib/PhotonUtils"; + +interface CameraSettingsStore { + cameras: CameraSettings[]; +} + +export const useCameraSettingsStore = defineStore("cameraSettings", { + state: (): CameraSettingsStore => ({ + cameras: [PlaceholderCameraSettings] + }), + getters: { + // TODO update types to update this value being undefined. This would be a decently large change. + currentCameraSettings(): CameraSettings { + return this.cameras[useStateStore().currentCameraIndex]; + }, + currentPipelineSettings(): ActivePipelineSettings { + return this.currentCameraSettings.pipelineSettings; + }, + currentPipelineType(): PipelineType { + return this.currentPipelineSettings.pipelineType; + }, + // This method only exists due to just how lazy I am and my dislike of consolidating the pipeline type enums (which mind you, suck as is) + currentWebsocketPipelineType(): WebsocketPipelineType { + return this.currentPipelineType - 2; + }, + currentVideoFormat(): VideoFormat { + return this.currentCameraSettings.validVideoFormats[this.currentPipelineSettings.cameraVideoModeIndex]; + }, + isCurrentVideoFormatCalibrated(): boolean { + return this.currentCameraSettings.completeCalibrations.some((v) => + resolutionsAreEqual(v.resolution, this.currentVideoFormat.resolution) + ); + }, + cameraNames(): string[] { + return this.cameras.map((c) => c.nickname); + }, + cameraUniqueNames(): string[] { + return this.cameras.map((c) => c.nickname); + }, + currentCameraName(): string { + return this.cameraNames[useStateStore().currentCameraIndex]; + }, + pipelineNames(): string[] { + return this.currentCameraSettings.pipelineNicknames; + }, + currentPipelineName(): string { + return this.pipelineNames[useStateStore().currentCameraIndex]; + }, + isDriverMode(): boolean { + return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.DriverMode; + }, + isCalibrationMode(): boolean { + return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.Calib3d; + }, + isCSICamera(): boolean { + return this.currentCameraSettings.isCSICamera; + }, + minExposureRaw(): number { + return this.currentCameraSettings.minExposureRaw; + }, + maxExposureRaw(): number { + return this.currentCameraSettings.maxExposureRaw; + }, + minWhiteBalanceTemp(): number { + return this.currentCameraSettings.minWhiteBalanceTemp; + }, + maxWhiteBalanceTemp(): number { + return this.currentCameraSettings.maxWhiteBalanceTemp; + } + }, + actions: { + updateCameraSettingsFromWebsocket(data: WebsocketCameraSettingsUpdate[]) { + const configuredCameras = data.map((d) => ({ + nickname: d.nickname, + uniqueName: d.uniqueName, + fov: { + value: d.fov, + managedByVendor: !d.isFovConfigurable + }, + stream: { + inputPort: d.inputStreamPort, + outputPort: d.outputStreamPort + }, + validVideoFormats: Object.entries(d.videoFormatList) + .sort(([firstKey], [secondKey]) => parseInt(firstKey) - parseInt(secondKey)) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(([k, v], i) => ({ + resolution: { + width: v.width, + height: v.height + }, + fps: v.fps, + pixelFormat: v.pixelFormat, + index: v.index || i, + diagonalFOV: v.diagonalFOV, + horizontalFOV: v.horizontalFOV, + verticalFOV: v.verticalFOV, + standardDeviation: v.standardDeviation, + mean: v.mean + })), + completeCalibrations: d.calibrations, + isCSICamera: d.isCSICamera, + minExposureRaw: d.minExposureRaw, + maxExposureRaw: d.maxExposureRaw, + pipelineNicknames: d.pipelineNicknames, + currentPipelineIndex: d.currentPipelineIndex, + pipelineSettings: d.currentPipelineSettings, + cameraQuirks: d.cameraQuirks, + minWhiteBalanceTemp: d.minWhiteBalanceTemp, + maxWhiteBalanceTemp: d.maxWhiteBalanceTemp + })); + this.cameras = configuredCameras.length > 0 ? configuredCameras : [PlaceholderCameraSettings]; + }, + /** + * Update the configurable camera settings. + * + * @param data camera settings to save. + * @param cameraIndex the index of the camera. + */ + updateCameraSettings(data: CameraSettingsChangeRequest, cameraIndex: number = useStateStore().currentCameraIndex) { + // The camera settings endpoint doesn't actually require all data, instead, it needs key data such as the FOV + const payload = { + settings: { + ...data + }, + index: cameraIndex + }; + return axios.post("/settings/camera", payload); + }, + /** + * Create a new Pipeline for the provided camera. + * + * @param newPipelineName the name of the new pipeline. + * @param pipelineType the type of the new pipeline. Cannot be {@link WebsocketPipelineType.Calib3d} or {@link WebsocketPipelineType.DriverMode}. + * @param cameraIndex the index of the camera + */ + createNewPipeline( + newPipelineName: string, + pipelineType: Exclude, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + const payload = { + addNewPipeline: [newPipelineName, pipelineType], + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * Modify the settings of the currently selected pipeline of the provided camera. + * + * @param settings settings to modify. The type of the settings should match the currently selected pipeline type. + * @param updateStore whether or not to update the store. This is useful if the input field already models the store reference. + * @param cameraIndex the index of the camera + */ + changeCurrentPipelineSetting( + settings: ActiveConfigurablePipelineSettings, + updateStore = true, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + const payload = { + changePipelineSetting: { + ...settings, + cameraIndex: cameraIndex + } + }; + if (updateStore) { + this.changePipelineSettingsInStore(settings, cameraIndex); + } + useStateStore().websocket?.send(payload, true); + }, + changePipelineSettingsInStore( + settings: Partial, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + Object.entries(settings).forEach(([k, v]) => { + this.cameras[cameraIndex].pipelineSettings[k] = v; + }); + }, + /** + * Change the nickname of the currently selected pipeline of the provided camera. + * + * @param newName the new nickname for the camera. + * @param updateStore whether or not to update the store. This is useful if the input field already models the store reference. + * @param cameraIndex the index of the camera + */ + changeCurrentPipelineNickname( + newName: string, + updateStore = true, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + const payload = { + changePipelineName: newName, + cameraIndex: cameraIndex + }; + if (updateStore) { + this.cameras[cameraIndex].pipelineSettings.pipelineNickname = newName; + } + useStateStore().websocket?.send(payload, true); + }, + /** + * Modify the Pipeline type of the currently selected pipeline of the provided camera. This overwrites the current pipeline's settings when the backend resets the current pipeline settings. + * + * @param type the pipeline type to set. Cannot be {@link WebsocketPipelineType.Calib3d} or {@link WebsocketPipelineType.DriverMode}. + * @param cameraIndex the index of the camera. + */ + changeCurrentPipelineType( + type: Exclude, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + const payload = { + pipelineType: type, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * Change the index of the pipeline of the currently selected camera. + * + * @param index pipeline index to set. + * @param updateStore whether or not to update the store. This is useful if the input field already models the store reference. + * @param cameraIndex the index of the camera. + */ + changeCurrentPipelineIndex( + index: number, + updateStore = true, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + const payload = { + currentPipeline: index, + cameraIndex: cameraIndex + }; + if (updateStore) { + if ( + this.cameras[cameraIndex].currentPipelineIndex !== -1 && + this.cameras[cameraIndex].currentPipelineIndex !== -2 + ) { + this.cameras[cameraIndex].lastPipelineIndex = this.cameras[cameraIndex].currentPipelineIndex; + } + this.cameras[cameraIndex].currentPipelineIndex = index; + } + useStateStore().websocket?.send(payload, true); + }, + setDriverMode(isDriverMode: boolean, cameraIndex: number = useStateStore().currentCameraIndex) { + const payload = { + driverMode: isDriverMode, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * Change the currently selected pipeline of the provided camera. + * + * @param cameraIndex the index of the camera's pipeline to change. + */ + deleteCurrentPipeline(cameraIndex: number = useStateStore().currentCameraIndex) { + const payload = { + deleteCurrentPipeline: {}, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * Duplicate the pipeline at the provided index. + * + * @param pipelineIndex index of the pipeline to duplicate. + * @param cameraIndex the index of the camera. + */ + duplicatePipeline(pipelineIndex: number, cameraIndex: number = useStateStore().currentCameraIndex) { + const payload = { + duplicatePipeline: pipelineIndex, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * Change the currently set camera + * + * @param cameraIndex the index of the camera to set as the current camera. + * @param updateStore whether or not to update the store. This is useful if the input field already models the store reference. + */ + setCurrentCameraIndex(cameraIndex: number, updateStore = true) { + const payload = { + currentCamera: cameraIndex + }; + if (updateStore) { + useStateStore().currentCameraIndex = cameraIndex; + } + useStateStore().websocket?.send(payload, true); + }, + /** + * Change the nickname of the provided camera. + * + * @param newName the new nickname of the camera. + * @param updateStore whether or not to update the store. This is useful if the input field already models the store reference. + * @param cameraIndex the index of the camera. + * @return HTTP request promise to the backend + */ + changeCameraNickname( + newName: string, + updateStore = true, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + const payload = { + name: newName, + cameraIndex: cameraIndex + }; + if (updateStore) { + this.currentCameraSettings.nickname = newName; + } + return axios.post("/settings/camera/setNickname", payload); + }, + /** + * Start the 3D calibration process for the provided camera. + * + * @param calibrationInitData initialization calibration data. + * @param cameraIndex the index of the camera. + */ + startPnPCalibration( + calibrationInitData: { + squareSizeIn: number; + markerSizeIn: number; + patternWidth: number; + patternHeight: number; + boardType: CalibrationBoardTypes; + useOldPattern: boolean; + tagFamily: CalibrationTagFamilies; + }, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + const stateCalibData = useStateStore().calibrationData; + const payload = { + startPnpCalibration: { + count: stateCalibData.imageCount, + minCount: stateCalibData.minimumImageCount, + hasEnough: stateCalibData.hasEnoughImages, + videoModeIndex: stateCalibData.videoFormatIndex, + ...calibrationInitData + }, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * End the 3D calibration process for the provided camera. + * + * @param cameraIndex the index of the camera + * @return HTTP request promise to the backend + */ + endPnPCalibration(cameraIndex: number = useStateStore().currentCameraIndex) { + return axios.post("/calibration/end", { index: cameraIndex }); + }, + + importCalibrationFromData( + data: { calibration: CameraCalibrationResult }, + cameraIndex: number = useStateStore().currentCameraIndex + ) { + const payload = { + ...data, + cameraIndex: cameraIndex + }; + return axios.post("/calibration/importFromData", payload); + }, + /** + * Take a snapshot for the calibration processes + * + * @param cameraIndex the index of the camera that is currently in the calibration process + */ + takeCalibrationSnapshot(cameraIndex: number = useStateStore().currentCameraIndex) { + const payload = { + takeCalibrationSnapshot: true, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * Save a snapshot of the input frame of the camera. + * + * @param cameraIndex the index of the camera + */ + saveInputSnapshot(cameraIndex: number = useStateStore().currentCameraIndex) { + const payload = { + saveInputSnapshot: true, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * Save a snapshot of the output frame of the camera. + * + * @param cameraIndex the index of the camera + */ + saveOutputSnapshot(cameraIndex: number = useStateStore().currentCameraIndex) { + const payload = { + saveOutputSnapshot: true, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + /** + * Set the robot offset mode type. + * + * @param type Offset type to take. + * @param cameraIndex the index of the camera. + */ + takeRobotOffsetPoint(type: RobotOffsetType, cameraIndex: number = useStateStore().currentCameraIndex) { + const payload = { + robotOffsetPoint: type, + cameraIndex: cameraIndex + }; + useStateStore().websocket?.send(payload, true); + }, + getCalibrationCoeffs( + resolution: Resolution, + cameraIndex: number = useStateStore().currentCameraIndex + ): CameraCalibrationResult | undefined { + 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; + } + } +}); diff --git a/photon-client/src/stores/settings/GeneralSettingsStore.ts b/photon-client/src/stores/settings/GeneralSettingsStore.ts new file mode 100644 index 0000000..f594078 --- /dev/null +++ b/photon-client/src/stores/settings/GeneralSettingsStore.ts @@ -0,0 +1,131 @@ +import { defineStore } from "pinia"; +import type { + ConfigurableNetworkSettings, + GeneralSettings, + LightingSettings, + MetricData, + NetworkSettings +} from "@/types/SettingTypes"; +import { NetworkConnectionType } from "@/types/SettingTypes"; +import { useStateStore } from "@/stores/StateStore"; +import axios from "axios"; +import type { WebsocketSettingsUpdate } from "@/types/WebsocketDataTypes"; +import type { AprilTagFieldLayout } from "@/types/PhotonTrackingTypes"; + +interface GeneralSettingsStore { + general: GeneralSettings; + network: NetworkSettings; + lighting: LightingSettings; + metrics: MetricData; + currentFieldLayout: AprilTagFieldLayout; +} + +export const useSettingsStore = defineStore("settings", { + state: (): GeneralSettingsStore => ({ + general: { + version: undefined, + gpuAcceleration: undefined, + hardwareModel: undefined, + hardwarePlatform: undefined, + mrCalWorking: true, + availableModels: {}, + supportedBackends: [] + }, + network: { + ntServerAddress: "", + shouldManage: true, + canManage: true, + connectionType: NetworkConnectionType.DHCP, + staticIp: "", + hostname: "photonvision", + runNTServer: false, + shouldPublishProto: false, + networkInterfaceNames: [ + { + connName: "Example Wired Connection", + devName: "eth0" + } + ], + networkingDisabled: false, + matchCamerasOnlyByPath: false + }, + lighting: { + supported: true, + brightness: 0 + }, + metrics: { + cpuTemp: undefined, + cpuUtil: undefined, + cpuMem: undefined, + gpuMem: undefined, + ramUtil: undefined, + gpuMemUtil: undefined, + cpuThr: undefined, + cpuUptime: undefined, + diskUtilPct: undefined, + npuUsage: undefined + }, + currentFieldLayout: { + field: { + length: 16.4592, + width: 8.2296 + }, + tags: [] + } + }), + getters: { + gpuAccelerationEnabled(): boolean { + return this.general.gpuAcceleration !== undefined; + }, + networkInterfaceNames(): string[] { + return this.network.networkInterfaceNames.map((i) => i.devName); + } + }, + actions: { + requestMetricsUpdate() { + return axios.post("/utils/publishMetrics"); + }, + updateMetricsFromWebsocket(data: Required) { + this.metrics = { + cpuTemp: data.cpuTemp || undefined, + cpuUtil: data.cpuUtil || undefined, + cpuMem: data.cpuMem || undefined, + gpuMem: data.gpuMem || undefined, + ramUtil: data.ramUtil || undefined, + gpuMemUtil: data.gpuMemUtil || undefined, + cpuThr: data.cpuThr || undefined, + cpuUptime: data.cpuUptime || undefined, + diskUtilPct: data.diskUtilPct || undefined, + npuUsage: data.npuUsage || undefined + }; + }, + updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) { + this.general = { + version: data.general.version || undefined, + hardwareModel: data.general.hardwareModel || undefined, + hardwarePlatform: data.general.hardwarePlatform || undefined, + gpuAcceleration: data.general.gpuAcceleration || undefined, + mrCalWorking: data.general.mrCalWorking, + availableModels: data.general.availableModels || undefined, + supportedBackends: data.general.supportedBackends || [] + }; + this.lighting = data.lighting; + this.network = data.networkSettings; + this.currentFieldLayout = data.atfl; + }, + updateGeneralSettings(payload: Required) { + return axios.post("/settings/general", payload); + }, + /** + * Modify the brightness of the LEDs. + * + * @param brightness brightness to set [0, 100] + */ + changeLEDBrightness(brightness: number) { + const payload = { + enabledLEDPercentage: brightness + }; + useStateStore().websocket?.send(payload, true); + } + } +}); diff --git a/photon-client/src/types/PhotonTrackingTypes.ts b/photon-client/src/types/PhotonTrackingTypes.ts new file mode 100644 index 0000000..596a7ab --- /dev/null +++ b/photon-client/src/types/PhotonTrackingTypes.ts @@ -0,0 +1,77 @@ +export interface Quaternion { + X: number; + Y: number; + Z: number; + W: number; +} + +export interface Translation3d { + x: number; + y: number; + z: number; +} + +export interface Rotation3d { + quaternion: Quaternion; +} + +export interface Pose3d { + translation: Translation3d; + rotation: Rotation3d; +} + +// TODO update backend to serialize this using correct layout +export interface Transform3d { + x: number; + y: number; + z: number; + qw: number; + qx: number; + qy: number; + qz: number; + angle_x: number; + angle_y: number; + angle_z: number; +} + +export interface AprilTagFieldLayout { + field: { + length: number; + width: number; + }; + tags: { + ID: number; + pose: Pose3d; + }[]; +} + +export interface PhotonTarget { + yaw: number; + pitch: number; + skew: number; + area: number; + // -1 if not set + ambiguity: number; + // -1 if not set + fiducialId: number; + confidence: number; + classId: number; + // undefined if 3d isn't enabled + pose?: Transform3d; +} + +export interface MultitagResult { + bestTransform: Transform3d; + bestReprojectionError: number; + fiducialIDsUsed: number[]; +} + +export interface PipelineResult { + fps: number; + latency: number; + targets: PhotonTarget[]; + // undefined if multitag failed or non-tag pipeline + multitagResult?: MultitagResult; + // Object detection class names -- empty if not doing object detection + classNames: string[]; +} diff --git a/photon-client/src/types/PipelineTypes.ts b/photon-client/src/types/PipelineTypes.ts new file mode 100644 index 0000000..b5c7abe --- /dev/null +++ b/photon-client/src/types/PipelineTypes.ts @@ -0,0 +1,349 @@ +import type { WebsocketNumberPair } from "@/types/WebsocketDataTypes"; + +export enum PipelineType { + DriverMode = 1, + Reflective = 2, + ColoredShape = 3, + AprilTag = 4, + Aruco = 5, + ObjectDetection = 6 +} + +export enum AprilTagFamily { + Family36h11 = 0, + Family25h9 = 1, + Family16h5 = 2 +} + +export enum RobotOffsetPointMode { + None = 0, + Single = 1, + Dual = 2 +} + +export enum TargetModel { + StrongholdHighGoal = 0, + DeepSpaceDualTarget = 1, + InfiniteRechargeHighGoalOuter = 2, + CircularPowerCell7in = 3, + RapidReactCircularCargoBall = 4, + AprilTag6in_16h5 = 5, + AprilTag6p5in_36h11 = 6 +} + +export interface PipelineSettings { + offsetRobotOffsetMode: RobotOffsetPointMode; + streamingFrameDivisor: number; + offsetDualPointBArea: number; + contourGroupingMode: number; + hsvValue: WebsocketNumberPair | [number, number]; + cameraGain: number; + cameraBlueGain: number; + cameraRedGain: number; + cornerDetectionSideCount: number; + contourRatio: WebsocketNumberPair | [number, number]; + contourTargetOffsetPointEdge: number; + pipelineNickname: string; + inputImageRotationMode: number; + contourArea: WebsocketNumberPair | [number, number]; + solvePNPEnabled: boolean; + contourFullness: WebsocketNumberPair | [number, number]; + pipelineIndex: number; + inputShouldShow: boolean; + cameraAutoExposure: boolean; + contourSpecklePercentage: number; + contourTargetOrientation: number; + targetModel: TargetModel; + cornerDetectionUseConvexHulls: boolean; + outputShouldShow: boolean; + outputShouldDraw: boolean; + offsetDualPointA: { x: number; y: number }; + offsetDualPointB: { x: number; y: number }; + hsvHue: WebsocketNumberPair | [number, number]; + ledMode: boolean; + hueInverted: boolean; + outputShowMultipleTargets: boolean; + contourSortMode: number; + cameraExposureRaw: number; + cameraMinExposureRaw: number; + cameraMaxExposureRaw: number; + offsetSinglePoint: { x: number; y: number }; + cameraBrightness: number; + offsetDualPointAArea: number; + cornerDetectionExactSideCount: boolean; + cameraVideoModeIndex: number; + cornerDetectionStrategy: number; + cornerDetectionAccuracyPercentage: number; + hsvSaturation: WebsocketNumberPair | [number, number]; + pipelineType: PipelineType; + contourIntersection: number; + + cameraAutoWhiteBalance: boolean; + cameraWhiteBalanceTemp: number; +} +export type ConfigurablePipelineSettings = Partial< + Omit< + PipelineSettings, + | "offsetDualPointAArea" + | "cornerDetectionSideCount" + | "pipelineNickname" + | "pipelineIndex" + | "pipelineType" + | "cornerDetectionUseConvexHulls" + | "offsetDualPointA" + | "offsetDualPointB" + | "ledMode" + | "offsetSinglePoint" + | "offsetDualPointBArea" + | "cornerDetectionExactSideCount" + | "cornerDetectionStrategy" + > +>; +// Omitted settings are changed for all pipeline types +export const DefaultPipelineSettings: Omit< + PipelineSettings, + "cameraGain" | "targetModel" | "ledMode" | "outputShowMultipleTargets" | "cameraExposureRaw" | "pipelineType" +> = { + offsetRobotOffsetMode: RobotOffsetPointMode.None, + streamingFrameDivisor: 0, + offsetDualPointBArea: 0, + contourGroupingMode: 0, + hsvValue: { first: 50, second: 255 }, + cameraBlueGain: 20, + cameraRedGain: 11, + cornerDetectionSideCount: 4, + contourRatio: { first: 0, second: 20 }, + contourTargetOffsetPointEdge: 0, + pipelineNickname: "Placeholder Pipeline", + inputImageRotationMode: 0, + contourArea: { first: 0, second: 100 }, + solvePNPEnabled: false, + contourFullness: { first: 0, second: 100 }, + pipelineIndex: 0, + inputShouldShow: false, + cameraAutoExposure: false, + contourSpecklePercentage: 5, + contourTargetOrientation: 1, + cornerDetectionUseConvexHulls: true, + outputShouldShow: true, + outputShouldDraw: true, + offsetDualPointA: { x: 0, y: 0 }, + offsetDualPointB: { x: 0, y: 0 }, + hsvHue: { first: 50, second: 180 }, + hueInverted: false, + contourSortMode: 0, + offsetSinglePoint: { x: 0, y: 0 }, + cameraBrightness: 50, + offsetDualPointAArea: 0, + cornerDetectionExactSideCount: false, + cameraVideoModeIndex: 0, + cornerDetectionStrategy: 0, + cornerDetectionAccuracyPercentage: 10, + hsvSaturation: { first: 50, second: 255 }, + contourIntersection: 1, + cameraAutoWhiteBalance: false, + cameraWhiteBalanceTemp: 4000, + cameraMinExposureRaw: 1, + cameraMaxExposureRaw: 2 +}; + +export interface ReflectivePipelineSettings extends PipelineSettings { + pipelineType: PipelineType.Reflective; + contourFilterRangeY: number; + contourFilterRangeX: number; +} +export type ConfigurableReflectivePipelineSettings = Partial> & + ConfigurablePipelineSettings; +export const DefaultReflectivePipelineSettings: ReflectivePipelineSettings = { + ...DefaultPipelineSettings, + cameraGain: 20, + targetModel: TargetModel.InfiniteRechargeHighGoalOuter, + ledMode: true, + outputShowMultipleTargets: false, + cameraExposureRaw: 6, + pipelineType: PipelineType.Reflective, + + contourFilterRangeY: 2, + contourFilterRangeX: 2 +}; + +export interface ColoredShapePipelineSettings extends PipelineSettings { + pipelineType: PipelineType.ColoredShape; + erode: boolean; + cameraCalibration: null; + dilate: boolean; + circleAccuracy: number; + contourRadius: WebsocketNumberPair | [number, number]; + circleDetectThreshold: number; + accuracyPercentage: number; + contourShape: number; + contourPerimeter: WebsocketNumberPair | [number, number]; + minDist: number; + maxCannyThresh: number; +} +export type ConfigurableColoredShapePipelineSettings = Partial< + Omit +> & + ConfigurablePipelineSettings; +export const DefaultColoredShapePipelineSettings: ColoredShapePipelineSettings = { + ...DefaultPipelineSettings, + cameraGain: 75, + targetModel: TargetModel.InfiniteRechargeHighGoalOuter, + ledMode: true, + outputShowMultipleTargets: false, + cameraExposureRaw: 20, + pipelineType: PipelineType.ColoredShape, + + erode: false, + cameraCalibration: null, + dilate: false, + circleAccuracy: 20, + contourRadius: { first: 0, second: 100 }, + circleDetectThreshold: 5, + accuracyPercentage: 10, + contourShape: 2, + contourPerimeter: { first: 0, second: 1.7976931348623157e308 }, + minDist: 20, + maxCannyThresh: 90 +}; + +export interface AprilTagPipelineSettings extends PipelineSettings { + pipelineType: PipelineType.AprilTag; + hammingDist: number; + numIterations: number; + decimate: number; + blur: number; + decisionMargin: number; + refineEdges: boolean; + debug: boolean; + threads: number; + tagFamily: AprilTagFamily; + doMultiTarget: boolean; + doSingleTargetAlways: boolean; +} +export type ConfigurableAprilTagPipelineSettings = Partial< + Omit +> & + ConfigurablePipelineSettings; +export const DefaultAprilTagPipelineSettings: AprilTagPipelineSettings = { + ...DefaultPipelineSettings, + cameraGain: 75, + targetModel: TargetModel.AprilTag6p5in_36h11, + ledMode: false, + outputShowMultipleTargets: true, + cameraExposureRaw: 20, + pipelineType: PipelineType.AprilTag, + + hammingDist: 0, + numIterations: 40, + decimate: 1, + blur: 0, + decisionMargin: 35, + refineEdges: true, + debug: false, + threads: 4, + tagFamily: AprilTagFamily.Family36h11, + doMultiTarget: false, + doSingleTargetAlways: false +}; + +export interface ArucoPipelineSettings extends PipelineSettings { + pipelineType: PipelineType.Aruco; + + tagFamily: AprilTagFamily; + + threshWinSizes: WebsocketNumberPair | [number, number]; + threshStepSize: number; + threshConstant: number; + debugThreshold: boolean; + + useCornerRefinement: boolean; + + useAruco3: boolean; + aruco3MinMarkerSideRatio: number; + aruco3MinCanonicalImgSide: number; + + doMultiTarget: boolean; + doSingleTargetAlways: boolean; +} +export type ConfigurableArucoPipelineSettings = Partial> & + ConfigurablePipelineSettings; +export const DefaultArucoPipelineSettings: ArucoPipelineSettings = { + ...DefaultPipelineSettings, + cameraGain: 75, + outputShowMultipleTargets: true, + targetModel: TargetModel.AprilTag6p5in_36h11, + cameraExposureRaw: -1, + cameraAutoExposure: true, + ledMode: false, + pipelineType: PipelineType.Aruco, + + tagFamily: AprilTagFamily.Family36h11, + threshWinSizes: { first: 11, second: 91 }, + threshStepSize: 40, + threshConstant: 10, + debugThreshold: false, + useCornerRefinement: true, + useAruco3: false, + aruco3MinMarkerSideRatio: 0.02, + aruco3MinCanonicalImgSide: 32, + doMultiTarget: false, + doSingleTargetAlways: false +}; + +export interface ObjectDetectionPipelineSettings extends PipelineSettings { + pipelineType: PipelineType.ObjectDetection; + confidence: number; + nms: number; + box_thresh: number; + model: string; +} +export type ConfigurableObjectDetectionPipelineSettings = Partial< + Omit +> & + ConfigurablePipelineSettings; +export const DefaultObjectDetectionPipelineSettings: ObjectDetectionPipelineSettings = { + ...DefaultPipelineSettings, + pipelineType: PipelineType.ObjectDetection, + cameraGain: 20, + targetModel: TargetModel.InfiniteRechargeHighGoalOuter, + ledMode: true, + outputShowMultipleTargets: false, + cameraExposureRaw: 6, + confidence: 0.9, + nms: 0.45, + box_thresh: 0.25, + model: "" +}; + +export interface Calibration3dPipelineSettings extends PipelineSettings { + drawAllSnapshots: boolean; +} +export type ConfigurableCalibration3dPipelineSettings = Partial> & + ConfigurablePipelineSettings; +export const DefaultCalibration3dPipelineSettings: Calibration3dPipelineSettings = { + ...DefaultPipelineSettings, + pipelineType: PipelineType.ObjectDetection, + cameraGain: 20, + targetModel: TargetModel.InfiniteRechargeHighGoalOuter, + ledMode: true, + outputShowMultipleTargets: false, + cameraExposureRaw: 6, + drawAllSnapshots: false +}; + +export type ActivePipelineSettings = + | ReflectivePipelineSettings + | ColoredShapePipelineSettings + | AprilTagPipelineSettings + | ArucoPipelineSettings + | ObjectDetectionPipelineSettings + | Calibration3dPipelineSettings; + +export type ActiveConfigurablePipelineSettings = + | ConfigurableReflectivePipelineSettings + | ConfigurableColoredShapePipelineSettings + | ConfigurableAprilTagPipelineSettings + | ConfigurableArucoPipelineSettings + | ConfigurableObjectDetectionPipelineSettings + | ConfigurableCalibration3dPipelineSettings; diff --git a/photon-client/src/types/SettingTypes.ts b/photon-client/src/types/SettingTypes.ts new file mode 100644 index 0000000..9cad1d6 --- /dev/null +++ b/photon-client/src/types/SettingTypes.ts @@ -0,0 +1,330 @@ +import { type ActivePipelineSettings, DefaultAprilTagPipelineSettings } from "@/types/PipelineTypes"; +import type { Pose3d } from "@/types/PhotonTrackingTypes"; + +export interface GeneralSettings { + version?: string; + gpuAcceleration?: string; + hardwareModel?: string; + hardwarePlatform?: string; + mrCalWorking: boolean; + availableModels: Record; + supportedBackends: string[]; +} + +export interface MetricData { + cpuTemp?: string; + cpuUtil?: string; + cpuMem?: string; + gpuMem?: string; + ramUtil?: string; + gpuMemUtil?: string; + cpuThr?: string; + cpuUptime?: string; + diskUtilPct?: string; + npuUsage?: string; +} + +export enum NetworkConnectionType { + DHCP = 0, + Static = 1 +} + +export interface NetworkInterfaceType { + connName: string; + devName: string; +} + +export interface NetworkSettings { + ntServerAddress: string; + connectionType: NetworkConnectionType; + staticIp: string; + hostname: string; + runNTServer: boolean; + shouldManage: boolean; + shouldPublishProto: boolean; + canManage: boolean; + networkManagerIface?: string; + setStaticCommand?: string; + setDHCPcommand?: string; + networkInterfaceNames: NetworkInterfaceType[]; + networkingDisabled: boolean; + matchCamerasOnlyByPath: boolean; +} + +export type ConfigurableNetworkSettings = Omit< + NetworkSettings, + "canManage" | "networkInterfaceNames" | "networkingDisabled" +>; + +export interface LightingSettings { + supported: boolean; + brightness: number; +} + +export enum LogLevel { + ERROR = 0, + WARN = 1, + INFO = 2, + DEBUG = 3, + TRACE = 4 +} + +export interface LogMessage { + level: LogLevel; + message: string; + timestamp: Date; +} + +export interface Resolution { + width: number; + height: number; +} + +export interface VideoFormat { + resolution: Resolution; + fps: number; + pixelFormat: string; + index?: number; + diagonalFOV?: number; + horizontalFOV?: number; + verticalFOV?: number; + mean?: number; +} + +export enum CvType { + CV_8U = 0, + CV_8S = 1, + CV_16U = 2, + CV_16S = 3, + CV_32S = 4, + CV_32F = 5, + CV_64F = 6, + CV_16F = 7 +} + +export interface JsonMatOfDouble { + rows: number; + cols: number; + type: CvType; + data: number[]; +} + +export interface JsonImageMat { + rows: number; + cols: number; + type: CvType; + data: string; // base64 encoded +} + +export interface CvPoint3 { + x: number; + y: number; + z: number; +} +export interface CvPoint { + x: number; + y: number; +} + +export interface BoardObservation { + locationInObjectSpace: CvPoint3[]; + locationInImageSpace: CvPoint[]; + reprojectionErrors: CvPoint[]; + optimisedCameraToObject: Pose3d; + includeObservationInCalibration: boolean; + snapshotName: string; + snapshotData: JsonImageMat; +} + +export interface CameraCalibrationResult { + resolution: Resolution; + cameraIntrinsics: JsonMatOfDouble; + distCoeffs: JsonMatOfDouble; + observations: BoardObservation[]; + calobjectWarp?: number[]; + // We might have to omit observations for bandwidth, so backend will send us this + numSnapshots: number; + meanErrors: number[]; +} + +export enum ValidQuirks { + AWBGain = "AWBGain", + AdjustableFocus = "AdjustableFocus", + InnoOV9281Controls = "InnoOV9281Controls", + ArduOV9281Controls = "ArduOV9281Controls", + ArduOV2311Controls = "ArduOV2311Controls", + ArduOV9782Controls = "ArduOV9782Controls", + ArduCamCamera = "ArduCamCamera", + CompletelyBroken = "CompletelyBroken", + FPSCap100 = "FPSCap100", + Gain = "Gain", + PiCam = "PiCam", + StickyFPS = "StickyFPS", + LifeCamControls = "LifeCamControls", + PsEyeControls = "PsEyeControls" +} + +export interface QuirkyCamera { + baseName: string; + usbVid: number; + usbPid: number; + displayName: string; + quirks: Record; +} + +export interface CameraSettings { + nickname: string; + uniqueName: string; + + fov: { + value: number; + managedByVendor: boolean; + }; + stream: { + inputPort: number; + outputPort: number; + }; + + validVideoFormats: VideoFormat[]; + completeCalibrations: CameraCalibrationResult[]; + + lastPipelineIndex?: number; + currentPipelineIndex: number; + pipelineNicknames: string[]; + pipelineSettings: ActivePipelineSettings; + + cameraQuirks: QuirkyCamera; + isCSICamera: boolean; + + minExposureRaw: number; + maxExposureRaw: number; + + minWhiteBalanceTemp: number; + maxWhiteBalanceTemp: number; +} + +export interface CameraSettingsChangeRequest { + fov: number; + quirksToChange: Record; +} + +export const PlaceholderCameraSettings: CameraSettings = { + nickname: "Placeholder Camera", + uniqueName: "Placeholder Name", + fov: { + value: 70, + managedByVendor: false + }, + stream: { + inputPort: 0, + outputPort: 0 + }, + validVideoFormats: [ + { + resolution: { width: 1920, height: 1080 }, + fps: 60, + pixelFormat: "RGB" + }, + { + resolution: { width: 1280, height: 720 }, + fps: 60, + pixelFormat: "RGB" + }, + { + resolution: { width: 640, height: 480 }, + fps: 30, + pixelFormat: "RGB" + } + ], + completeCalibrations: [ + { + resolution: { width: 1920, height: 1080 }, + cameraIntrinsics: { + rows: 1, + cols: 1, + type: 1, + data: [1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + distCoeffs: { + rows: 1, + cols: 1, + type: 1, + data: [10, 11, 12, 13] + }, + observations: [ + { + locationInImageSpace: [ + { x: 100, y: 100 }, + { x: 210, y: 100 }, + { x: 320, y: 101 } + ], + locationInObjectSpace: [{ x: 0, y: 0, z: 0 }], + optimisedCameraToObject: { + translation: { x: 1, y: 2, z: 3 }, + rotation: { quaternion: { W: 1, X: 0, Y: 0, Z: 0 } } + }, + reprojectionErrors: [ + { x: 1, y: 1 }, + { x: 2, y: 1 }, + { x: 3, y: 1 } + ], + includeObservationInCalibration: false, + snapshotName: "img0.png", + snapshotData: { rows: 480, cols: 640, type: CvType.CV_8U, data: "" } + } + ], + numSnapshots: 1, + meanErrors: [123.45] + } + ], + pipelineNicknames: ["Placeholder Pipeline"], + lastPipelineIndex: 0, + currentPipelineIndex: 0, + pipelineSettings: DefaultAprilTagPipelineSettings, + cameraQuirks: { + displayName: "Blank 1", + baseName: "Blank 2", + usbVid: -1, + usbPid: -1, + quirks: { + AWBGain: false, + AdjustableFocus: false, + ArduOV9281Controls: false, + ArduOV2311Controls: false, + ArduOV9782Controls: false, + ArduCamCamera: false, + CompletelyBroken: false, + FPSCap100: false, + Gain: false, + PiCam: false, + StickyFPS: false, + InnoOV9281Controls: false, + LifeCamControls: false, + PsEyeControls: false + } + }, + isCSICamera: false, + minExposureRaw: 1, + maxExposureRaw: 100, + minWhiteBalanceTemp: 2000, + maxWhiteBalanceTemp: 10000 +}; + +export enum CalibrationBoardTypes { + Chessboard = 0, + Charuco = 1 +} + +export enum CalibrationTagFamilies { + Dict_4X4_1000 = 0, + Dict_5X5_1000 = 1, + Dict_6X6_1000 = 2, + Dict_7X7_1000 = 3 +} + +export enum RobotOffsetType { + Clear = 0, + Single = 1, + DualFirst = 2, + DualSecond = 3 +} diff --git a/photon-client/src/types/WebsocketDataTypes.ts b/photon-client/src/types/WebsocketDataTypes.ts new file mode 100644 index 0000000..2e9b6da --- /dev/null +++ b/photon-client/src/types/WebsocketDataTypes.ts @@ -0,0 +1,111 @@ +import type { + CameraCalibrationResult, + GeneralSettings, + LightingSettings, + LogLevel, + MetricData, + NetworkSettings, + QuirkyCamera +} from "@/types/SettingTypes"; +import type { ActivePipelineSettings } from "@/types/PipelineTypes"; +import type { AprilTagFieldLayout, PipelineResult } from "@/types/PhotonTrackingTypes"; + +export interface WebsocketLogMessage { + logMessage: { + logLevel: LogLevel; + logMessage: string; + }; +} +export interface WebsocketSettingsUpdate { + general: Required; + lighting: Required; + networkSettings: NetworkSettings; + atfl: AprilTagFieldLayout; +} + +export interface WebsocketNumberPair { + first: number; + second: number; +} + +export type WebsocketVideoFormat = Record< + number, + { + fps: number; + height: number; + width: number; + pixelFormat: string; + index?: number; + diagonalFOV?: number; + horizontalFOV?: number; + verticalFOV?: number; + standardDeviation?: number; + mean?: number; + } +>; + +export interface WebsocketCameraSettingsUpdate { + calibrations: CameraCalibrationResult[]; + currentPipelineIndex: number; + currentPipelineSettings: ActivePipelineSettings; + fov: number; + inputStreamPort: number; + isFovConfigurable: boolean; + isCSICamera: boolean; + nickname: string; + uniqueName: string; + outputStreamPort: number; + pipelineNicknames: string[]; + videoFormatList: WebsocketVideoFormat; + cameraQuirks: QuirkyCamera; + minExposureRaw: number; + maxExposureRaw: number; + minWhiteBalanceTemp: number; + maxWhiteBalanceTemp: number; +} +export interface WebsocketNTUpdate { + connected: boolean; + address?: string; + clients?: number; +} + +// key is the index of the camera, value is that camera's result +export type WebsocketPipelineResultUpdate = Record; + +export interface WebsocketCalibrationData { + patternWidth: number; + boardType: number; + hasEnough: boolean; + count: number; + minCount: number; + videoModeIndex: number; + patternHeight: number; + squareSizeIn: number; + markerSizeIn: number; +} + +export interface IncomingWebsocketData { + log?: WebsocketLogMessage; + settings?: WebsocketSettingsUpdate; + cameraSettings?: WebsocketCameraSettingsUpdate[]; + ntConnectionInfo?: WebsocketNTUpdate; + metrics?: Required; + updatePipelineResult?: WebsocketPipelineResultUpdate; + networkInfo?: { + possibleRios: string[]; + deviceIps: string[]; + }; + mutatePipelineSettings?: Partial; + cameraIndex?: number; // Sent when mutating pipeline settings to check against currently active + calibrationData?: WebsocketCalibrationData; +} + +export enum WebsocketPipelineType { + Calib3d = -2, + DriverMode = -1, + Reflective = 0, + ColoredShape = 1, + AprilTag = 2, + Aruco = 3, + ObjectDetection = 4 +} diff --git a/photon-client/src/views/CameraSettingsView.vue b/photon-client/src/views/CameraSettingsView.vue new file mode 100644 index 0000000..818ad81 --- /dev/null +++ b/photon-client/src/views/CameraSettingsView.vue @@ -0,0 +1,51 @@ + + + diff --git a/photon-client/src/views/DashboardView.vue b/photon-client/src/views/DashboardView.vue new file mode 100644 index 0000000..05db694 --- /dev/null +++ b/photon-client/src/views/DashboardView.vue @@ -0,0 +1,55 @@ + + + diff --git a/photon-client/src/views/DocsView.vue b/photon-client/src/views/DocsView.vue new file mode 100644 index 0000000..c9d1064 --- /dev/null +++ b/photon-client/src/views/DocsView.vue @@ -0,0 +1,18 @@ + + +