Compare commits

...

13 Commits

Author SHA1 Message Date
Sam Freund
5f59e9ab22 bump rubik image (#2424) 2026-04-05 18:56:33 -07:00
Jade
515a1a3d78 Fix comments in OutputStream pipeline (#2415) 2026-03-28 15:13:47 -05:00
Sam Freund
03ffcb1215 Upgrade website dependencies (#2414) 2026-03-27 14:31:18 -04:00
Sam Freund
131098bfdd Add AE quirk to OV2311 (#2411) 2026-03-27 10:52:04 -05:00
Chris Gerth
b5277e5f4c Add Community Contribution Guidelines (#2405)
## Description

Leadership team identified gaps in how we introduce new developers to
the community.

Additional docs for onboarding new developers, helping clarify roles and
what counts as a "good" PR.

## Meta

Merge checklist:
- [ X] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [ X] The description documents the _what_ and _why_, including events
that led to this PR
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with all settings going back to the previous seasons's last release
(seasons end after champs ends)
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
- [ ] If this PR adds a dependency, the license has been checked for
compatibility and steps taken to follow it
2026-03-27 02:32:17 +00:00
Jesse Kane
52f1f7726f Fix strong reference in Cleaners (#2404) 2026-03-26 18:55:06 +00:00
Sam Freund
e19534da47 Remove old camera quirk aliases (#2412) 2026-03-26 14:22:58 -04:00
Gold856
488646e755 Upgrade to Vite 8 (#2408) 2026-03-26 04:39:38 +00:00
Jade
a8a0024321 Add nix files to .gitignore (#2409)
I assume based off discord comments a PR won't be accepted for a
shell.nix or equivalent flake solution so we should gitignore this so
people don't accidentally add them to PRs
2026-03-25 23:04:38 -05:00
Sam Freund
032deba775 refactor dark mode checks (#2407)
The current method for checking light vs. dark mode is to compare the
name of the theme against a hardcoded string. This PR uses a dark mode
boolean. This change is for verbosity and so that we're not reliant on
theme name. Additionally, we change some references to colors to the
global theme, instead of indexing the list of themes.
2026-03-24 17:49:56 -05:00
Gold856
7b240a027a Add more linting rules (#2406)
As a precursor to #2394, add a bunch of linting rules to try and catch
more mistakes/potential code errors/unnecessary code. Add a bunch of
rules from https://eslint.vuejs.org/rules/ in the "uncategorized"
section that seem useful to have.
2026-03-23 18:41:11 -04:00
Gold856
d4bfe643d5 Clean up C++ headers (#2402) 2026-03-19 06:10:04 +00:00
Stéphane Dalton
12446a6c44 Populate classId and confidence level for object detection in Java simulation (#2372)
## Description

compute confidence level based on target area in total image size and
populate classId and confidence level in Java (while building the
PhotonTrackedTarget)

## Changes
- Add new VisionTargetSim constructor for object detection
- If class ID specified but confidence = -1, estimate based on total
area

---------

Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
2026-03-19 03:42:37 +00:00
95 changed files with 1755 additions and 1638 deletions

View File

@@ -10,6 +10,7 @@ concurrency:
cancel-in-progress: true
env:
# WHEN BUMPING, UPDATE RUBIK IMAGE TO POINT TO VAR INSTEAD OF BEING HARDCODED
IMAGE_VERSION: v2026.1.3
jobs:
@@ -560,7 +561,8 @@ jobs:
artifact-name: LinuxArm64
image_suffix: rubikpi3
plat_override: LINUX_QCS6490
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rubikpi3.tar.xz
# CHANGE BACK TO $IMAGE_VERSION AFTER BUMPING
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2026.1.4/photonvision_rubikpi3.tar.xz
minimum_free_mb: 1024
root_location: 'offset=569376768'
shrink_image: 'no'

5
.gitignore vendored
View File

@@ -156,3 +156,8 @@ photon-client/playwright-report/
photon-client/blob-report/
photon-client/playwright/.cache/
photon-client/playwright/.auth/
# Nix files
shell.nix
flake.nix
flake.lock

View File

@@ -0,0 +1,68 @@
# Welcome!
First and foremost, welcome to PhotonVision Development! We're pumped that you're interested to jump in, and help out!
Like most things in FIRST, PhotonVision is reliant on the hard work of dedicated volunteers. It doesn't exist without you. You're joining in with a strong tradition of open-source software development.
This page talks a bit about how we develop PhotonVision, as a community. It applies to all repos and community aspects for PhotonVision.
## Getting Started
The very first thing we'd recommend - get your computer set up to be able to build and run PhotonVision. [Docs for that are here](building-photon.md).
The two best ways to figure out what to do first:
1. Take a look at [the main PhotonVision Repo's Issues](https://github.com/PhotonVision/photonvision/issues) - especially those marked `bug` or `good first issue`!
2. Connect on [the Discord Server](https://discord.gg/wYxTwym) - Introduce yourself, and talk about what you're interested in!
From there - assign yourself to an issue if you intend to work it. Create a Fork in github, and make and test your changes. Once passing builds, meeting your expectations, and passing CI, open a pull request back to the main repo.
## Submitting A PR
Pull Requests are the mechanism used to ensure we only merge high-quality, reviewed, concrete, and organized changes to the codebase.
Things that peer reviewers will look for include:
* All CI checks are passing on the server.
* Documentation - does the PR match the issue? Is the description detailed?
* Cohesiveness - does the PR express a singular, related set of changes?
* Architecture - is the code consistent with other code around it? Is it maintainable for the long term?
* Testing - have unit tests been added as appropriate? Has the change been tested on real hardware?
Work as you can to clean up any changes requested. Once all changes are addressed, the contain should get merged, and included in the next release! Horary!
## General Developer Interaction
The main guiding principle: remember that we're all volunteers. While promptness is always appreciated (and occasionally required as release deadlines approach), it's important to remember each individual is only contributing when their personal schedule allows. Expect delays, prefer asynchronous communication, and be polite with reminders.
While most of the community members are either FIRST robotics mentors or students, the PhotonVision development team is primarily focused on delivering high quality software. Mentorship can and does occur, but is not the primary goal. Members are expected to do their learning fairly independently.
Seek to build trust in the quality of your work. Think carefully on your opinions before asking others to think about them too.
Bias toward action, and productionizable code. Limit the number of active PR's to help keep focus.
Finally, be sure to embody the ethos of Gracious Professionalism in all your actions, on all platforms in the project. See more in our [code of conduct](https://github.com/PhotonVision/.github/blob/738dfcb792fdbfc2e8408c0135e389179fc483c0/codeofcoduct.md)
### AI Usage
Coding assistants driven by Large Language Models ("AI") are extremely powerful tools, and have been used on more than one occasion to accelerate development.
PhotonVision still maintains a fundamental philosophy that the human submitting the pull request is responsible for the code and its behavior, regardless of the tools used to create it.
These tools can also generate a large volume of code changes, very rapidly. The above guidelines on PR quality still apply - large, undirected, or overly-scoped PR's are likely to be ignored, regardless of tooling used to generate them.
### Violations
We're thankful that we've rarely experienced major issues in our community. In all cases, the project leads shall have the final decision making authority when dealing with violations of these guidelines.
## Yearly Development Cycle
PhotonVision's Development Cycle follows the same general flow as the FRC Build Season. Larger experiments get done over the summer, fall focuses on testing and "production-readiness", build season focuses on keeping all teams running smoothly.
The actual priorities also shift as developers have more or time to commit, or express interest in specific direction.
PR's are absolutely always welcome. Just note depending on the scope and size, they may not be reviewed or merged immediately.
## Project Governance
This project is jointly lead by Matt and Banks, who may be contacted on Discord. They serve as a "Benevolent leader for life" role, coordinating and approving architecture as needed, and curating the team of developers who have Pull Request review and merge responsibilities.

View File

@@ -1,6 +1,7 @@
# Contributing to PhotonVision Projects
```{toctree}
guidelines
building-photon
building-docs
linting

View File

@@ -95,6 +95,27 @@ These `TargetModel` are paired with a target pose to create a `VisionTargetSim`.
The pose of a `VisionTargetSim` object can be updated to simulate moving targets. Note, however, that this will break latency simulation for that target.
:::
To use simulated object detection, you must provide an objDetClassId (zero-indexed class ID) and confidence value. When you set objDetConf to -1, the simulation computes confidence based on the area of the target in the camera's field of view. To simulate a object detection model with one class (fuel, index 0) and specify confidence, you'd write:
```{eval-rst}
.. tab-set-code::
.. code-block:: java
// arbitrary position on field
final var targetPose = new Pose3d(new Translation3d(2, 0, 0), new Rotation3d());
// Class id, zero-indexed
final int classId = 0;
// Confidence, between 0 and 1.
final float conf = 0.67f;
// 6 inch diameter ball
final TargetModel ballModel = new TargetModel(Units.inchesToMeters(6));
final var ballTargetSim = new VisionTargetSim(targetPose, ballModel, classId, conf);
// Add this vision target to the vision system simulation to make it visible
visionSim.addVisionTargets(visionTarget);
```
For convenience, an `AprilTagFieldLayout` can also be added to automatically create a target for each of its AprilTags.
```{eval-rst}

View File

@@ -4,7 +4,7 @@ import { defineConfigWithVueTs, vueTsConfigs } from "@vue/eslint-config-typescri
import skipFormattingConfig from "@vue/eslint-config-prettier/skip-formatting";
export default defineConfigWithVueTs(
pluginVue.configs["flat/recommended"],
pluginVue.configs["flat/recommended-error"],
vueTsConfigs.recommended,
skipFormattingConfig,
{
@@ -26,10 +26,24 @@ export default defineConfigWithVueTs(
semi: ["error", "always"],
"eol-last": "error",
eqeqeq: "error",
"no-useless-concat": "error",
"object-curly-spacing": ["error", "always"],
"quote-props": ["error", "as-needed"],
"no-case-declarations": "off",
"vue/eqeqeq": "error",
"vue/no-useless-concat": "error",
"vue/no-constant-condition": "error",
"vue/no-empty-pattern": "error",
"vue/no-undef-directives": "error",
"vue/no-undef-properties": "error",
"vue/no-unused-properties": "error",
"vue/no-unused-refs": "error",
"vue/no-use-v-else-with-v-for": "error",
"vue/no-useless-mustaches": "error",
"vue/no-useless-v-bind": "error",
"vue/require-default-prop": "off",
"vue/v-for-delimiter-style": "error",
"vue/v-on-event-hyphenation": "off",
"@typescript-eslint/no-explicit-any": "off",
"vue/valid-v-slot": ["error", { allowModifiers: true }]

View File

@@ -41,11 +41,11 @@
"@vue/eslint-config-typescript": "^14.5.0",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.31.0",
"eslint-plugin-vue": "^10.3.0",
"eslint-plugin-vue": "^10.7.0",
"prettier": "^3.6.2",
"sass": "^1.89.2",
"typescript": "^5.8.3",
"vite": "^7.0.5",
"vite": "^8.0.2",
"vite-plugin-vuetify": "^2.1.1"
}
}

View File

@@ -74,7 +74,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
command: process.platform == "win32" ? "" : "./" + "gradlew run",
command: process.platform === "win32" ? "" : "./gradlew run",
url: "http://localhost:5800",
timeout: 300 * 1000,
reuseExistingServer: !process.env.CI,

File diff suppressed because it is too large Load Diff

View File

@@ -50,8 +50,8 @@ const drawTargets = async (targets: PhotonTarget[]) => {
return;
}
if (theme.global.name.value === "LightTheme") scene.background = new Color(0xa9a9a9);
else scene.background = new Color(0x000000);
if (theme.global.current.value.dark) scene.background = new Color(0x000000);
else scene.background = new Color(0xa9a9a9);
scene.remove(...previousTargets);
previousTargets = [];
@@ -158,8 +158,8 @@ onMounted(async () => {
if (!canvas) return;
renderer = new WebGLRenderer({ canvas: canvas });
if (theme.global.name.value === "LightTheme") scene.background = new Color(0xa9a9a9);
else scene.background = new Color(0x000000);
if (theme.global.current.value.dark) scene.background = new Color(0x000000);
else scene.background = new Color(0xa9a9a9);
onWindowResize();
window.addEventListener("resize", onWindowResize);
@@ -234,7 +234,7 @@ watchEffect(() => {
<v-btn
style="width: 100%"
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="resetCamFirstPerson"
>
First Person
@@ -244,7 +244,7 @@ watchEffect(() => {
<v-btn
style="width: 100%"
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="resetCamThirdPerson"
>
Third Person

View File

@@ -208,8 +208,8 @@ onMounted(async () => {
const ambientLight = new AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
if (theme.global.name.value === "LightTheme") scene.background = new Color(0xa9a9a9);
else scene.background = new Color(0x000000);
if (theme.global.current.value.dark) scene.background = new Color(0x000000);
else scene.background = new Color(0xa9a9a9);
// Initialize a stable aspect ratio so subsequent resize events derive
// height from width, avoiding layout feedback during continuous resizing
@@ -347,7 +347,7 @@ watch(
<v-btn
style="width: 100%"
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="resetCamFirstPerson"
>
First Person
@@ -357,7 +357,7 @@ watch(
<v-btn
style="width: 100%"
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="resetCamThirdPerson"
>
Third Person

View File

@@ -50,7 +50,7 @@ const containerStyle = computed<StyleValue>(() => {
});
const overlayStyle = computed<StyleValue>(() => {
if (useStateStore().colorPickingMode || streamSrc.value == emptyStreamSrc) {
if (useStateStore().colorPickingMode || streamSrc.value === emptyStreamSrc) {
return { display: "none" };
} else {
return {};

View File

@@ -14,7 +14,7 @@ import { useStateStore } from "@/stores/StateStore";
{{ useStateStore().snackbarData.message }}
</p>
<v-progress-linear
v-if="useStateStore().snackbarData.progressBar != -1"
v-if="useStateStore().snackbarData.progressBar !== -1"
v-model="useStateStore().snackbarData.progressBar"
height="15"
:color="useStateStore().snackbarData.progressBarColor"

View File

@@ -38,7 +38,7 @@ const renderCompact = computed<boolean>(() => compact.value || !mdAndUp.value);
<v-list-item link to="/settings" prepend-icon="mdi-cog">
<v-list-item-title>Settings</v-list-item-title>
</v-list-item>
<v-list-item ref="camerasTabOpener" link to="/cameras" prepend-icon="mdi-camera">
<v-list-item link to="/cameras" prepend-icon="mdi-camera">
<v-list-item-title>Camera</v-list-item-title>
</v-list-item>
<v-list-item
@@ -73,7 +73,7 @@ const renderCompact = computed<boolean>(() => compact.value || !mdAndUp.value);
</v-list-item>
<v-list-item
link
:prepend-icon="theme.global.name.value === 'LightTheme' ? 'mdi-white-balance-sunny' : 'mdi-weather-night'"
:prepend-icon="theme.global.current.value.dark ? 'mdi-weather-night' : 'mdi-white-balance-sunny'"
@click="() => toggleTheme(theme)"
>
<v-list-item-title>Theme</v-list-item-title>

View File

@@ -28,7 +28,7 @@ const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
if (useCameraSettingsStore().currentCameraSettings.validVideoFormats.length === 0) return uniqueResolutions;
useCameraSettingsStore().currentCameraSettings.validVideoFormats.forEach((format) => {
const index = uniqueResolutions.findIndex((v) => resolutionsAreEqual(v.resolution, format.resolution));
const contains = index != -1;
const contains = index !== -1;
let skip = false;
if (contains && format.fps > uniqueResolutions[index].fps) {
uniqueResolutions.splice(index, 1);
@@ -132,7 +132,7 @@ const downloadCalibBoard = async () => {
const yPos = chessboardStartY + squareY * squareSizeIn.value;
// Only draw the odd squares to create the chessboard pattern
if (squareY % 2 != squareX % 2) {
if (squareY % 2 !== squareX % 2) {
doc.rect(xPos, yPos, squareSizeIn.value, squareSizeIn.value, "F");
}
}
@@ -293,7 +293,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
<v-card-subtitle v-if="!isCalibrating" class="pl-0 pb-3 pt-4 opacity-100"
>Configure New Calibration</v-card-subtitle
>
<v-form ref="form" v-model="settingsValid">
<v-form v-model="settingsValid">
<pv-select
v-model="uniqueVideoResolutionString"
label="Resolution"
@@ -470,7 +470,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
closable
density="compact"
class="mb-5"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
:color="useSettingsStore().general.mrCalWorking ? 'buttonPassive' : 'error'"
:icon="useSettingsStore().general.mrCalWorking ? 'mdi-check' : 'mdi-close'"
:text="
@@ -481,7 +481,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
/>
<div v-if="isCalibrating" class="d-flex justify-center align-center pb-5">
<v-chip
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
label
:color="useStateStore().calibrationData.hasEnoughImages ? 'buttonPassive' : 'light-grey'"
>
@@ -494,7 +494,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
color="buttonPassive"
size="small"
block
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
:disabled="!settingsValid"
@click="downloadCalibBoard"
>
@@ -509,7 +509,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
density="compact"
text="Too many corners. Finish calibration now!"
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
<div class="d-flex pt-5">
<v-col cols="6" class="pa-0 pr-2">
@@ -517,7 +517,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
size="small"
block
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
:disabled="!settingsValid || tooManyPoints"
@click="isCalibrating ? useCameraSettingsStore().takeCalibrationSnapshot() : startCalibration()"
>
@@ -531,7 +531,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
<v-btn
size="small"
block
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
:color="useStateStore().calibrationData.hasEnoughImages ? 'buttonActive' : 'error'"
:disabled="!isCalibrating || !settingsValid"
@click="endCalibration"

View File

@@ -40,8 +40,8 @@ const importCalibration = async () => {
const data = await parseJsonFile<CameraCalibrationResult>(uploadedJson);
if (
data.resolution.height != props.videoFormat.resolution.height ||
data.resolution.width != props.videoFormat.resolution.width
data.resolution.height !== props.videoFormat.resolution.height ||
data.resolution.width !== props.videoFormat.resolution.width
) {
useStateStore().showSnackbarMessage({
color: "error",
@@ -121,7 +121,7 @@ const viewingImg = ref(0);
<v-btn
color="buttonPassive"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="openUploadPhotonCalibJsonPrompt"
>
<v-icon start size="large"> mdi-import</v-icon>
@@ -140,7 +140,7 @@ const viewingImg = ref(0);
color="buttonPassive"
:disabled="!currentCalibrationCoeffs"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="openExportCalibrationPrompt"
>
<v-icon start size="large">mdi-export</v-icon>
@@ -318,7 +318,7 @@ const viewingImg = ref(0);
color="primary"
text="The selected video format has not been calibrated."
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
</div>
<Suspense v-else-if="tab === 'details'">
@@ -341,7 +341,7 @@ const viewingImg = ref(0);
<pv-delete-modal
v-model="confirmRemoveDialog.show"
:width="500"
:title="'Delete Calibration'"
title="Delete Calibration"
:description="`Are you sure you want to delete the calibration for '${confirmRemoveDialog.vf.resolution.width}x${confirmRemoveDialog.vf.resolution.height}'? This action cannot be undone.`"
:on-confirm="() => removeCalibration(confirmRemoveDialog.vf)"
/>

View File

@@ -99,7 +99,7 @@ const expanded = ref([]);
<v-card-text class="pt-0">
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'tonal'"
@click="fetchSnapshots"
>
<v-icon start class="open-icon" size="large"> mdi-folder </v-icon>
@@ -115,7 +115,7 @@ const expanded = ref([]);
density="compact"
text="There are currently no saved snapshots."
icon="mdi-information-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
</v-card-text>
<v-card-text v-else class="pt-0">
@@ -125,7 +125,7 @@ const expanded = ref([]);
density="compact"
text="Snapshot timestamps depend on when the coprocessor was last connected to the internet."
icon="mdi-information-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
<v-data-table
v-model:expanded="expanded"

View File

@@ -66,10 +66,10 @@ const settingsHaveChanged = (): boolean => {
const b = useCameraSettingsStore().currentCameraSettings;
for (const q in ValidQuirks) {
if (a.quirksToChange[q] != b.cameraQuirks.quirks[q]) return true;
if (a.quirksToChange[q] !== b.cameraQuirks.quirks[q]) return true;
}
return a.fov != b.fov.value;
return a.fov !== b.fov.value;
};
const resetTempSettingsStruct = () => {
@@ -179,7 +179,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
size="small"
color="buttonActive"
:disabled="!settingsHaveChanged()"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="saveCameraSettings"
>
<v-icon start size="large"> mdi-content-save </v-icon>
@@ -191,7 +191,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
block
size="small"
color="error"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (showDeleteCamera = true)"
>
<v-icon start size="large"> mdi-trash-can-outline </v-icon>

View File

@@ -103,7 +103,7 @@ const fpsTooLow = computed<boolean>(() => {
<v-btn
color="buttonPassive"
class="fill"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
:disabled="
useCameraSettingsStore().isDriverMode ||
useCameraSettingsStore().isCalibrationMode ||
@@ -116,7 +116,7 @@ const fpsTooLow = computed<boolean>(() => {
<v-btn
color="buttonPassive"
class="fill"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
:disabled="
useCameraSettingsStore().isDriverMode ||
useCameraSettingsStore().isCalibrationMode ||

View File

@@ -49,7 +49,7 @@ const confirmationText = ref("");
color="buttonActive"
style="float: right"
width="100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="onBackup"
>
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
@@ -65,7 +65,7 @@ const confirmationText = ref("");
? confirmationText.toLowerCase() !== expectedConfirmationText.toLowerCase()
: false
"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="
onConfirm();
confirmationText = '';

View File

@@ -407,14 +407,14 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<v-card-actions class="pr-5 pt-10px pb-5">
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="cancelPipelineCreation"
>
Cancel
</v-btn>
<v-btn
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
:disabled="checkPipelineName(newPipelineName) !== true"
@click="createNewPipeline"
>
@@ -441,7 +441,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<v-card-actions class="pa-5 pt-0">
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
class="text-black"
@click="cancelChangePipelineType"
>
@@ -449,7 +449,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-btn>
<v-btn
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="confirmChangePipelineType"
>
Confirm

View File

@@ -122,14 +122,14 @@ const onBeforeTabUpdate = () => {
density="compact"
text="Camera is not connected. Please check your connection and try again."
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
</template>
<template v-else>
<v-col
v-for="(tabGroupData, tabGroupIndex) in tabGroups"
:key="tabGroupIndex"
:cols="tabGroupIndex == 1 && useCameraSettingsStore().currentPipelineSettings.doMultiTarget ? 7 : ''"
:cols="tabGroupIndex === 1 && useCameraSettingsStore().currentPipelineSettings.doMultiTarget ? 7 : ''"
:class="tabGroupIndex !== tabGroups.length - 1 && 'pr-3'"
@vue:before-update="onBeforeTabUpdate"
>

View File

@@ -35,7 +35,7 @@ const processingMode = computed<number>({
<v-btn
color="buttonPassive"
:disabled="!useCameraSettingsStore().hasConnected"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
class="w-50"
>
<template #prepend>
@@ -48,10 +48,10 @@ const processingMode = computed<number>({
:disabled="
!useCameraSettingsStore().hasConnected ||
!useCameraSettingsStore().isCurrentVideoFormatCalibrated ||
useCameraSettingsStore().currentPipelineSettings.pipelineType == PipelineType.ObjectDetection ||
useCameraSettingsStore().currentPipelineSettings.pipelineType == PipelineType.ColoredShape
useCameraSettingsStore().currentPipelineSettings.pipelineType === PipelineType.ObjectDetection ||
useCameraSettingsStore().currentPipelineSettings.pipelineType === PipelineType.ColoredShape
"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
class="w-50"
>
<template #prepend>
@@ -69,7 +69,7 @@ const processingMode = computed<number>({
<v-btn
color="buttonPassive"
class="fill w-50"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
>
<v-icon start class="mode-btn-icon" size="large">mdi-import</v-icon>
<span class="mode-btn-label">Raw</span>
@@ -77,7 +77,7 @@ const processingMode = computed<number>({
<v-btn
color="buttonPassive"
class="fill w-50"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
>
<v-icon start class="mode-btn-icon" size="large">mdi-export</v-icon>
<span class="mode-btn-label">Processed</span>

View File

@@ -168,7 +168,7 @@ const interactiveCols = computed(() =>
block
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Single)"
>
Take Point
@@ -179,7 +179,7 @@ const interactiveCols = computed(() =>
size="small"
block
color="error"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Clear)"
>
Clear All Points
@@ -196,7 +196,7 @@ const interactiveCols = computed(() =>
block
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.DualFirst)"
>
Take First Point
@@ -208,7 +208,7 @@ const interactiveCols = computed(() =>
block
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.DualSecond)"
>
Take Second Point
@@ -219,7 +219,7 @@ const interactiveCols = computed(() =>
size="small"
block
color="error"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Clear)"
>
Clear All Points

View File

@@ -207,7 +207,7 @@ const resetCurrentBuffer = () => {
color="buttonActive"
class="mb-4 mt-1"
style="width: min-content"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="resetCurrentBuffer"
>Reset Samples</v-btn
>

View File

@@ -191,7 +191,7 @@ const interactiveCols = computed(() =>
block
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 2 : 3)"
>
<v-icon start size="large"> mdi-minus </v-icon>
@@ -204,7 +204,7 @@ const interactiveCols = computed(() =>
class="text-black"
size="small"
block
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="enableColorPicking(1)"
>
<v-icon start size="large"> mdi-plus-minus </v-icon>
@@ -217,7 +217,7 @@ const interactiveCols = computed(() =>
block
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 3 : 2)"
>
<v-icon start size="large"> mdi-plus </v-icon>
@@ -232,7 +232,7 @@ const interactiveCols = computed(() =>
color="primary"
class="text-black"
size="small"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="disableColorPicking"
>
Cancel

View File

@@ -312,7 +312,7 @@ watch(metricsHistorySnapshot, () => {
<v-col>
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="useStateStore().showLogModal = true"
>
<v-icon start class="open-icon" size="large"> mdi-eye </v-icon>
@@ -322,7 +322,7 @@ watch(metricsHistorySnapshot, () => {
<v-col>
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="openExportLogsPrompt"
>
<v-icon start class="open-icon" size="large"> mdi-download </v-icon>
@@ -345,7 +345,7 @@ watch(metricsHistorySnapshot, () => {
<v-col>
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (showImportDialog = true)"
>
<v-icon start class="open-icon" size="large"> mdi-import </v-icon>
@@ -355,7 +355,7 @@ watch(metricsHistorySnapshot, () => {
<v-col>
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="openExportSettingsPrompt"
>
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
@@ -369,7 +369,7 @@ watch(metricsHistorySnapshot, () => {
<v-col cols="12" sm="6"
><v-btn
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="restartProgram"
>
<v-icon start class="open-icon" size="large"> mdi-restart </v-icon>
@@ -379,7 +379,7 @@ watch(metricsHistorySnapshot, () => {
<v-col cols="12" sm="6">
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="openOfflineUpdatePrompt"
>
<v-icon start class="open-icon" size="large"> mdi-upload </v-icon>
@@ -400,7 +400,7 @@ watch(metricsHistorySnapshot, () => {
<v-col cols="12" sm="6">
<v-btn
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="restartDevice"
>
<v-icon start class="open-icon" size="large"> mdi-restart-alert </v-icon>
@@ -410,7 +410,7 @@ watch(metricsHistorySnapshot, () => {
<v-col cols="12" sm="6">
<v-btn
color="error"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (showFactoryReset = true)"
>
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
@@ -453,7 +453,7 @@ watch(metricsHistorySnapshot, () => {
<v-card-text class="pt-0 flex-0-0 pb-2">
<div class="d-flex justify-space-between pb-3 pt-3">
<span>CPU Temperature</span>
<span>{{ cpuTempData.at(-1)?.value == -1 ? "--- " : Math.round(cpuTempData.at(-1)?.value ?? 0) }}°C</span>
<span>{{ cpuTempData.at(-1)?.value === -1 ? "--- " : Math.round(cpuTempData.at(-1)?.value ?? 0) }}°C</span>
</div>
<Suspense>
<!-- Allows us to import echarts when it's actually needed -->
@@ -470,7 +470,10 @@ watch(metricsHistorySnapshot, () => {
tooltip="Measured rate for this coprocessor ONLY. This FMS limit is for ALL robot communication. If you are experiencing bandwidth issues while under this limit, check other sources."
/>
<span
>{{ networkUsageData.at(-1)?.value == -1 ? "---" : networkUsageData.at(-1)?.value.toFixed(3) }} Mb/s</span
>{{
networkUsageData.at(-1)?.value === -1 ? "---" : networkUsageData.at(-1)?.value.toFixed(3)
}}
Mb/s</span
>
</div>
<Suspense>
@@ -529,7 +532,7 @@ watch(metricsHistorySnapshot, () => {
<v-btn
color="primary"
:disabled="importFile === null"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="handleSettingsImport"
>
<v-icon start class="open-icon"> mdi-import </v-icon>
@@ -552,7 +555,7 @@ watch(metricsHistorySnapshot, () => {
<v-btn
color="buttonActive"
width="100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="
offlineUpdateDialog.show = false;
handleOfflineUpdate(offlineUpdate.files[0]);

View File

@@ -185,7 +185,7 @@ watchEffect(() => {
</v-card-title>
<div class="pa-5 pt-0">
<v-card-title class="pl-0 pt-0 pb-10px">Networking</v-card-title>
<v-form ref="form" v-model="settingsValid">
<v-form v-model="settingsValid">
<pv-input
v-model="tempSettingsStruct.ntServerAddress"
label="Team Number/NetworkTables Server Address"
@@ -205,7 +205,7 @@ watchEffect(() => {
density="compact"
text="The NetworkTables Server Address is not set or is invalid. NetworkTables is unable to connect."
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
<pv-radio
v-show="!useSettingsStore().network.networkingDisabled"
@@ -279,7 +279,7 @@ watchEffect(() => {
density="compact"
text="Cannot detect any wired connections! Send program logs to the developers for help."
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
<pv-switch
v-model="tempSettingsStruct.runNTServer"
@@ -293,7 +293,7 @@ watchEffect(() => {
density="compact"
text="This mode is intended for debugging and should be off for proper usage. PhotonLib will NOT work!"
icon="mdi-information-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
<v-card-title class="pl-0 pt-3 pb-10px">Miscellaneous</v-card-title>
<pv-switch
@@ -308,13 +308,13 @@ watchEffect(() => {
density="compact"
text="This mode is intended for debugging and may reduce performance; it should be off for field use."
icon="mdi-information-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
</v-form>
<v-btn
color="primary"
class="mt-3"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
style="color: black; width: 100%"
:disabled="!settingsValid || !settingsHaveChanged()"
@click="saveGeneralSettings"
@@ -377,7 +377,7 @@ watchEffect(() => {
</v-card-text>
<v-card-actions class="pa-5 pt-0">
<v-btn
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
color="buttonPassive"
class="text-black"
@click="showThemeConfig = false"
@@ -385,7 +385,7 @@ watchEffect(() => {
Close
</v-btn>
<v-btn
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
color="buttonActive"
class="text-black"
@click="

View File

@@ -53,9 +53,9 @@ const getOptions = (data: ChartData[] = []) => {
return `${tooltip}</div>`;
},
backgroundColor: theme.themes.value[theme.global.name.value].colors.background,
backgroundColor: theme.global.current.value.colors.background,
textStyle: {
color: theme.themes.value[theme.global.name.value].colors.onBackground
color: theme.global.current.value.colors.onBackground
},
axisPointer: {
animation: false
@@ -149,10 +149,9 @@ const getSeries = (data: ChartData[] = []) => {
: null,
lineStyle: {
width: 1.5,
color:
theme.global.name.value === "LightTheme"
? theme.themes.value[theme.global.name.value].colors.primary
: `rgb(${color.r}, ${color.g}, ${color.b})`
color: theme.global.current.value.dark
? `rgb(${color.r}, ${color.g}, ${color.b})`
: theme.global.current.value.colors.primary
},
areaStyle: {
color: {
@@ -164,17 +163,15 @@ const getSeries = (data: ChartData[] = []) => {
colorStops: [
{
offset: 0,
color:
theme.global.name.value === "LightTheme"
? `${theme.themes.value[theme.global.name.value].colors.primary}40`
: `rgba(${color.r}, ${color.g}, ${color.b}, 0.15)`
color: theme.global.current.value.dark
? `rgba(${color.r}, ${color.g}, ${color.b}, 0.15)`
: `${theme.global.current.value.colors.primary}40`
},
{
offset: 1,
color:
theme.global.name.value === "LightTheme"
? `${theme.themes.value[theme.global.name.value].colors.primary}40`
: `rgba(${color.r}, ${color.g}, ${color.b}, 0.15)`
color: theme.global.current.value.dark
? `rgba(${color.r}, ${color.g}, ${color.b}, 0.15)`
: `${theme.global.current.value.colors.primary}40`
}
]
}

View File

@@ -166,7 +166,7 @@ const handleBulkImport = async () => {
<v-btn
color="buttonActive"
class="justify-center"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (showImportDialog = true)"
>
<v-icon start class="open-icon"> mdi-import </v-icon>
@@ -245,7 +245,7 @@ const handleBulkImport = async () => {
importHeight === null ||
importVersion === null
"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="handleImport()"
>
<v-icon start class="open-icon" size="large"> mdi-import </v-icon>
@@ -260,7 +260,7 @@ const handleBulkImport = async () => {
<v-btn
color="buttonActive"
class="justify-center"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (showBulkImportDialog = true)"
>
<v-icon start class="open-icon"> mdi-import </v-icon>
@@ -278,7 +278,7 @@ const handleBulkImport = async () => {
color="buttonActive"
width="100%"
:disabled="importFile === null"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="handleBulkImport()"
>
<v-icon start class="open-icon" size="large"> mdi-import </v-icon>
@@ -292,7 +292,7 @@ const handleBulkImport = async () => {
<v-col cols="12" sm="6">
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="openExportPrompt"
>
<v-icon start class="open-icon"> mdi-export </v-icon>
@@ -309,7 +309,7 @@ const handleBulkImport = async () => {
<v-col cols="12" sm="6">
<v-btn
color="error"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (showNukeDialog = true)"
>
<v-icon left class="open-icon"> mdi-trash </v-icon>
@@ -339,7 +339,7 @@ const handleBulkImport = async () => {
small
color="error"
title="Delete Model"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (confirmDeleteDialog = { show: true, model })"
>
<v-icon size="large">mdi-trash-can-outline</v-icon>
@@ -351,7 +351,7 @@ const handleBulkImport = async () => {
small
color="buttonActive"
title="Rename Model"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (showRenameDialog = { show: true, model, newName: '' })"
>
<v-icon size="large">mdi-pencil</v-icon>
@@ -362,7 +362,7 @@ const handleBulkImport = async () => {
icon
small
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="() => (showInfo = { show: true, model })"
>
<v-icon size="large">mdi-information</v-icon>
@@ -391,13 +391,13 @@ const handleBulkImport = async () => {
</div>
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
<v-btn
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
color="error"
@click="showRenameDialog.show = false"
>Cancel</v-btn
>
<v-btn
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
color="buttonActive"
@click="renameModel(showRenameDialog.model, showRenameDialog.newName)"
>Rename</v-btn
@@ -413,7 +413,7 @@ const handleBulkImport = async () => {
<v-btn
color="buttonPassive"
width="100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="openExportIndividualModelPrompt"
>
<v-icon left class="open-icon" size="large"> mdi-export </v-icon>
@@ -446,8 +446,8 @@ const handleBulkImport = async () => {
:on-backup="openExportPrompt"
:on-confirm="nukeModels"
title="Delete and Reset All Object Detection Models"
:description="'This will delete ALL object detection models and re-extract the default object detection models. This action cannot be undone.'"
:expected-confirmation-text="'Delete Models'"
description="This will delete ALL object detection models and re-extract the default object detection models. This action cannot be undone."
expected-confirmation-text="Delete Models"
delete-text="Delete all models"
/>
</v-card>

View File

@@ -2,7 +2,7 @@ import { type ThemeInstance } from "vuetify";
import { LightTheme, DarkTheme } from "@/plugins/vuetify";
export const resetTheme = (theme: ThemeInstance) => {
const themeType = theme.global.name.value === "LightTheme" ? "light" : "dark";
const themeType = theme.global.current.value.dark ? "dark" : "light";
localStorage.removeItem(`${themeType}-background`);
localStorage.removeItem(`${themeType}-primary`);
localStorage.removeItem(`${themeType}-secondary`);
@@ -12,13 +12,13 @@ export const resetTheme = (theme: ThemeInstance) => {
};
export const getThemeColor = (theme: ThemeInstance, color: string): string => {
const themeType = theme.global.name.value === "LightTheme" ? "light" : "dark";
const defaultTheme = theme.global.name.value === "LightTheme" ? LightTheme : DarkTheme;
const themeType = theme.global.current.value.dark ? "dark" : "light";
const defaultTheme = theme.global.current.value.dark ? DarkTheme : LightTheme;
return localStorage.getItem(`${themeType}-${color}`) ?? defaultTheme.colors![color]!;
};
export const setThemeColor = (theme: ThemeInstance, color: string, value: string | null) => {
const themeType = theme.global.name.value === "LightTheme" ? "light" : "dark";
const themeType = theme.global.current.value.dark ? "dark" : "light";
if (value) localStorage.setItem(`${themeType}-${color}`, value);
else localStorage.removeItem(`${themeType}-${color}`);
@@ -38,7 +38,7 @@ export const restoreThemeConfig = (theme: ThemeInstance) => {
if (storedTheme) theme.global.name.value = storedTheme;
// Restore custom theme colors
const themeType = theme.global.name.value === "LightTheme" ? "light" : "dark";
const themeType = theme.global.current.value.dark ? "dark" : "light";
const defaultTheme = theme.global.name.value === "LightTheme" ? LightTheme : DarkTheme;
const customBackground = localStorage.getItem(`${themeType}-background`);
@@ -47,7 +47,7 @@ export const restoreThemeConfig = (theme: ThemeInstance) => {
const customSurface = localStorage.getItem(`${themeType}-surface`);
theme.themes.value[theme.global.name.value].colors.background = customBackground ?? defaultTheme.colors!.background!;
theme.themes.value[theme.global.name.value].colors.sidebar = theme.themes.value[theme.global.name.value].dark
theme.themes.value[theme.global.name.value].colors.sidebar = theme.global.current.value.dark
? (customBackground ?? defaultTheme.colors!.sidebar!)
: (customSurface ?? defaultTheme.colors!.sidebar!);

View File

@@ -71,10 +71,10 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.DriverMode;
},
isCalibrationMode(): boolean {
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.Calib3d;
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.Calib3d;
},
isFocusMode(): boolean {
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.FocusCamera;
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.FocusCamera;
},
isCSICamera(): boolean {
return this.currentCameraSettings.isCSICamera;

View File

@@ -232,7 +232,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
<v-btn
color="buttonPassive"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="
setCameraView(
module.matchedCameraInfo,
@@ -248,7 +248,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
class="text-black"
color="buttonActive"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
:loading="deactivatingModule"
@click="deactivateModule(module.uniqueName)"
>
@@ -261,7 +261,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
color="error"
style="width: 100%"
:loading="module.uniqueName === deletingCamera"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="
() =>
(confirmDeleteDialog = {
@@ -326,7 +326,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
<v-btn
color="buttonPassive"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="
setCameraView(
module.matchedCameraInfo,
@@ -342,7 +342,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
class="text-black"
color="buttonActive"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
:loading="activatingModule"
@click="activateModule(module.uniqueName)"
>
@@ -355,7 +355,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
color="error"
style="width: 100%"
:loading="module.uniqueName === deletingCamera"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="
() =>
(confirmDeleteDialog = {
@@ -393,7 +393,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
<v-btn
color="buttonPassive"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="setCameraView(camera, false)"
>
<span>Details</span>
@@ -405,7 +405,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
color="buttonActive"
style="width: 100%"
:loading="assigningCamera"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
@click="assignCamera(camera)"
>
Activate
@@ -457,7 +457,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
density="compact"
text="A different camera may have been connected to this device! Compare the following information carefully."
icon="mdi-information-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
/>
<PvCameraMatchCard :saved="viewingCamera[0]" :current="getMatchedDevice(viewingCamera[0])" />
</v-card-text>

View File

@@ -92,7 +92,7 @@ const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfigurat
color="error"
density="compact"
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
>
<span>
Arducam camera detected! Please configure the camera model in the <a href="#/cameras">Camera tab</a>!
@@ -104,7 +104,7 @@ const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfigurat
color="error"
density="compact"
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
>
<span>
Conflicting hostname detected! Please change the hostname in the <a href="#/settings">Settings tab</a>!
@@ -116,7 +116,7 @@ const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfigurat
color="error"
density="compact"
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
>
<span
>One or more cameras have an FPS limit set! This may cause performance issues. Check your logs for more
@@ -129,7 +129,7 @@ const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfigurat
color="error"
density="compact"
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
>
<span
>Conflicting camera name(s) detected! Please change the name(s) of

View File

@@ -25,7 +25,7 @@ export default defineConfig({
}
},
build: {
rollupOptions: {
rolldownOptions: {
external: ["html2canvas", "dompurify", "canvg"]
},
sourcemap: true

View File

@@ -17,8 +17,6 @@
package org.photonvision.vision.camera;
import com.fasterxml.jackson.annotation.JsonAlias;
public enum CameraQuirk {
/** Camera settable for controllable image gain */
Gain,
@@ -29,7 +27,6 @@ public enum CameraQuirk {
/** Cap at 100FPS for high-bandwidth cameras */
FPSCap100,
/** Separate red/blue gain controls available */
@JsonAlias("AWBGain") // remove after https://github.com/PhotonVision/photonvision/issues/1488
AwbRedBlueGain,
/** Will not work with photonvision - Logitech C270 at least */
CompletelyBroken,
@@ -43,11 +40,8 @@ public enum CameraQuirk {
* Camera is an arducam USB ov9281 which has a funky exposure issue where it is defined in v4l as
* 1-5000 instead of 1-75
*/
@JsonAlias("ArduOV9281") // remove after https://github.com/PhotonVision/photonvision/issues/1488
ArduOV9281Controls,
/** Dummy quirk to tell OV2311 from OV9281 */
// from 2024.3.1
@JsonAlias("ArduOV2311") // remove after https://github.com/PhotonVision/photonvision/issues/1488
ArduOV2311Controls,
ArduOV9782Controls,
/**

View File

@@ -170,10 +170,12 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
@Override
public void setAutoExposure(boolean cameraAutoExposure) {
if ((configuration.cameraQuirks.hasQuirk(CameraQuirk.ArduOV9281Controls)
|| configuration.cameraQuirks.hasQuirk(CameraQuirk.ArduOV9782Controls))
|| configuration.cameraQuirks.hasQuirk(CameraQuirk.ArduOV9782Controls)
|| configuration.cameraQuirks.hasQuirk(CameraQuirk.ArduOV2311Controls))
&& !cameraAutoExposure) {
// OV9281 and OV9782 on Linux seems to sometimes ignore our exposure requests on first boot if
// we're in manual mode. Poking the camera into and out of auto exposure seems to fix it.
// OV9281, OV9782, and OV2311 on Linux seems to sometimes ignore our exposure requests on
// first boot if we're in manual mode. Poking the camera into and out of auto exposure seems
// to fix it.
try {
setAutoExposureImpl(false);
Thread.sleep(2000);

View File

@@ -19,8 +19,8 @@ package org.photonvision.vision.objects;
import java.awt.Color;
import java.lang.ref.Cleaner;
import java.lang.ref.Cleaner.Cleanable;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.photonvision.common.logging.LogGroup;
@@ -36,8 +36,11 @@ public class RknnObjectDetector implements ObjectDetector {
/** Cleaner instance to release the detector when it goes out of scope */
private final Cleaner cleaner = Cleaner.create();
/** Atomic boolean to ensure that the native object can only be released once. */
private AtomicBoolean released = new AtomicBoolean(false);
private final Cleanable cleanable;
private static Runnable cleanupAction(long ptr) {
return () -> RknnJNI.destroy(ptr);
}
/** Pointer to the native object */
private final long objPointer;
@@ -80,7 +83,7 @@ public class RknnObjectDetector implements ObjectDetector {
logger.debug("Created detector for model " + model.modelFile.getName());
// Register the cleaner to release the detector when it goes out of scope
cleaner.register(this, this::release);
cleanable = cleaner.register(this, cleanupAction(objPointer));
}
/**
@@ -137,17 +140,6 @@ public class RknnObjectDetector implements ObjectDetector {
/** Thread-safe method to release the detector. */
@Override
public void release() {
// Checks if the atomic is 'false', and if so, sets it to 'true'
if (released.compareAndSet(false, true)) {
if (objPointer <= 0) {
logger.error(
"Detector is not initialized, and so it can't be released! Model: "
+ model.modelFile.getName());
return;
}
RknnJNI.destroy(objPointer);
logger.debug("Released detector for model " + model.modelFile.getName());
}
cleanable.clean();
}
}

View File

@@ -19,8 +19,8 @@ package org.photonvision.vision.objects;
import java.awt.Color;
import java.lang.ref.Cleaner;
import java.lang.ref.Cleaner.Cleanable;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.photonvision.common.logging.LogGroup;
@@ -36,8 +36,11 @@ public class RubikObjectDetector implements ObjectDetector {
/** Cleaner instance to release the detector when it goes out of scope */
private final Cleaner cleaner = Cleaner.create();
/** Atomic boolean to ensure that the native object can only be released once. */
private AtomicBoolean released = new AtomicBoolean(false);
private final Cleanable cleanable;
private static Runnable cleanupAction(long ptr) {
return () -> RubikJNI.destroy(ptr);
}
/** Pointer to the native object */
private final long ptr;
@@ -88,7 +91,7 @@ public class RubikObjectDetector implements ObjectDetector {
logger.debug("Created detector for model " + model.modelFile.getName());
// Register the cleaner to release the detector when it goes out of scope
cleaner.register(this, this::release);
cleanable = cleaner.register(this, cleanupAction(ptr));
}
/**
@@ -146,17 +149,7 @@ public class RubikObjectDetector implements ObjectDetector {
/** Thread-safe method to release the detector. */
@Override
public void release() {
// Checks if the atomic is 'false', and if so, sets it to 'true'
if (released.compareAndSet(false, true)) {
if (!isValid()) {
logger.error(
"Detector is not initialized, and so it can't be released! Model: "
+ model.modelFile.getName());
return;
}
RubikJNI.destroy(ptr);
logger.debug("Released detector for model " + model.modelFile.getName());
}
cleanable.clean();
}
private boolean isValid() {

View File

@@ -150,7 +150,7 @@ public class OutputStreamPipeline {
if (!(settings instanceof AprilTagPipelineSettings)
&& !(settings instanceof ArucoPipelineSettings)
&& !(settings instanceof Calibration3dPipelineSettings)) {
// If we're processing anything other than Apriltags..
// If we're processing anything other than AprilTags or Aruco targets
var draw2dCrosshairResultOnOutput = draw2dCrosshairPipe.run(Pair.of(outMat, targetsToDraw));
sumPipeNanosElapsed += pipeProfileNanos[4] = draw2dCrosshairResultOnOutput.nanosElapsed;
@@ -203,7 +203,7 @@ public class OutputStreamPipeline {
}
} else if (settings instanceof ArucoPipelineSettings) {
if (settings.solvePNPEnabled) {
// Draw 3d Apriltag markers (camera is calibrated and running in 3d mode)
// Draw 3d aruco markers (camera is calibrated and running in 3d mode)
pipeProfileNanos[5] = 0;
pipeProfileNanos[6] = 0;
@@ -213,7 +213,7 @@ public class OutputStreamPipeline {
pipeProfileNanos[8] = 0;
} else {
// Draw 2d apriltag markers
// Draw 2d aruco markers
var draw2dTargetsOnInput = draw2dArucoPipe.run(Pair.of(outMat, targetsToDraw));
sumPipeNanosElapsed += pipeProfileNanos[5] = draw2dTargetsOnInput.nanosElapsed;

View File

@@ -368,6 +368,15 @@ class PhotonCameraSim:
noisyTargetCorners,
)
# Compute object detection confidence if this is an obj det target
classId = tgt.objDetClassId
conf = tgt.objDetConf
if classId >= 0 and conf < 0:
# Simulate confidence using sqrt-scaled area for a more realistic
# curve. Raw areaPercent/100 is tiny for most targets; sqrt scaling
# gives reasonable values even for small-but-visible objects.
conf = max(0.0, min(1.0, math.sqrt(areaPercent / 100.0) * 2.0))
smallVec: list[TargetCorner] = []
for corner in minAreaRectPts:
smallVec.append(TargetCorner(corner[0], corner[1]))
@@ -381,6 +390,8 @@ class PhotonCameraSim:
area=areaPercent,
skew=math.degrees(centerRot.X()),
fiducialId=tgt.fiducialId,
objDetectId=classId,
objDetectConf=conf,
detectedCorners=cornersFloat,
minAreaRectCorners=smallVec,
bestCameraToTarget=pnpSim.best if pnpSim else Transform3d(),

View File

@@ -1,4 +1,5 @@
import math
from typing import overload
from wpimath.geometry import Pose3d, Translation3d
@@ -8,19 +9,78 @@ from ..estimation.targetModel import TargetModel
class VisionTargetSim:
"""Describes a vision target located somewhere on the field that your vision system can detect."""
def __init__(self, pose: Pose3d, model: TargetModel, id: int = -1):
"""Describes a fiducial tag located somewhere on the field that your vision system can detect.
@overload
def __init__(self, pose: Pose3d, model: TargetModel) -> None:
"""
Describes a retro-reflective/colored shape vision target located somewhere on the field that
your vision system can detect.
:param pose: Pose3d of the target in field-relative coordinates
:param model: TargetModel which describes the shape of the target
"""
...
@overload
def __init__(self, pose: Pose3d, model: TargetModel, id: int) -> None:
"""
Describes a fiducial tag located somewhere on the field that your vision system can detect.
:param pose: Pose3d of the tag in field-relative coordinates
:param model: TargetModel which describes the shape of the target(tag)
:param model: TargetModel which describes the geometry of the target (tag)
:param id: The ID of this fiducial tag
"""
...
@overload
def __init__(
self, pose: Pose3d, model: TargetModel, objDetClassId: int, objDetConf: float
) -> None:
"""
Describes an object-detection vision target located somewhere on the field that your vision
system can detect.
:param pose: Pose3d of the target in field-relative coordinates
:param model: TargetModel which describes the shape of the target
:param objDetClassId: The object detection class ID, or -1 to exclude from object detection
:param objDetConf: The object detection confidence, or -1.0 to compute from target area
in the camera's field of view
"""
...
def __init__(
self,
pose: Pose3d,
model: TargetModel,
*args,
**kwargs,
):
if kwargs:
raise TypeError(
f"VisionTargetSim does not accept keyword arguments: {list(kwargs.keys())}"
)
self.pose: Pose3d = pose
self.model: TargetModel = model
self.fiducialId: int = id
self.objDetClassId: int = -1
self.objDetConf: float = -1.0
if len(args) == 0:
# VisionTargetSim(pose, model)
self.fiducialId: int = -1
self.objDetClassId: int = -1
self.objDetConf: float = -1.0
elif len(args) == 1:
# VisionTargetSim(pose, model, id)
self.fiducialId = args[0]
self.objDetClassId = -1
self.objDetConf = -1.0
elif len(args) == 2:
# VisionTargetSim(pose, model, objDetClassId, objDetConf)
self.fiducialId = -1
self.objDetClassId = args[0]
self.objDetConf = args[1]
else:
raise ValueError(
f"VisionTargetSim takes 2-4 arguments, got {2 + len(args)}"
)
def __lt__(self, right) -> bool:
return self.pose.translation().norm() < right.pose.translation().norm()

View File

@@ -525,6 +525,16 @@ public class PhotonCameraSim implements AutoCloseable {
.get();
}
// If object detection (user classId valid) but conf wasn't provided, estimate
int classId = tgt.objDetClassId;
float conf = tgt.objDetConf;
if (classId >= 0 && conf < 0) {
// Simulate confidence using sqrt-scaled area for a more realistic
// curve. Raw areaPercent/100 is tiny for most targets; sqrt scaling
// gives reasonable values even for small-but-visible objects.
conf = (float) MathUtil.clamp(Math.sqrt(areaPercent / 100.0) * 2.0, 0.0, 1.0);
}
detectableTgts.add(
new PhotonTrackedTarget(
-Math.toDegrees(centerRot.getZ()),
@@ -532,8 +542,8 @@ public class PhotonCameraSim implements AutoCloseable {
areaPercent,
Math.toDegrees(centerRot.getX()),
tgt.fiducialID,
-1,
-1,
classId,
conf,
pnpSim.best,
pnpSim.alt,
pnpSim.ambiguity,

View File

@@ -36,16 +36,21 @@ public class VisionTargetSim {
public final int fiducialID;
/** The object detection class ID, or -1 if not applicable. */
public final int objDetClassId;
/** The object detection confidence, or -1 if not applicable. */
public final float objDetConf;
/**
* Describes a vision target located somewhere on the field that your vision system can detect.
* Describes a retro-reflective/colored shape vision target located somewhere on the field that
* your vision system can detect.
*
* @param pose Pose3d of the tag in field-relative coordinates
* @param model TargetModel which describes the geometry of the target
*/
public VisionTargetSim(Pose3d pose, TargetModel model) {
this.pose = pose;
this.model = model;
this.fiducialID = -1;
this(pose, model, -1, -1, -1);
}
/**
@@ -56,9 +61,33 @@ public class VisionTargetSim {
* @param id The ID of this fiducial tag
*/
public VisionTargetSim(Pose3d pose, TargetModel model, int id) {
this(pose, model, id, -1, -1);
}
/**
* Describes an object-detection vision target located somewhere on the field that your vision
* system can detect. Class ID is the (zero-indexed) index of the object's class ID in the list of
* all classes. Confidence can be specified, or pass -1 to estimate confidence based on 2 *
* sqrt(target area / total image area)
*
* @param pose Pose3d of the target in field-relative coordinates
* @param model TargetModel which describes the geometry of the target
* @param objDetClassId The object detection class ID, if -1 it will not be detected by object
* detection
* @param objDetConf The object detection confidence, or -1 in which case the simulation will
* compute a confidence based on the area of the target in the camera's field of view
*/
public VisionTargetSim(Pose3d pose, TargetModel model, int objDetClassId, float objDetConf) {
this(pose, model, -1, objDetClassId, objDetConf);
}
private VisionTargetSim(
Pose3d pose, TargetModel model, int id, int objDetClassId, float objDetConf) {
this.pose = pose;
this.model = model;
this.fiducialID = id;
this.objDetClassId = objDetClassId;
this.objDetConf = objDetConf;
}
/**
@@ -97,7 +126,11 @@ public class VisionTargetSim {
return model;
}
/** This target's vertices offset from its field pose. */
/**
* This target's vertices offset from its field pose.
*
* @return A vector of Translation3d representing the vertices of the target
*/
public List<Translation3d> getFieldVertices() {
return model.getFieldVertices(pose);
}

View File

@@ -35,11 +35,10 @@
#include <hal/FRCUsageReporting.h>
#include <net/TimeSyncServer.h>
#include <opencv2/core.hpp>
#include <opencv2/core/mat.hpp>
#include <opencv2/core/utility.hpp>
#include <wpi/json.h>
#include "PhotonVersion.h"
#include "opencv2/core/utility.hpp"
#include "photon/dataflow/structures/Packet.h"
static constexpr units::second_t WARN_DEBOUNCE_SEC = 5_s;

View File

@@ -26,7 +26,6 @@
#include <limits>
#include <optional>
#include <span>
#include <utility>
#include <vector>

View File

@@ -32,6 +32,11 @@
#include <frc/apriltag/AprilTagFieldLayout.h>
#include <frc/apriltag/AprilTagFields.h>
#include "photon/estimation/CameraTargetRelation.h"
#include "photon/estimation/RotTrlTransform3d.h"
#include "photon/estimation/VisionEstimation.h"
#include "photon/simulation/VideoSimUtil.h"
namespace photon {
PhotonCameraSim::PhotonCameraSim(PhotonCamera* camera)
: PhotonCameraSim(camera, photon::SimCameraProperties::PERFECT_90DEG(),
@@ -214,12 +219,23 @@ PhotonPipelineResult PhotonCameraSim::Process(
}
std::optional<photon::PnpResult> pnpSim = std::nullopt;
if (tgt.fiducialId >= 0 && tgt.GetFieldVertices().size() == 4) {
if (tgt.GetFiducialId() >= 0 && tgt.GetFieldVertices().size() == 4) {
pnpSim = OpenCVHelp::SolvePNP_Square(
prop.GetIntrinsics(), prop.GetDistCoeffs(),
tgt.GetModel().GetVertices(), noisyTargetCorners);
}
// Compute object detection confidence if this is an obj det target
int classId = tgt.GetObjDetClassId();
float conf = tgt.GetObjDetConf();
if (classId >= 0 && conf < 0) {
// Simulate confidence using sqrt-scaled area for a more realistic
// curve. Raw areaPercent/100 is tiny for most targets; sqrt scaling
// gives reasonable values even for small-but-visible objects.
conf = static_cast<float>(
std::clamp(std::sqrt(areaPercent / 100.0) * 2.0, 0.0, 1.0));
}
std::vector<std::pair<float, float>> tempCorners =
OpenCVHelp::PointsToCorners(minAreaRectPts);
std::vector<TargetCorner> smallVec;
@@ -236,8 +252,8 @@ PhotonPipelineResult PhotonCameraSim::Process(
detectableTgts.emplace_back(
-centerRot.Z().convert<units::degrees>().to<double>(),
-centerRot.Y().convert<units::degrees>().to<double>(), areaPercent,
centerRot.X().convert<units::degrees>().to<double>(), tgt.fiducialId,
tgt.objDetClassId, tgt.objDetConf,
centerRot.X().convert<units::degrees>().to<double>(),
tgt.GetFiducialId(), classId, conf,
pnpSim ? pnpSim->best : frc::Transform3d{},
pnpSim ? pnpSim->alt : frc::Transform3d{},
pnpSim ? pnpSim->ambiguity : -1, smallVec, cornersDouble);
@@ -254,8 +270,8 @@ PhotonPipelineResult PhotonCameraSim::Process(
VisionTargetSim tgt = pair.first;
std::vector<cv::Point2f> corners = pair.second;
if (tgt.fiducialId > 0) {
VideoSimUtil::Warp165h5TagImage(tgt.fiducialId, corners, true,
if (tgt.GetFiducialId() > 0) {
VideoSimUtil::Warp165h5TagImage(tgt.GetFiducialId(), corners, true,
videoSimFrameRaw);
} else if (!tgt.GetModel().GetIsSpherical()) {
std::vector<cv::Point2f> contour = corners;

View File

@@ -28,6 +28,8 @@
#include <utility>
#include <vector>
#include <frc/Errors.h>
using namespace photon;
void SimCameraProperties::SetCalibration(int width, int height,

View File

@@ -31,7 +31,6 @@
#include <frc/Alert.h>
#include <networktables/BooleanTopic.h>
#include <networktables/DoubleArrayTopic.h>
#include <networktables/DoubleTopic.h>
#include <networktables/IntegerTopic.h>
#include <networktables/MultiSubscriber.h>
#include <networktables/NetworkTable.h>

View File

@@ -29,7 +29,7 @@
#include <frc/geometry/Rotation3d.h>
#include <frc/geometry/Transform3d.h>
#include <frc/interpolation/TimeInterpolatableBuffer.h>
#include <opencv2/core/mat.hpp>
#include <wpi/SmallVector.h>
#include "photon/PhotonCamera.h"
#include "photon/targeting/PhotonPipelineResult.h"

View File

@@ -26,11 +26,7 @@
#include <cmath>
#include "photon/dataflow/structures/Packet.h"
#include "photon/targeting/MultiTargetPNPResult.h"
#include "photon/targeting/PhotonPipelineResult.h"
#include "photon/targeting/PhotonTrackedTarget.h"
#include "photon/targeting/PnpResult.h"
namespace photon {

View File

@@ -32,12 +32,6 @@
#include <units/length.h>
#include <units/math.h>
#include "photon/dataflow/structures/Packet.h"
#include "photon/targeting/MultiTargetPNPResult.h"
#include "photon/targeting/PhotonPipelineResult.h"
#include "photon/targeting/PhotonTrackedTarget.h"
#include "photon/targeting/PnpResult.h"
namespace photon {
class PhotonUtils {
public:

View File

@@ -24,23 +24,15 @@
#pragma once
#include <algorithm>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include <cameraserver/CameraServer.h>
#include <frc/Timer.h>
#include <frc/apriltag/AprilTagFieldLayout.h>
#include <frc/apriltag/AprilTagFields.h>
#include <photon/PhotonCamera.h>
#include <photon/PhotonTargetSortMode.h>
#include <photon/estimation/CameraTargetRelation.h>
#include <photon/estimation/VisionEstimation.h>
#include <photon/networktables/NTTopicSet.h>
#include <photon/simulation/SimCameraProperties.h>
#include <photon/simulation/VideoSimUtil.h>
#include <photon/simulation/VisionTargetSim.h>
#include <units/math.h>
#include <wpi/timestamp.h>

View File

@@ -24,15 +24,12 @@
#pragma once
#include <algorithm>
#include <random>
#include <string>
#include <utility>
#include <vector>
#include <Eigen/Core>
#include <frc/Errors.h>
#include <frc/MathUtil.h>
#include <frc/geometry/Rotation2d.h>
#include <frc/geometry/Translation3d.h>
#include <photon/estimation/OpenCVHelp.h>

View File

@@ -38,8 +38,7 @@
#include <opencv2/objdetect.hpp>
#include <units/length.h>
#include "SimCameraProperties.h"
#include "photon/estimation/RotTrlTransform3d.h"
#include "photon/simulation/SimCameraProperties.h"
namespace mathutil {
template <typename T>

View File

@@ -346,11 +346,14 @@ class VisionSystemSim {
const std::vector<VisionTargetSim>& targets) {
std::vector<VisionTargetSim> removedList;
for (auto& entry : targetSets) {
for (auto target : entry.second) {
auto it = std::find(targets.begin(), targets.end(), target);
if (it != targets.end()) {
removedList.emplace_back(target);
entry.second.erase(it);
auto& vec = entry.second;
auto it = vec.begin();
while (it != vec.end()) {
if (std::find(targets.begin(), targets.end(), *it) != targets.end()) {
removedList.emplace_back(*it);
it = vec.erase(it);
} else {
++it;
}
}
}

View File

@@ -31,23 +31,120 @@
#include "photon/estimation/TargetModel.h"
namespace photon {
/** Describes a vision target located somewhere on the field that your vision
* system can detect. */
class VisionTargetSim {
public:
/**
* Describes a retro-reflective/colored shape vision target located somewhere
* on the field that your vision system can detect.
*
* @param pose Pose3d of the tag in field-relative coordinates
* @param model TargetModel which describes the geometry of the target
*/
VisionTargetSim(const frc::Pose3d& pose, const TargetModel& model)
: fiducialId(-1), pose(pose), model(model) {}
: fiducialId(-1),
objDetClassId(-1),
objDetConf(-1),
pose(pose),
model(model) {}
/**
* Describes a fiducial tag located somewhere on the field that your vision
* system can detect.
*
* @param pose Pose3d of the tag in field-relative coordinates
* @param model TargetModel which describes the geometry of the target(tag)
* @param id The ID of this fiducial tag
*/
VisionTargetSim(const frc::Pose3d& pose, const TargetModel& model, int id)
: fiducialId(id), pose(pose), model(model) {}
: fiducialId(id),
objDetClassId(-1),
objDetConf(-1),
pose(pose),
model(model) {}
/**
* Describes an object-detection vision target located somewhere on the field
* that your vision system can detect. Class ID is the (zero-indexed) index of
* the object's class ID in the list of all classes. Confidence can be
* specified, or pass -1 to estimate confidence based on 2 * sqrt(target area
* / total image area)
*
* @param pose Pose3d of the target in field-relative coordinates
* @param model TargetModel which describes the geometry of the target
* @param objDetClassId The object detection class ID, if -1 it will not be
* detected by object detection
* @param objDetConf The object detection confidence, or -1 in which case the
* simulation will compute a confidence based on the area of the target in the
* camera's field of view
*/
VisionTargetSim(const frc::Pose3d& pose, const TargetModel& model,
int objDetClassId, float objDetConf)
: fiducialId(-1),
objDetClassId(objDetClassId),
objDetConf(objDetConf),
pose(pose),
model(model) {}
/**
* Sets the pose of this target on the field.
*
* @param newPose The pose in field-relative coordinates
*/
void SetPose(const frc::Pose3d& newPose) { pose = newPose; }
/**
* Sets the model describing this target's geometry.
*
* @param newModel The model of the target
*/
void SetModel(const TargetModel& newModel) { model = newModel; }
/**
* Returns the pose of this target on the field.
*
* @return The pose in field-relative coordinates
*/
frc::Pose3d GetPose() const { return pose; }
/**
* Returns the model describing this target's geometry.
*
* @return The model of the target
*/
TargetModel GetModel() const { return model; }
/**
* Returns the fiducial ID of this target, or -1 if not a fiducial target.
*
* @return The fiducial ID
*/
int GetFiducialId() const { return fiducialId; }
/**
* Returns the object detection class ID of this target, or -1 if not an
* object detection target.
*
* @return The object detection class ID
*/
int GetObjDetClassId() const { return objDetClassId; }
/**
* Returns the object detection confidence of this target, or -1 if
* confidence is estimated from target area or is not an object.
*
* @return The object detection confidence
*/
float GetObjDetConf() const { return objDetConf; }
/**
* This target's vertices offset from its field pose.
* @return A vector of Translation3d representing the vertices of the target
*/
std::vector<frc::Translation3d> GetFieldVertices() const {
return model.GetFieldVertices(pose);
}
int fiducialId;
int objDetClassId = -1;
float objDetConf = -1;
bool operator<(const VisionTargetSim& right) const {
return pose.Translation().Norm() < right.pose.Translation().Norm();
@@ -70,6 +167,9 @@ class VisionTargetSim {
}
private:
int fiducialId;
int objDetClassId;
float objDetConf;
frc::Pose3d pose;
TargetModel model;
};

View File

@@ -601,7 +601,49 @@ class VisionSystemSimTest {
robotPose = new Pose2d(-2, -2, Rotation2d.fromDegrees(30));
visionSysSim.update(robotPose);
ambiguity = waitForSequenceNumber(camera, 2).getBestTarget().getPoseAmbiguity();
var target2 = waitForSequenceNumber(camera, 2).getBestTarget();
ambiguity = target2.getPoseAmbiguity();
assertTrue(0 < ambiguity && ambiguity < 0.2, "Tag ambiguity expected to be low");
// and prove that object detection class id/conf are -1 when we look at a tag
assertEquals(-1, target2.objDetectId);
assertEquals(-1, target2.objDetectConf);
}
@Test
public void testObjectDetection() {
var visionSysSim = new VisionSystemSim("Test");
var camera = new PhotonCamera(inst, "camera");
var cameraSim = new PhotonCameraSim(camera);
visionSysSim.addCamera(cameraSim, new Transform3d());
cameraSim.prop.setCalibration(640, 480, Rotation2d.fromDegrees(80));
cameraSim.setMinTargetAreaPixels(20.0);
final var targetPose = new Pose3d(new Translation3d(2, 0, 0), new Rotation3d(0, 0, Math.PI));
final int classId = 3;
final float conf = 0.67f;
final TargetModel ballModel = new TargetModel(Units.inchesToMeters(6));
final var ballTarget = new VisionTargetSim(targetPose, ballModel, classId, conf);
visionSysSim.addVisionTargets(ballTarget);
var robotPose = Pose2d.kZero;
visionSysSim.update(robotPose);
var target1 = waitForSequenceNumber(camera, 1).getBestTarget();
assertEquals(classId, target1.objDetectId);
assertEquals(conf, target1.objDetectConf);
assertEquals(-1, target1.fiducialId);
// much around with the target to force PhotonCameraSim::process calculate conf
visionSysSim.removeVisionTargets(ballTarget);
final float conf2 = -1;
final var ballTarget2 = new VisionTargetSim(targetPose, ballModel, classId, conf2);
visionSysSim.addVisionTargets(ballTarget2);
visionSysSim.update(robotPose);
var target2 = waitForSequenceNumber(camera, 2).getBestTarget();
assertEquals(classId, target2.objDetectId);
// 2 * sqrt(area pixels) at this particular pose
assertEquals(0.131, target2.objDetectConf, 0.01);
assertEquals(-1, target2.fiducialId);
}
}

View File

@@ -24,7 +24,6 @@
#include "photon/PhotonPoseEstimator.h"
#include <map>
#include <utility>
#include <vector>

View File

@@ -22,12 +22,11 @@
* SOFTWARE.
*/
#include <iostream>
#include <gtest/gtest.h>
#include <wpi/print.h>
#include "PhotonVersion.h"
TEST(VersionTest, PrintVersion) {
std::cout << photon::PhotonVersion::versionString << std::endl;
wpi::println("{}", photon::PhotonVersion::versionString);
}

View File

@@ -24,8 +24,6 @@
#include "photon/simulation/VisionSystemSim.h"
#include <chrono>
#include <thread>
#include <tuple>
#include <vector>
@@ -33,6 +31,7 @@
#include <wpi/deprecated.h>
#include "photon/PhotonUtils.h"
#include "photon/estimation/VisionEstimation.h"
// Ignore GetLatestResult warnings
WPI_IGNORE_DEPRECATED

View File

@@ -17,20 +17,14 @@
#include "net/TimeSyncClient.h"
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <mutex>
#include <thread>
#include <Eigen/Core>
#include <wpi/Logger.h>
#include <wpi/print.h>
#include <wpi/struct/Struct.h>
#include <wpinet/UDPClient.h>
#include <wpinet/uv/util.h>
#include "ntcore_cpp.h"

View File

@@ -17,22 +17,18 @@
#include "net/TimeSyncServer.h"
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <mutex>
#include <thread>
#include <ntcore_cpp.h>
#include <wpi/Logger.h>
#include <wpi/print.h>
#include <wpi/struct/Struct.h>
#include <wpinet/UDPClient.h>
#include <wpinet/uv/util.h>
#include "ntcore_cpp.h"
#include "net/TimeSyncStructs.h"
static void ServerLoggerFunc(unsigned int level, const char* file,
unsigned int line, const char* msg) {

View File

@@ -17,11 +17,8 @@
#include "photon/constrained_solvepnp/wrap/casadi_wrapper.h"
#include <chrono>
#include <cstdio>
#include <iostream>
#include <optional>
#include <vector>
#include <Eigen/Cholesky>
#include <Eigen/Core>
@@ -192,10 +189,10 @@ constrained_solvepnp::do_optimization(
fmt::println("{} tags", nTags);
// fmt::println("nstate {}", nState);
std::cout << "robot2camera:\n" << robot2camera << std::endl;
std::cout << "x guess:\n" << x_guess << std::endl;
std::cout << "field2pt:\n" << field2points << std::endl;
std::cout << "observations:\n" << point_observations << std::endl;
fmt::println("robot2camera:\n{}", robot2camera);
fmt::println("x guess:\n{}", x_guess);
fmt::println("field2pt:\n{}", field2points);
fmt::println("observations:\n{}", point_observations);
fmt::println("---------^^^^^^^^---------");
}
@@ -252,7 +249,7 @@ constrained_solvepnp::do_optimization(
auto H_ldlt = H.ldlt();
if (H_ldlt.info() != Eigen::Success) {
std::cerr << "LDLT decomp failed! H=" << std::endl << H << std::endl;
fmt::println(stderr, "LDLT decomp failed! H=\n{}", H);
return wpi::unexpected{slp::ExitStatus::LOCALLY_INFEASIBLE};
}
@@ -276,7 +273,7 @@ constrained_solvepnp::do_optimization(
H_ldlt = H_reg.ldlt();
if (H_ldlt.info() != Eigen::Success) {
std::cerr << "LDLT decomp failed! H=" << std::endl << H << std::endl;
fmt::println(stderr, "LDLT decomp failed! H=\n{}", H);
return wpi::unexpected{slp::ExitStatus::LOCALLY_INFEASIBLE};
}

View File

@@ -17,13 +17,10 @@
#include "photon/estimation/VisionEstimation.h"
#include <iostream>
#include <utility>
#include <vector>
#include "photon/constrained_solvepnp/wrap/casadi_wrapper.h"
#include "photon/estimation/OpenCVHelp.h"
#include "photon/targeting/MultiTargetPNPResult.h"
namespace photon {
namespace VisionEstimation {

View File

@@ -17,14 +17,4 @@
#include "photon/targeting/PhotonTrackedTarget.h"
#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>
#include <frc/geometry/Translation2d.h>
#include <wpi/SmallVector.h>
static constexpr const uint8_t MAX_CORNERS = 8;
namespace photon {} // namespace photon

View File

@@ -17,31 +17,21 @@
#pragma once
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <frc/filter/MedianFilter.h>
#include <wpi/Logger.h>
#include <wpi/print.h>
#include <wpi/static_circular_buffer.h>
#include <wpi/struct/Struct.h>
#include <wpinet/EventLoopRunner.h>
#include <wpinet/UDPClient.h>
#include <wpinet/uv/Buffer.h>
#include <wpinet/uv/Timer.h>
#include <wpinet/uv/Udp.h>
#include "TimeSyncStructs.h"
#include "ntcore_cpp.h"
namespace wpi {
namespace tsp {

View File

@@ -17,30 +17,18 @@
#pragma once
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <wpi/Logger.h>
#include <wpi/print.h>
#include <wpi/struct/Struct.h>
#include <wpinet/EventLoopRunner.h>
#include <wpinet/UDPClient.h>
#include <wpinet/uv/Buffer.h>
#include <wpinet/uv/Timer.h>
#include <wpinet/uv/Udp.h>
#include "TimeSyncStructs.h"
#include "ntcore_cpp.h"
namespace wpi {
namespace tsp {

View File

@@ -17,17 +17,13 @@
#pragma once
#include <algorithm>
#include <bit>
#include <cstring>
#include <iostream>
#include <concepts>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/Demangle.h>
#include <wpi/ct_string.h>
#include <wpi/struct/Struct.h>
namespace photon {

View File

@@ -30,7 +30,6 @@
#define OPENCV_DISABLE_EIGEN_TENSOR_SUPPORT
#include <opencv2/core/eigen.hpp>
#include "photon/targeting/MultiTargetPNPResult.h"
#include "photon/targeting/PnpResult.h"
#include "photon/targeting/TargetCorner.h"

View File

@@ -102,7 +102,5 @@ class NTTopicSet {
cameraDistortionPublisher =
subTable->GetDoubleArrayTopic("cameraDistortion").Publish();
}
private:
};
} // namespace photon

View File

@@ -19,11 +19,6 @@
#include <utility>
#include <frc/geometry/Transform3d.h>
#include <wpi/SmallVector.h>
#include "PnpResult.h"
#include "photon/dataflow/structures/Packet.h"
#include "photon/struct/MultiTargetPNPResultStruct.h"
namespace photon {

View File

@@ -18,16 +18,13 @@
#pragma once
#include <span>
#include <string>
#include <utility>
#include <units/time.h>
#include <wpi/SmallVector.h>
#include "MultiTargetPNPResult.h"
#include "PhotonTrackedTarget.h"
#include "fmt/base.h"
#include "photon/dataflow/structures/Packet.h"
#include "photon/struct/PhotonPipelineResultStruct.h"
namespace photon {

View File

@@ -17,13 +17,10 @@
#pragma once
#include <cstddef>
#include <string>
#include <utility>
#include <vector>
#include <frc/geometry/Transform3d.h>
#include <wpi/SmallVector.h>
#include "photon/struct/PhotonTrackedTargetStruct.h"

View File

@@ -19,9 +19,6 @@
#include <utility>
#include <frc/geometry/Transform3d.h>
#include "photon/dataflow/structures/Packet.h"
#include "photon/struct/PnpResultStruct.h"
namespace photon {

View File

@@ -15,13 +15,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <span>
#include <vector>
#include <fmt/core.h>
#include <fmt/ranges.h>
#include "org_photonvision_jni_ConstrainedSolvepnpJni.h"
#include "photon/constrained_solvepnp/wrap/casadi_wrapper.h"
@@ -72,18 +68,6 @@ Java_org_photonvision_jni_ConstrainedSolvepnpJni_do_1optimization
pointObservationsMat(pointObservationsVec.data(), 2,
pointObservationsVec.size() / 2);
#if 0
fmt::println("======================================================");
fmt::println("Got robot2camera raw {}", robot2cameraVec);
fmt::println("Camera cal {} {} {} {}", cameraCal_.fx, cameraCal_.fy,
cameraCal_.cx, cameraCal_.cy);
fmt::println("{} tags", nTags);
std::cout << "robot2camera:\n" << robot2cameraMat << std::endl;
std::cout << "x guess:\n" << xGuessMat << std::endl;
std::cout << "field2pt:\n" << field2pointsMat << std::endl;
std::cout << "observations:\n" << pointObservationsMat << std::endl;
#endif
wpi::expected<constrained_solvepnp::RobotStateMat, slp::ExitStatus> result =
constrained_solvepnp::do_optimization(
headingFree, nTags, cameraCal_, robot2cameraMat, xGuessMat,

View File

@@ -15,8 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <cstdio>
#include <org_photonvision_jni_TimeSyncClient.h>
#include <org_photonvision_jni_TimeSyncServer.h>

View File

@@ -15,12 +15,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <chrono>
#include <cstdio>
#include <iostream>
#include <vector>
#include <frc/fmt/Eigen.h>
#include <gtest/gtest.h>
#include <wpi/print.h>
#include <wpi/timestamp.h>
#include "photon/constrained_solvepnp/wrap/casadi_wrapper.h"
@@ -169,11 +166,10 @@ void print_cost(casadi_real robot_x, casadi_real robot_y,
x_guess, field2points, point_observations, 0, 0);
auto end = wpi::Now();
std::cout << i << "," << static_cast<bool>(x_out) << "," << end - start
<< std::endl;
std::cout << "Solution:"
<< x_out.value_or(constrained_solvepnp::RobotStateMat::Identity())
<< std::endl;
wpi::println("{},{},{}", i, static_cast<bool>(x_out), end - start);
wpi::println(
"Solution: {}",
x_out.value_or(constrained_solvepnp::RobotStateMat::Identity()));
// std::cout << "iter "
// << i
// // << "\nGuess:\n" << x_guess << "\n Optimized ->\n"

View File

@@ -19,6 +19,7 @@
#include <hal/HAL.h>
#include <net/TimeSyncClient.h>
#include <net/TimeSyncServer.h>
#include <wpi/print.h>
TEST(TimeSyncProtocolTest, Smoketest) {
using namespace wpi::tsp;

View File

@@ -24,8 +24,6 @@
#include "Robot.h"
#include <iostream>
#include <frc/simulation/BatterySim.h>
#include <frc/simulation/RoboRioSim.h>
#include <photon/PhotonUtils.h>

View File

@@ -24,7 +24,6 @@
#include "subsystems/SwerveDrive.h"
#include <iostream>
#include <string>
#include <frc/TimedRobot.h>

View File

@@ -24,8 +24,6 @@
#include "subsystems/SwerveDriveSim.h"
#include <iostream>
#include <frc/RobotController.h>
#include <frc/system/Discretization.h>

View File

@@ -24,7 +24,6 @@
#include "subsystems/SwerveModule.h"
#include <iostream>
#include <string>
#include <frc/MathUtil.h>

View File

@@ -24,8 +24,6 @@
#include "Robot.h"
#include <iostream>
#include <frc/simulation/BatterySim.h>
#include <frc/simulation/RoboRioSim.h>

View File

@@ -24,7 +24,6 @@
#include "subsystems/SwerveDrive.h"
#include <iostream>
#include <string>
#include <frc/TimedRobot.h>

View File

@@ -24,8 +24,6 @@
#include "subsystems/SwerveDriveSim.h"
#include <iostream>
#include <frc/RobotController.h>
#include <frc/system/Discretization.h>

View File

@@ -24,7 +24,6 @@
#include "subsystems/SwerveModule.h"
#include <iostream>
#include <string>
#include <frc/MathUtil.h>

View File

@@ -24,8 +24,6 @@
#include "Robot.h"
#include <iostream>
#include <frc/simulation/BatterySim.h>
#include <frc/simulation/RoboRioSim.h>

View File

@@ -24,7 +24,6 @@
#include "subsystems/SwerveDrive.h"
#include <iostream>
#include <string>
#include <frc/TimedRobot.h>

View File

@@ -24,8 +24,6 @@
#include "subsystems/SwerveDriveSim.h"
#include <iostream>
#include <frc/RobotController.h>
#include <frc/system/Discretization.h>

View File

@@ -24,7 +24,6 @@
#include "subsystems/SwerveModule.h"
#include <iostream>
#include <string>
#include <frc/MathUtil.h>

View File

@@ -16,7 +16,6 @@
*/
#include <string.h>
#include <regex>
/*
* Autogenerated file! Do not manually edit this file. This version is

View File

@@ -10,15 +10,15 @@
},
"devDependencies": {
"@toolwind/anchors": "^1.0.10",
"prettier": "^3.5.3",
"typescript": "~5.7.2",
"vite": "^7.1.2",
"vite-ssg": "^28.2.2"
"prettier": "^3.8.1",
"typescript": "~5.7.3",
"vite": "^8.0.3",
"vite-ssg": "^28.3.0"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.12",
"@vitejs/plugin-vue": "^6.0.2",
"tailwindcss": "^4.1.12",
"vue": "^3.5.25"
"@tailwindcss/vite": "^4.2.2",
"@vitejs/plugin-vue": "^6.0.5",
"tailwindcss": "^4.2.2",
"vue": "^3.5.31"
}
}

1598
website/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -103,7 +103,7 @@ const socialLinks = [
>
<a href="#" class="flex items-center gap-3 py-2 group">
<img
src="/public/images/PhotonVision-Icon-BG.png"
src="/images/PhotonVision-Icon-BG.png"
alt="PhotonVision"
class="w-10 h-10 group-hover:scale-110 transition-transform duration-300"
/>
@@ -177,7 +177,7 @@ const socialLinks = [
>
<span class="self-center md:self-initial mb-8 md:mb-0 md:me-12 shrink">
<img
src="/public/images/PhotonVision-Icon-BG.png"
src="/images/PhotonVision-Icon-BG.png"
alt="PhotonVision Logo"
class="max-w-20 md:max-w-80 drop-shadow-2xl md:p-0 transition-transform duration-500"
/>
@@ -225,7 +225,7 @@ const socialLinks = [
class="absolute -inset-4 bg-gradient-to-r from-primary via-brand-blue to-brand-yellow rounded-2xl blur-xl opacity-30"
></div>
<img
src="/public/images/demo.png"
src="/images/demo.png"
alt="Demo of PhotonVision UI"
loading="lazy"
class="relative rounded-xl shadow-2xl border border-zinc-700/50"
@@ -255,7 +255,7 @@ const socialLinks = [
<section id="video" class="relative">
<div class="relative">
<video
src="/public/videos/in-action.mp4"
src="/videos/in-action.mp4"
playsinline
autoplay
loop
@@ -338,7 +338,7 @@ const socialLinks = [
>
<div class="flex items-center gap-4">
<img
src="/public/images/PhotonVision-Icon-BG.png"
src="/images/PhotonVision-Icon-BG.png"
class="w-12 h-12 hover:scale-110 transition-transform duration-300"
alt="PhotonVision logo"
/>