mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-19 00:41:41 +00:00
Switch from FasterXML Jackson to Avaje Jsonb (#2503)
## Description WPILib switched from FasterXML Jackson to Avaje Jsonb for speed reasons in https://github.com/wpilibsuite/allwpilib/pull/8721. This does the same for PhotonVision. Some temporary Jackson adapters are present to allow compatibility with alpha-4 ahead of updating Photon's WPILib version. A few old backwards compatibility migrations were also dropped if they were difficult to port to Avaje Jsonb or otherwise complicated the code. ## 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 --------- Co-authored-by: samfreund <samf.236@proton.me> Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
This commit is contained in:
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -98,7 +98,7 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v5
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
@@ -239,7 +239,7 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
# - os: windows-2022
|
# - os: windows-2022
|
||||||
# artifact-name: Win64
|
# artifact-name: Win64
|
||||||
- os: macos-26
|
- os: macos-15 # TODO: Restore to macos-26 with WPILib alpha-6
|
||||||
artifact-name: macOS
|
artifact-name: macOS
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
artifact-name: Linux
|
artifact-name: Linux
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ cppHeaderFileInclude {
|
|||||||
\.h$
|
\.h$
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiableFileExclude {
|
generatedFileExclude {
|
||||||
photon-lib/py/photonlibpy/generated/
|
photon-lib/py/photonlibpy/generated/
|
||||||
photon-targeting/src/generated/
|
photon-targeting/src/generated/
|
||||||
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
|
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ PhotonVision was forked from [Chameleon Vision](https://github.com/Chameleon-Vis
|
|||||||
* [EJML](https://github.com/lessthanoptimal/ejml)
|
* [EJML](https://github.com/lessthanoptimal/ejml)
|
||||||
* [Javalin](https://javalin.io/)
|
* [Javalin](https://javalin.io/)
|
||||||
* [JSON](https://json.org)
|
* [JSON](https://json.org)
|
||||||
* [FasterXML](https://github.com/FasterXML) - Specifically [jackson](https://github.com/FasterXML/jackson)
|
* [Avaje](https://avaje.io) - Specifically [jsonb](https://avaje.io/jsonb/)
|
||||||
* [MessagePack for Java](https://github.com/msgpack/msgpack-java)
|
* [MessagePack for Java](https://github.com/msgpack/msgpack-java)
|
||||||
* [OSHI](https://github.com/oshi/oshi)
|
* [OSHI](https://github.com/oshi/oshi)
|
||||||
* [QuickBuffers](https://github.com/HebiRobotics/QuickBuffers)
|
* [QuickBuffers](https://github.com/HebiRobotics/QuickBuffers)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "cpp"
|
id "cpp"
|
||||||
id "com.diffplug.spotless" version "8.1.0"
|
id "com.diffplug.spotless" version "8.1.0"
|
||||||
id "org.wpilib.WPILibRepositoriesPlugin" version "2027.0.0"
|
id "org.wpilib.WPILibRepositoriesPlugin" version "2027.0.0"
|
||||||
id 'org.wpilib.NativeUtils' version '2027.4.1' apply false
|
id 'org.wpilib.NativeUtils' version '2027.5.1' apply false
|
||||||
id 'org.wpilib.DeployUtils' version '2027.1.0' apply false
|
id 'org.wpilib.DeployUtils' version '2027.1.0' apply false
|
||||||
id 'org.photonvision.tools.WpilibTools' version '3.0.0-photon'
|
id 'org.photonvision.tools.WpilibTools' version '3.0.0-photon'
|
||||||
id 'com.google.protobuf' version '0.9.5' apply false
|
id 'com.google.protobuf' version '0.9.5' apply false
|
||||||
@@ -40,6 +40,8 @@ ext {
|
|||||||
openCVversion = "4.10.0-3"
|
openCVversion = "4.10.0-3"
|
||||||
ejmlVersion = "0.43.1";
|
ejmlVersion = "0.43.1";
|
||||||
jacksonVersion = "2.15.2";
|
jacksonVersion = "2.15.2";
|
||||||
|
avajeJsonbVersion = "3.14-RC4";
|
||||||
|
msgpackVersion = "0.9.0";
|
||||||
quickbufVersion = "1.3.3";
|
quickbufVersion = "1.3.3";
|
||||||
jacocoVersion = "0.8.14";
|
jacocoVersion = "0.8.14";
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ spotless {
|
|||||||
java {
|
java {
|
||||||
target fileTree('.') {
|
target fileTree('.') {
|
||||||
include '**/*.java'
|
include '**/*.java'
|
||||||
exclude '**/build/**', '**/build-*/**', '**/src/generated/**'
|
exclude '**/build/**', '**/build-*/**', '**/src/generated/**', "**/bin/generated-sources/**"
|
||||||
}
|
}
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
googleJavaFormat()
|
googleJavaFormat()
|
||||||
|
|||||||
@@ -1,57 +1,51 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PVCameraInfo } from "@/types/SettingTypes";
|
import type { PVCameraInfo } from "@/types/SettingTypes";
|
||||||
import { cameraInfoFor } from "@/lib/PhotonUtils";
|
|
||||||
|
|
||||||
const { camera } = defineProps({
|
const { camera } = defineProps<{ camera: PVCameraInfo }>();
|
||||||
camera: {
|
|
||||||
type: PVCameraInfo,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<v-table density="compact" :style="{ backgroundColor: 'var(--v-primary-base)' }">
|
<v-table density="compact" :style="{ backgroundColor: 'var(--v-primary-base)' }">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-if="cameraInfoFor(camera).dev !== undefined && cameraInfoFor(camera).dev !== null">
|
<tr v-if="'dev' in camera && camera.dev !== null">
|
||||||
<td>Device Number:</td>
|
<td>Device Number:</td>
|
||||||
<td>{{ cameraInfoFor(camera).dev }}</td>
|
<td>{{ camera.dev }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).name !== undefined && cameraInfoFor(camera).name !== null">
|
<tr v-if="'name' in camera && camera.name !== null">
|
||||||
<td>Name:</td>
|
<td>Name:</td>
|
||||||
<td>{{ cameraInfoFor(camera).name }}</td>
|
<td>{{ camera.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type:</td>
|
<td>Type:</td>
|
||||||
<td v-if="camera.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
<td v-if="camera.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||||
<td v-else-if="camera.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
<td v-else-if="camera.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||||
<td v-else-if="camera.PVFileCameraInfo" class="mb-3">File Camera</td>
|
<td v-else-if="camera.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||||
<td v-else>Unidentified Camera Type</td>
|
<td v-else>Unidentified Camera Type</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).baseName !== undefined && cameraInfoFor(camera).baseName !== null">
|
<tr v-if="'baseName' in camera && camera.baseName !== null">
|
||||||
<td>Base Name:</td>
|
<td>Base Name:</td>
|
||||||
<td>{{ cameraInfoFor(camera).baseName }}</td>
|
<td>{{ camera.baseName }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).vendorId !== undefined && cameraInfoFor(camera).vendorId !== null">
|
<tr v-if="'vendorId' in camera && camera.vendorId !== null">
|
||||||
<td>Vendor ID:</td>
|
<td>Vendor ID:</td>
|
||||||
<td>{{ cameraInfoFor(camera).vendorId }}</td>
|
<td>{{ camera.vendorId }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).productId !== undefined && cameraInfoFor(camera).productId !== null">
|
<tr v-if="'productId' in camera && camera.productId !== null">
|
||||||
<td>Product ID:</td>
|
<td>Product ID:</td>
|
||||||
<td>{{ cameraInfoFor(camera).productId }}</td>
|
<td>{{ camera.productId }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).path !== undefined && cameraInfoFor(camera).path !== null">
|
<tr v-if="'path' in camera && camera.path !== null">
|
||||||
<td>Path:</td>
|
<td>Path:</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(camera).path }}</td>
|
<td style="word-break: break-all">{{ camera.path }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).uniquePath !== undefined && cameraInfoFor(camera).uniquePath !== null">
|
<tr v-if="'uniquePath' in camera && camera.uniquePath !== null">
|
||||||
<td>Unique Path:</td>
|
<td>Unique Path:</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(camera).uniquePath }}</td>
|
<td style="word-break: break-all">{{ camera.uniquePath }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).otherPaths !== undefined && cameraInfoFor(camera).otherPaths !== null">
|
<tr v-if="'otherPaths' in camera && camera.otherPaths !== null">
|
||||||
<td>Other Paths:</td>
|
<td>Other Paths:</td>
|
||||||
<td>{{ cameraInfoFor(camera).otherPaths }}</td>
|
<td>{{ camera.otherPaths }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PVCameraInfo } from "@/types/SettingTypes";
|
import type { PVCameraInfo } from "@/types/SettingTypes";
|
||||||
import { cameraInfoFor } from "@/lib/PhotonUtils";
|
|
||||||
|
|
||||||
function isEqual<T>(a: T, b: T): boolean {
|
function isEqual<T>(a: T, b: T): boolean {
|
||||||
if (a === b) {
|
if (a === b) {
|
||||||
@@ -16,16 +15,7 @@ function isEqual<T>(a: T, b: T): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { saved, current } = defineProps({
|
const { saved, current } = defineProps<{ saved: PVCameraInfo; current: PVCameraInfo }>();
|
||||||
saved: {
|
|
||||||
type: PVCameraInfo,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
current: {
|
|
||||||
type: PVCameraInfo,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -38,79 +28,70 @@ const { saved, current } = defineProps({
|
|||||||
<th>Current</th>
|
<th>Current</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).dev !== undefined && cameraInfoFor(saved).dev !== null"
|
v-if="'dev' in saved && 'dev' in current && saved.dev !== null"
|
||||||
:class="cameraInfoFor(saved).dev !== cameraInfoFor(current).dev ? 'mismatch' : ''"
|
:class="saved.dev !== current.dev ? 'mismatch' : ''"
|
||||||
>
|
>
|
||||||
<td>Device Number:</td>
|
<td>Device Number:</td>
|
||||||
<td>{{ cameraInfoFor(saved).dev }}</td>
|
<td>{{ saved.dev }}</td>
|
||||||
<td>{{ cameraInfoFor(current).dev }}</td>
|
<td>{{ current.dev }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr v-if="saved.name !== null" :class="saved.name !== current.name ? 'mismatch' : ''">
|
||||||
v-if="cameraInfoFor(saved).name !== undefined && cameraInfoFor(saved).name !== null"
|
|
||||||
:class="cameraInfoFor(saved).name !== cameraInfoFor(current).name ? 'mismatch' : ''"
|
|
||||||
>
|
|
||||||
<td>Name:</td>
|
<td>Name:</td>
|
||||||
<td>{{ cameraInfoFor(saved).name }}</td>
|
<td>{{ saved.name }}</td>
|
||||||
<td>{{ cameraInfoFor(current).name }}</td>
|
<td>{{ current.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).baseName !== undefined && cameraInfoFor(saved).baseName !== null"
|
v-if="'baseName' in saved && 'baseName' in current && saved.baseName !== null"
|
||||||
:class="cameraInfoFor(saved).baseName !== cameraInfoFor(current).baseName ? 'mismatch' : ''"
|
:class="saved.baseName !== current.baseName ? 'mismatch' : ''"
|
||||||
>
|
>
|
||||||
<td>Base Name:</td>
|
<td>Base Name:</td>
|
||||||
<td>{{ cameraInfoFor(saved).baseName }}</td>
|
<td>{{ saved.baseName }}</td>
|
||||||
<td>{{ cameraInfoFor(current).baseName }}</td>
|
<td>{{ current.baseName }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type:</td>
|
<td>Type:</td>
|
||||||
<td v-if="saved.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
<td v-if="saved.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||||
<td v-else-if="saved.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
<td v-else-if="saved.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||||
<td v-else-if="saved.PVFileCameraInfo" class="mb-3">File Camera</td>
|
<td v-else-if="saved.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||||
<td v-else>Unidentified Camera Type</td>
|
<td v-else>Unidentified Camera Type</td>
|
||||||
<td v-if="current.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
<td v-if="current.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||||
<td v-else-if="current.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
<td v-else-if="current.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||||
<td v-else-if="current.PVFileCameraInfo" class="mb-3">File Camera</td>
|
<td v-else-if="current.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||||
<td v-else>Unidentified Camera Type</td>
|
<td v-else>Unidentified Camera Type</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).vendorId !== undefined && cameraInfoFor(saved).vendorId !== null"
|
v-if="'vendorId' in saved && 'vendorId' in current && saved.vendorId !== null"
|
||||||
:class="cameraInfoFor(saved).vendorId !== cameraInfoFor(current).vendorId ? 'mismatch' : ''"
|
:class="saved.vendorId !== current.vendorId ? 'mismatch' : ''"
|
||||||
>
|
>
|
||||||
<td>Vendor ID:</td>
|
<td>Vendor ID:</td>
|
||||||
<td>{{ cameraInfoFor(saved).vendorId }}</td>
|
<td>{{ saved.vendorId }}</td>
|
||||||
<td>{{ cameraInfoFor(current).vendorId }}</td>
|
<td>{{ current.vendorId }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).productId !== undefined && cameraInfoFor(saved).productId !== null"
|
v-if="'productId' in saved && 'productId' in current && saved.productId !== null"
|
||||||
:class="cameraInfoFor(saved).productId !== cameraInfoFor(current).productId ? 'mismatch' : ''"
|
:class="saved.productId !== current.productId ? 'mismatch' : ''"
|
||||||
>
|
>
|
||||||
<td>Product ID:</td>
|
<td>Product ID:</td>
|
||||||
<td>{{ cameraInfoFor(saved).productId }}</td>
|
<td>{{ saved.productId }}</td>
|
||||||
<td>{{ cameraInfoFor(current).productId }}</td>
|
<td>{{ current.productId }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr v-if="saved.path !== null" :class="saved.path !== current.path ? 'mismatch' : ''">
|
||||||
v-if="cameraInfoFor(saved).path !== undefined && cameraInfoFor(saved).path !== null"
|
|
||||||
:class="cameraInfoFor(saved).path !== cameraInfoFor(current).path ? 'mismatch' : ''"
|
|
||||||
>
|
|
||||||
<td>Path:</td>
|
<td>Path:</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(saved).path }}</td>
|
<td style="word-break: break-all">{{ saved.path }}</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(current).path }}</td>
|
<td style="word-break: break-all">{{ current.path }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr v-if="saved.uniquePath !== null" :class="saved.uniquePath !== current.uniquePath ? 'mismatch' : ''">
|
||||||
v-if="cameraInfoFor(saved).uniquePath !== undefined && cameraInfoFor(saved).uniquePath !== null"
|
|
||||||
:class="cameraInfoFor(saved).uniquePath !== cameraInfoFor(current).uniquePath ? 'mismatch' : ''"
|
|
||||||
>
|
|
||||||
<td>Unique Path:</td>
|
<td>Unique Path:</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(saved).uniquePath }}</td>
|
<td style="word-break: break-all">{{ saved.uniquePath }}</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(current).uniquePath }}</td>
|
<td style="word-break: break-all">{{ current.uniquePath }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).otherPaths !== undefined && cameraInfoFor(saved).otherPaths !== null"
|
v-if="'otherPaths' in saved && 'otherPaths' in current && saved.otherPaths !== null"
|
||||||
:class="isEqual(cameraInfoFor(saved).otherPaths, cameraInfoFor(current).otherPaths) ? '' : 'mismatch'"
|
:class="isEqual(saved.otherPaths, current.otherPaths) ? '' : 'mismatch'"
|
||||||
>
|
>
|
||||||
<td>Other Paths:</td>
|
<td>Other Paths:</td>
|
||||||
<td>{{ cameraInfoFor(saved).otherPaths }}</td>
|
<td>{{ saved.otherPaths }}</td>
|
||||||
<td>{{ cameraInfoFor(current).otherPaths }}</td>
|
<td>{{ current.otherPaths }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ const interactiveCols = computed(() =>
|
|||||||
/>
|
/>
|
||||||
<pv-switch
|
<pv-switch
|
||||||
v-model="useCameraSettingsStore().currentPipelineSettings.blockForFrames"
|
v-model="useCameraSettingsStore().currentPipelineSettings.blockForFrames"
|
||||||
:disabled="!useCameraSettingsStore().currentCameraSettings.matchedCameraInfo.PVUsbCameraInfo"
|
:disabled="useCameraSettingsStore().currentCameraSettings.matchedCameraInfo.type !== 'PVUsbCameraInfo'"
|
||||||
label="Low Latency Mode"
|
label="Low Latency Mode"
|
||||||
:switch-cols="interactiveCols"
|
:switch-cols="interactiveCols"
|
||||||
tooltip="When enabled, USB cameras wait for the next camera frame for lowest latency. When disabled, uses the most recent available frame for higher FPS."
|
tooltip="When enabled, USB cameras wait for the next camera frame for lowest latency. When disabled, uses the most recent available frame for higher FPS."
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import type { PVCameraInfo, Resolution } from "@/types/SettingTypes";
|
import type { Resolution } from "@/types/SettingTypes";
|
||||||
import axios, { type AxiosRequestConfig } from "axios";
|
import axios, { type AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
export const resolutionsAreEqual = (a: Resolution, b: Resolution) => {
|
export const resolutionsAreEqual = (a: Resolution, b: Resolution) => {
|
||||||
@@ -109,23 +109,3 @@ export const axiosPost = async (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type CameraInfoDetails = Partial<
|
|
||||||
NonNullable<PVCameraInfo["PVUsbCameraInfo"]> &
|
|
||||||
NonNullable<PVCameraInfo["PVCSICameraInfo"]> &
|
|
||||||
NonNullable<PVCameraInfo["PVFileCameraInfo"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const cameraInfoFor = (camera: PVCameraInfo | null): CameraInfoDetails => {
|
|
||||||
if (!camera) return {};
|
|
||||||
if (camera.PVUsbCameraInfo) {
|
|
||||||
return camera.PVUsbCameraInfo;
|
|
||||||
}
|
|
||||||
if (camera.PVCSICameraInfo) {
|
|
||||||
return camera.PVCSICameraInfo;
|
|
||||||
}
|
|
||||||
if (camera.PVFileCameraInfo) {
|
|
||||||
return camera.PVFileCameraInfo;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -116,23 +116,20 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
inputPort: d.inputStreamPort,
|
inputPort: d.inputStreamPort,
|
||||||
outputPort: d.outputStreamPort
|
outputPort: d.outputStreamPort
|
||||||
},
|
},
|
||||||
validVideoFormats: Object.entries(d.videoFormatList)
|
validVideoFormats: d.videoFormatList.map((v, i) => ({
|
||||||
.sort(([firstKey], [secondKey]) => parseInt(firstKey) - parseInt(secondKey))
|
resolution: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
width: v.width,
|
||||||
.map<VideoFormat>(([k, v], i) => ({
|
height: v.height
|
||||||
resolution: {
|
},
|
||||||
width: v.width,
|
fps: v.fps,
|
||||||
height: v.height
|
pixelFormat: v.pixelFormat,
|
||||||
},
|
index: v.index || i,
|
||||||
fps: v.fps,
|
diagonalFOV: v.diagonalFOV,
|
||||||
pixelFormat: v.pixelFormat,
|
horizontalFOV: v.horizontalFOV,
|
||||||
index: v.index || i,
|
verticalFOV: v.verticalFOV,
|
||||||
diagonalFOV: v.diagonalFOV,
|
standardDeviation: v.standardDeviation,
|
||||||
horizontalFOV: v.horizontalFOV,
|
mean: v.mean
|
||||||
verticalFOV: v.verticalFOV,
|
})),
|
||||||
standardDeviation: v.standardDeviation,
|
|
||||||
mean: v.mean
|
|
||||||
})),
|
|
||||||
completeCalibrations: d.calibrations,
|
completeCalibrations: d.calibrations,
|
||||||
isCSICamera: d.isCSICamera,
|
isCSICamera: d.isCSICamera,
|
||||||
minExposureRaw: d.minExposureRaw,
|
minExposureRaw: d.minExposureRaw,
|
||||||
|
|||||||
@@ -75,46 +75,29 @@ export type ConfigurableNetworkSettings = Omit<
|
|||||||
"canManage" | "networkInterfaceNames" | "networkingDisabled"
|
"canManage" | "networkInterfaceNames" | "networkingDisabled"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface PVCameraInfoBase {
|
interface PVCameraInfoBase {
|
||||||
/*
|
type: "PVUsbCameraInfo" | "PVCSICameraInfo" | "PVFileCameraInfo";
|
||||||
Huge hack. In Jackson, this is set based on the underlying type -- this
|
path: string;
|
||||||
then maps to one of the 3 subclasses here below. Not sure how to best deal with this.
|
name: string;
|
||||||
*/
|
uniquePath: string;
|
||||||
cameraTypename: "PVUsbCameraInfo" | "PVCSICameraInfo" | "PVFileCameraInfo";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PVUsbCameraInfo {
|
export interface PVUsbCameraInfo extends PVCameraInfoBase {
|
||||||
|
type: "PVUsbCameraInfo";
|
||||||
dev: number;
|
dev: number;
|
||||||
name: string;
|
|
||||||
otherPaths: string[];
|
otherPaths: string[];
|
||||||
path: string;
|
|
||||||
vendorId: number;
|
vendorId: number;
|
||||||
productId: number;
|
productId: number;
|
||||||
|
|
||||||
// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
|
|
||||||
uniquePath: string;
|
|
||||||
}
|
}
|
||||||
export interface PVCSICameraInfo {
|
export interface PVCSICameraInfo extends PVCameraInfoBase {
|
||||||
|
type: "PVCSICameraInfo";
|
||||||
baseName: string;
|
baseName: string;
|
||||||
path: string;
|
|
||||||
|
|
||||||
// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
|
|
||||||
uniquePath: string;
|
|
||||||
}
|
}
|
||||||
export interface PVFileCameraInfo {
|
export interface PVFileCameraInfo extends PVCameraInfoBase {
|
||||||
path: string;
|
type: "PVFileCameraInfo";
|
||||||
name: string;
|
|
||||||
|
|
||||||
// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
|
|
||||||
uniquePath: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This camera info will only ever hold one of its members - the others should be undefined.
|
export type PVCameraInfo = PVUsbCameraInfo | PVCSICameraInfo | PVFileCameraInfo;
|
||||||
export class PVCameraInfo {
|
|
||||||
PVUsbCameraInfo: PVUsbCameraInfo | undefined;
|
|
||||||
PVCSICameraInfo: PVCSICameraInfo | undefined;
|
|
||||||
PVFileCameraInfo: PVFileCameraInfo | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VsmState {
|
export interface VsmState {
|
||||||
disabledConfigs: WebsocketCameraSettingsUpdate[];
|
disabledConfigs: WebsocketCameraSettingsUpdate[];
|
||||||
@@ -439,13 +422,10 @@ export const PlaceholderCameraSettings: UiCameraConfiguration = reactive({
|
|||||||
minWhiteBalanceTemp: 2000,
|
minWhiteBalanceTemp: 2000,
|
||||||
maxWhiteBalanceTemp: 10000,
|
maxWhiteBalanceTemp: 10000,
|
||||||
matchedCameraInfo: {
|
matchedCameraInfo: {
|
||||||
PVFileCameraInfo: {
|
type: "PVFileCameraInfo",
|
||||||
name: "Foobar",
|
name: "Foobar",
|
||||||
path: "/dev/foobar",
|
path: "/dev/foobar",
|
||||||
uniquePath: "/dev/foobar2"
|
uniquePath: "/dev/foobar2"
|
||||||
},
|
|
||||||
PVCSICameraInfo: undefined,
|
|
||||||
PVUsbCameraInfo: undefined
|
|
||||||
},
|
},
|
||||||
fpsLimit: -1,
|
fpsLimit: -1,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
|
|||||||
@@ -30,21 +30,18 @@ export interface WebsocketNumberPair {
|
|||||||
second: number;
|
second: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WebsocketVideoFormat = Record<
|
export type WebsocketVideoFormat = {
|
||||||
number,
|
fps: number;
|
||||||
{
|
height: number;
|
||||||
fps: number;
|
width: number;
|
||||||
height: number;
|
pixelFormat: string;
|
||||||
width: number;
|
index?: number;
|
||||||
pixelFormat: string;
|
diagonalFOV?: number;
|
||||||
index?: number;
|
horizontalFOV?: number;
|
||||||
diagonalFOV?: number;
|
verticalFOV?: number;
|
||||||
horizontalFOV?: number;
|
standardDeviation?: number;
|
||||||
verticalFOV?: number;
|
mean?: number;
|
||||||
standardDeviation?: number;
|
}[];
|
||||||
mean?: number;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
// Companion to UICameraConfiguration in Java
|
// Companion to UICameraConfiguration in Java
|
||||||
export interface WebsocketCameraSettingsUpdate {
|
export interface WebsocketCameraSettingsUpdate {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { PlaceholderCameraSettings, PVCameraInfo } from "@/types/SettingTypes";
|
import { PlaceholderCameraSettings, type PVCameraInfo } from "@/types/SettingTypes";
|
||||||
import { axiosPost, getResolutionString, cameraInfoFor } from "@/lib/PhotonUtils";
|
import { axiosPost, getResolutionString } from "@/lib/PhotonUtils";
|
||||||
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
||||||
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
import PvCameraInfoCard from "@/components/common/pv-camera-info-card.vue";
|
import PvCameraInfoCard from "@/components/common/pv-camera-info-card.vue";
|
||||||
@@ -59,22 +59,15 @@ const deleteThisCamera = async (cameraUniqueName: string) => {
|
|||||||
|
|
||||||
const cameraConnected = (uniquePath: string | undefined): boolean => {
|
const cameraConnected = (uniquePath: string | undefined): boolean => {
|
||||||
if (!uniquePath) return false;
|
if (!uniquePath) return false;
|
||||||
return (
|
return useStateStore().vsmState.allConnectedCameras.find((it) => it.uniquePath === uniquePath) !== undefined;
|
||||||
useStateStore().vsmState.allConnectedCameras.find((it) => cameraInfoFor(it).uniquePath === uniquePath) !== undefined
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const unmatchedCameras = computed(() => {
|
const unmatchedCameras = computed(() => {
|
||||||
const activeVmPaths = Object.values(useCameraSettingsStore().cameras).map(
|
const activeVmPaths = Object.values(useCameraSettingsStore().cameras).map((it) => it.matchedCameraInfo.uniquePath);
|
||||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath
|
const disabledVmPaths = useStateStore().vsmState.disabledConfigs.map((it) => it.matchedCameraInfo.uniquePath);
|
||||||
);
|
|
||||||
const disabledVmPaths = useStateStore().vsmState.disabledConfigs.map(
|
|
||||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath
|
|
||||||
);
|
|
||||||
|
|
||||||
return useStateStore().vsmState.allConnectedCameras.filter(
|
return useStateStore().vsmState.allConnectedCameras.filter(
|
||||||
(it) =>
|
(it) => !activeVmPaths.includes(it.uniquePath) && !disabledVmPaths.includes(it.uniquePath)
|
||||||
!activeVmPaths.includes(cameraInfoFor(it).uniquePath) && !disabledVmPaths.includes(cameraInfoFor(it).uniquePath)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,8 +78,8 @@ const activeVisionModules = computed(() =>
|
|||||||
// Display connected cameras first
|
// Display connected cameras first
|
||||||
.sort(
|
.sort(
|
||||||
(first, second) =>
|
(first, second) =>
|
||||||
(cameraConnected(cameraInfoFor(second.matchedCameraInfo).uniquePath) ? 1 : 0) -
|
(cameraConnected(second.matchedCameraInfo.uniquePath) ? 1 : 0) -
|
||||||
(cameraConnected(cameraInfoFor(first.matchedCameraInfo).uniquePath) ? 1 : 0)
|
(cameraConnected(first.matchedCameraInfo.uniquePath) ? 1 : 0)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -105,18 +98,18 @@ const setCameraView = (camera: PVCameraInfo | null, isConnected: boolean | null)
|
|||||||
const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||||
if (!info) {
|
if (!info) {
|
||||||
return {
|
return {
|
||||||
PVFileCameraInfo: undefined,
|
type: "PVFileCameraInfo",
|
||||||
PVCSICameraInfo: undefined,
|
path: "",
|
||||||
PVUsbCameraInfo: undefined
|
name: "",
|
||||||
|
uniquePath: ""
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
useStateStore().vsmState.allConnectedCameras.find(
|
useStateStore().vsmState.allConnectedCameras.find((it) => it.uniquePath === info.uniquePath) || {
|
||||||
(it) => cameraInfoFor(it).uniquePath === cameraInfoFor(info).uniquePath
|
type: "PVFileCameraInfo",
|
||||||
) || {
|
path: "",
|
||||||
PVFileCameraInfo: undefined,
|
name: "",
|
||||||
PVCSICameraInfo: undefined,
|
uniquePath: ""
|
||||||
PVUsbCameraInfo: undefined
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -135,12 +128,11 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
class="pr-0"
|
class="pr-0"
|
||||||
>
|
>
|
||||||
<v-card color="surface" class="rounded-12">
|
<v-card color="surface" class="rounded-12">
|
||||||
<v-card-title>{{ cameraInfoFor(module.matchedCameraInfo).name }}</v-card-title>
|
<v-card-title>{{ module.matchedCameraInfo.name }}</v-card-title>
|
||||||
<v-card-subtitle v-if="!cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)"
|
<v-card-subtitle v-if="!cameraConnected(module.matchedCameraInfo.uniquePath)"
|
||||||
>Status: <span class="inactive-status">Disconnected</span></v-card-subtitle
|
>Status: <span class="inactive-status">Disconnected</span></v-card-subtitle
|
||||||
>
|
>
|
||||||
<v-card-subtitle
|
<v-card-subtitle v-else-if="cameraConnected(module.matchedCameraInfo.uniquePath) && !module.mismatch"
|
||||||
v-else-if="cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) && !module.mismatch"
|
|
||||||
>Status: <span class="active-status">Active</span></v-card-subtitle
|
>Status: <span class="active-status">Active</span></v-card-subtitle
|
||||||
>
|
>
|
||||||
<v-card-subtitle v-else>Status: <span class="mismatch-status">Mismatch</span></v-card-subtitle>
|
<v-card-subtitle v-else>Status: <span class="mismatch-status">Mismatch</span></v-card-subtitle>
|
||||||
@@ -149,7 +141,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-if="
|
v-if="
|
||||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) &&
|
cameraConnected(module.matchedCameraInfo.uniquePath) &&
|
||||||
useStateStore().backendResults[module.uniqueName]
|
useStateStore().backendResults[module.uniqueName]
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@@ -191,7 +183,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
<div
|
<div
|
||||||
v-if="cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)"
|
v-if="cameraConnected(module.matchedCameraInfo.uniquePath)"
|
||||||
:id="`stream-container-${index}`"
|
:id="`stream-container-${index}`"
|
||||||
class="d-flex flex-column justify-center align-center mt-3"
|
class="d-flex flex-column justify-center align-center mt-3"
|
||||||
style="height: 250px"
|
style="height: 250px"
|
||||||
@@ -210,12 +202,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||||
@click="
|
@click="setCameraView(module.matchedCameraInfo, cameraConnected(module.matchedCameraInfo.uniquePath))"
|
||||||
setCameraView(
|
|
||||||
module.matchedCameraInfo,
|
|
||||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span>Details</span>
|
<span>Details</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -292,7 +279,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Connected</td>
|
<td>Connected</td>
|
||||||
<td>{{ cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) }}</td>
|
<td>{{ cameraConnected(module.matchedCameraInfo.uniquePath) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
@@ -304,12 +291,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||||
@click="
|
@click="setCameraView(module.matchedCameraInfo, cameraConnected(module.matchedCameraInfo.uniquePath))"
|
||||||
setCameraView(
|
|
||||||
module.matchedCameraInfo,
|
|
||||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span>Details</span>
|
<span>Details</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -354,15 +336,15 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
<v-col v-for="(camera, index) in unmatchedCameras" :key="index" cols="12" sm="6" lg="4" class="pr-0">
|
<v-col v-for="(camera, index) in unmatchedCameras" :key="index" cols="12" sm="6" lg="4" class="pr-0">
|
||||||
<v-card class="pr-0 rounded-12" color="surface">
|
<v-card class="pr-0 rounded-12" color="surface">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<span v-if="camera.PVUsbCameraInfo">USB Camera:</span>
|
<span v-if="camera.type === 'PVUsbCameraInfo'">USB Camera:</span>
|
||||||
<span v-else-if="camera.PVCSICameraInfo">CSI Camera:</span>
|
<span v-else-if="camera.type === 'PVCSICameraInfo'">CSI Camera:</span>
|
||||||
<span v-else-if="camera.PVFileCameraInfo">File Camera:</span>
|
<span v-else-if="camera.type === 'PVFileCameraInfo'">File Camera:</span>
|
||||||
<span v-else>Unknown Camera:</span>
|
<span v-else>Unknown Camera:</span>
|
||||||
<span>{{ cameraInfoFor(camera)?.name ?? cameraInfoFor(camera)?.baseName }}</span>
|
<span>{{ camera.name }}</span>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-subtitle>Status: Unassigned</v-card-subtitle>
|
<v-card-subtitle>Status: Unassigned</v-card-subtitle>
|
||||||
<v-card-text class="pt-3">
|
<v-card-text class="pt-3">
|
||||||
<span style="word-break: break-all">{{ cameraInfoFor(camera)?.path }}</span>
|
<span style="word-break: break-all">{{ camera?.path }}</span>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text class="pt-0">
|
<v-card-text class="pt-0">
|
||||||
<v-row>
|
<v-row>
|
||||||
@@ -413,7 +395,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
<v-dialog v-model="viewingDetails" max-width="800">
|
<v-dialog v-model="viewingDetails" max-width="800">
|
||||||
<v-card v-if="viewingCamera[0] !== null" flat color="surface">
|
<v-card v-if="viewingCamera[0] !== null" flat color="surface">
|
||||||
<v-card-title class="d-flex justify-space-between">
|
<v-card-title class="d-flex justify-space-between">
|
||||||
<span>{{ cameraInfoFor(viewingCamera[0])?.name ?? cameraInfoFor(viewingCamera[0])?.baseName }}</span>
|
<span>{{ viewingCamera[0].name }}</span>
|
||||||
<v-btn variant="text" @click="setCameraView(null, null)">
|
<v-btn variant="text" @click="setCameraView(null, null)">
|
||||||
<v-icon size="x-large">mdi-close</v-icon>
|
<v-icon size="x-large">mdi-close</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -423,9 +405,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text
|
<v-card-text
|
||||||
v-else-if="
|
v-else-if="
|
||||||
activeVisionModules.find(
|
activeVisionModules.find((it) => it.matchedCameraInfo.uniquePath === viewingCamera[0]?.uniquePath)?.mismatch
|
||||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath === cameraInfoFor(viewingCamera[0]).uniquePath
|
|
||||||
)?.mismatch
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<v-alert
|
<v-alert
|
||||||
|
|||||||
@@ -17,9 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -36,6 +34,7 @@ import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
|||||||
import org.photonvision.vision.processes.PipelineManager;
|
import org.photonvision.vision.processes.PipelineManager;
|
||||||
import org.wpilib.vision.camera.UsbCameraInfo;
|
import org.wpilib.vision.camera.UsbCameraInfo;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class CameraConfiguration {
|
public class CameraConfiguration {
|
||||||
private static final Logger logger = new Logger(CameraConfiguration.class, LogGroup.Camera);
|
private static final Logger logger = new Logger(CameraConfiguration.class, LogGroup.Camera);
|
||||||
|
|
||||||
@@ -62,11 +61,8 @@ public class CameraConfiguration {
|
|||||||
|
|
||||||
public int streamIndex = 0; // 0 index means ports [1181, 1182], 1 means [1183, 1184], etc...
|
public int streamIndex = 0; // 0 index means ports [1181, 1182], 1 means [1183, 1184], etc...
|
||||||
|
|
||||||
// Ignore the pipes, as we serialize them to their own column to hack around
|
public List<CVPipelineSettings> pipelineSettings = new ArrayList<>();
|
||||||
// polymorphic lists
|
|
||||||
@JsonIgnore public List<CVPipelineSettings> pipelineSettings = new ArrayList<>();
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public DriverModePipelineSettings driveModeSettings = new DriverModePipelineSettings();
|
public DriverModePipelineSettings driveModeSettings = new DriverModePipelineSettings();
|
||||||
|
|
||||||
public CameraConfiguration(PVCameraInfo cameraInfo, String uniqueName, String nickname) {
|
public CameraConfiguration(PVCameraInfo cameraInfo, String uniqueName, String nickname) {
|
||||||
@@ -78,24 +74,22 @@ public class CameraConfiguration {
|
|||||||
logger.debug("Creating USB camera configuration for " + this.toShortString());
|
logger.debug("Creating USB camera configuration for " + this.toShortString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shiny new constructor
|
// JSON Constructor (can't be marked with @Json.Creator due to public fields that aren't part of
|
||||||
@JsonCreator
|
// the parameters)
|
||||||
public CameraConfiguration(
|
public CameraConfiguration(
|
||||||
@JsonProperty("uniqueName") String uniqueName,
|
String uniqueName,
|
||||||
@JsonProperty("matchedCameraInfo") PVCameraInfo matchedCameraInfo,
|
PVCameraInfo matchedCameraInfo,
|
||||||
@JsonProperty("nickname") String nickname,
|
String nickname,
|
||||||
@JsonProperty("deactivated") boolean deactivated,
|
boolean deactivated,
|
||||||
@JsonProperty("cameraQuirks") QuirkyCamera cameraQuirks,
|
QuirkyCamera cameraQuirks,
|
||||||
@JsonProperty("FOV") double FOV,
|
double FOV,
|
||||||
@JsonProperty("calibrations") List<CameraCalibrationCoefficients> calibrations,
|
int currentPipelineIndex) {
|
||||||
@JsonProperty("currentPipelineIndex") int currentPipelineIndex) {
|
|
||||||
this.uniqueName = uniqueName;
|
this.uniqueName = uniqueName;
|
||||||
this.matchedCameraInfo = matchedCameraInfo;
|
this.matchedCameraInfo = matchedCameraInfo;
|
||||||
this.nickname = nickname;
|
this.nickname = nickname;
|
||||||
this.deactivated = deactivated;
|
this.deactivated = deactivated;
|
||||||
this.cameraQuirks = cameraQuirks;
|
this.cameraQuirks = cameraQuirks;
|
||||||
this.FOV = FOV;
|
this.FOV = FOV;
|
||||||
this.calibrations = calibrations != null ? calibrations : new ArrayList<>();
|
|
||||||
this.currentPipelineIndex = currentPipelineIndex;
|
this.currentPipelineIndex = currentPipelineIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,14 +114,14 @@ public class CameraConfiguration {
|
|||||||
PVCameraInfo matchedCameraInfo;
|
PVCameraInfo matchedCameraInfo;
|
||||||
|
|
||||||
/** Legacy constructor for compat with 2024.3.1 */
|
/** Legacy constructor for compat with 2024.3.1 */
|
||||||
@JsonCreator
|
@Json.Creator
|
||||||
public LegacyCameraConfigStruct(
|
public LegacyCameraConfigStruct(
|
||||||
@JsonProperty("baseName") String baseName,
|
String baseName,
|
||||||
@JsonProperty("path") String path,
|
String path,
|
||||||
@JsonProperty("otherPaths") String[] otherPaths,
|
String[] otherPaths,
|
||||||
@JsonProperty("cameraType") CameraType cameraType,
|
CameraType cameraType,
|
||||||
@JsonProperty("usbVID") int usbVID,
|
int usbVID,
|
||||||
@JsonProperty("usbPID") int usbPID) {
|
int usbPID) {
|
||||||
if (cameraType == CameraType.UsbCamera) {
|
if (cameraType == CameraType.UsbCamera) {
|
||||||
this.matchedCameraInfo =
|
this.matchedCameraInfo =
|
||||||
PVCameraInfo.fromUsbCameraInfo(
|
PVCameraInfo.fromUsbCameraInfo(
|
||||||
@@ -171,16 +165,16 @@ public class CameraConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace a calibration in our list with the same unrotatedImageSize with a new one, or add it if
|
* Replace a calibration in our list with the same resolution with a new one, or add it if none
|
||||||
* none exists yet. If we are replacing an existing calibration, the old one will be "released"
|
* exists yet. If we are replacing an existing calibration, the old one will be "released" and the
|
||||||
* and the underlying data matrices will become invalid.
|
* underlying data matrices will become invalid.
|
||||||
*
|
*
|
||||||
* @param calibration The calibration to add.
|
* @param calibration The calibration to add.
|
||||||
*/
|
*/
|
||||||
public void addCalibration(CameraCalibrationCoefficients calibration) {
|
public void addCalibration(CameraCalibrationCoefficients calibration) {
|
||||||
logger.info("adding calibration " + calibration.unrotatedImageSize);
|
logger.info("adding calibration " + calibration.resolution);
|
||||||
calibrations.stream()
|
calibrations.stream()
|
||||||
.filter(it -> it.unrotatedImageSize.equals(calibration.unrotatedImageSize))
|
.filter(it -> it.resolution.equals(calibration.resolution))
|
||||||
.findAny()
|
.findAny()
|
||||||
.ifPresent(
|
.ifPresent(
|
||||||
(it) -> {
|
(it) -> {
|
||||||
@@ -194,12 +188,12 @@ public class CameraConfiguration {
|
|||||||
* Remove a calibration from our list. If found, the calibration will be "released". If not found,
|
* Remove a calibration from our list. If found, the calibration will be "released". If not found,
|
||||||
* no-op.
|
* no-op.
|
||||||
*
|
*
|
||||||
* @param unrotatedImageSize The resolution to remove.
|
* @param resolution The resolution to remove.
|
||||||
*/
|
*/
|
||||||
public void removeCalibration(Size unrotatedImageSize) {
|
public void removeCalibration(Size resolution) {
|
||||||
logger.info("deleting calibration " + unrotatedImageSize);
|
logger.info("deleting calibration " + resolution);
|
||||||
calibrations.stream()
|
calibrations.stream()
|
||||||
.filter(it -> it.unrotatedImageSize.equals(unrotatedImageSize))
|
.filter(it -> it.resolution.equals(resolution))
|
||||||
.findAny()
|
.findAny()
|
||||||
.ifPresent(
|
.ifPresent(
|
||||||
(it) -> {
|
(it) -> {
|
||||||
@@ -215,7 +209,6 @@ public class CameraConfiguration {
|
|||||||
*
|
*
|
||||||
* <p>This represents our best guess at an immutable path to detect a camera at.
|
* <p>This represents our best guess at an immutable path to detect a camera at.
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
|
||||||
public String getDevicePath() {
|
public String getDevicePath() {
|
||||||
return matchedCameraInfo.uniquePath();
|
return matchedCameraInfo.uniquePath();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
|
import io.avaje.json.JsonException;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -34,7 +37,6 @@ import org.opencv.core.Size;
|
|||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.FileUtils;
|
import org.photonvision.common.util.file.FileUtils;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.photonvision.vision.processes.VisionSource;
|
import org.photonvision.vision.processes.VisionSource;
|
||||||
import org.zeroturnaround.zip.ZipUtil;
|
import org.zeroturnaround.zip.ZipUtil;
|
||||||
|
|
||||||
@@ -233,14 +235,15 @@ public class ConfigManager {
|
|||||||
Path.of(getModelsDirectory().toString(), "photonvision-object-detection-models.json")
|
Path.of(getModelsDirectory().toString(), "photonvision-object-detection-models.json")
|
||||||
.toFile();
|
.toFile();
|
||||||
try {
|
try {
|
||||||
JacksonUtils.serialize(
|
Jsonb.instance()
|
||||||
tempProperties.toPath(), this.getConfig().neuralNetworkPropertyManager());
|
.type(NeuralNetworkModelsSettings.class)
|
||||||
|
.toJson(this.getConfig().getNeuralNetworkProperties(), new FileWriter(tempProperties));
|
||||||
ZipUtil.pack(getModelsDirectory(), out);
|
ZipUtil.pack(getModelsDirectory(), out);
|
||||||
// Now delete the tempProperties
|
// Now delete the tempProperties
|
||||||
if (tempProperties.exists()) {
|
if (tempProperties.exists()) {
|
||||||
Files.delete(tempProperties.toPath());
|
Files.delete(tempProperties.toPath());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -17,47 +17,49 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import org.photonvision.common.hardware.statusLED.StatusLEDType;
|
import org.photonvision.common.hardware.statusLED.StatusLEDType;
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@Json
|
||||||
public class HardwareConfig {
|
public class HardwareConfig {
|
||||||
public final String deviceName;
|
public String deviceName;
|
||||||
|
|
||||||
// LED control
|
// LED control
|
||||||
public final ArrayList<Integer> ledPins;
|
public List<Integer> ledPins;
|
||||||
public final boolean ledsCanDim;
|
public boolean ledsCanDim;
|
||||||
public final ArrayList<Integer> ledBrightnessRange;
|
public List<Integer> ledBrightnessRange;
|
||||||
public final int ledPWMFrequency;
|
public int ledPWMFrequency;
|
||||||
public final StatusLEDType statusLEDType;
|
public StatusLEDType statusLEDType;
|
||||||
|
|
||||||
@JsonAlias("statusRGBPins")
|
// MIGRATION: 2026
|
||||||
public final ArrayList<Integer> statusLEDPins;
|
@Json.Alias("statusRGBPins")
|
||||||
|
public List<Integer> statusLEDPins;
|
||||||
|
|
||||||
@JsonAlias("statusRGBActiveHigh")
|
// MIGRATION: 2026
|
||||||
public final boolean statusLEDActiveHigh;
|
@Json.Alias("statusRGBActiveHigh")
|
||||||
|
public boolean statusLEDActiveHigh;
|
||||||
|
|
||||||
// Custom GPIO
|
// Custom GPIO
|
||||||
public final String getGPIOCommand;
|
public String getGPIOCommand;
|
||||||
public final String setGPIOCommand;
|
public String setGPIOCommand;
|
||||||
public final String setPWMCommand;
|
public String setPWMCommand;
|
||||||
public final String setPWMFrequencyCommand;
|
public String setPWMFrequencyCommand;
|
||||||
public final String releaseGPIOCommand;
|
public String releaseGPIOCommand;
|
||||||
|
|
||||||
// Device stuff
|
// Device stuff
|
||||||
public final String restartHardwareCommand;
|
public String restartHardwareCommand;
|
||||||
public final double vendorFOV; // -1 for unmanaged
|
public double vendorFOV; // -1 for unmanaged
|
||||||
|
|
||||||
public HardwareConfig(
|
public HardwareConfig(
|
||||||
String deviceName,
|
String deviceName,
|
||||||
ArrayList<Integer> ledPins,
|
List<Integer> ledPins,
|
||||||
boolean ledsCanDim,
|
boolean ledsCanDim,
|
||||||
ArrayList<Integer> ledBrightnessRange,
|
List<Integer> ledBrightnessRange,
|
||||||
int ledPwmFrequency,
|
int ledPwmFrequency,
|
||||||
StatusLEDType statusLEDType,
|
StatusLEDType statusLEDType,
|
||||||
ArrayList<Integer> statusLEDPins,
|
List<Integer> statusLEDPins,
|
||||||
boolean statusLEDActiveHigh,
|
boolean statusLEDActiveHigh,
|
||||||
String getGPIOCommand,
|
String getGPIOCommand,
|
||||||
String setGPIOCommand,
|
String setGPIOCommand,
|
||||||
|
|||||||
@@ -17,8 +17,11 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import io.avaje.json.JsonException;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -34,9 +37,6 @@ import java.util.stream.Stream;
|
|||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.FileUtils;
|
import org.photonvision.common.util.file.FileUtils;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
|
||||||
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
|
||||||
import org.photonvision.vision.processes.VisionSource;
|
import org.photonvision.vision.processes.VisionSource;
|
||||||
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
import org.wpilib.vision.apriltag.AprilTagFields;
|
import org.wpilib.vision.apriltag.AprilTagFields;
|
||||||
@@ -126,14 +126,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
AprilTagFieldLayout atfl = null;
|
AprilTagFieldLayout atfl = null;
|
||||||
|
|
||||||
if (hardwareConfigFile.exists()) {
|
if (hardwareConfigFile.exists()) {
|
||||||
try {
|
try (var stream = new FileInputStream(hardwareConfigFile)) {
|
||||||
hardwareConfig =
|
hardwareConfig = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
|
||||||
JacksonUtils.deserialize(hardwareConfigFile.toPath(), HardwareConfig.class);
|
|
||||||
if (hardwareConfig == null) {
|
if (hardwareConfig == null) {
|
||||||
logger.error("Could not deserialize hardware config! Loading defaults");
|
logger.error("Could not deserialize hardware config! Loading defaults");
|
||||||
hardwareConfig = new HardwareConfig();
|
hardwareConfig = new HardwareConfig();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize hardware config! Loading defaults");
|
logger.error("Could not deserialize hardware config! Loading defaults");
|
||||||
hardwareConfig = new HardwareConfig();
|
hardwareConfig = new HardwareConfig();
|
||||||
}
|
}
|
||||||
@@ -143,14 +142,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hardwareSettingsFile.exists()) {
|
if (hardwareSettingsFile.exists()) {
|
||||||
try {
|
try (var stream = new FileInputStream(hardwareSettingsFile)) {
|
||||||
hardwareSettings =
|
hardwareSettings = Jsonb.instance().type(HardwareSettings.class).fromJson(stream);
|
||||||
JacksonUtils.deserialize(hardwareSettingsFile.toPath(), HardwareSettings.class);
|
|
||||||
if (hardwareSettings == null) {
|
if (hardwareSettings == null) {
|
||||||
logger.error("Could not deserialize hardware settings! Loading defaults");
|
logger.error("Could not deserialize hardware settings! Loading defaults");
|
||||||
hardwareSettings = new HardwareSettings();
|
hardwareSettings = new HardwareSettings();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize hardware settings! Loading defaults");
|
logger.error("Could not deserialize hardware settings! Loading defaults");
|
||||||
hardwareSettings = new HardwareSettings();
|
hardwareSettings = new HardwareSettings();
|
||||||
}
|
}
|
||||||
@@ -160,13 +158,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (networkConfigFile.exists()) {
|
if (networkConfigFile.exists()) {
|
||||||
try {
|
try (var stream = new FileInputStream(networkConfigFile)) {
|
||||||
networkConfig = JacksonUtils.deserialize(networkConfigFile.toPath(), NetworkConfig.class);
|
networkConfig = Jsonb.instance().type(NetworkConfig.class).fromJson(stream);
|
||||||
if (networkConfig == null) {
|
if (networkConfig == null) {
|
||||||
logger.error("Could not deserialize network config! Loading defaults");
|
logger.error("Could not deserialize network config! Loading defaults");
|
||||||
networkConfig = new NetworkConfig();
|
networkConfig = new NetworkConfig();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize network config! Loading defaults");
|
logger.error("Could not deserialize network config! Loading defaults");
|
||||||
networkConfig = new NetworkConfig();
|
networkConfig = new NetworkConfig();
|
||||||
}
|
}
|
||||||
@@ -184,13 +182,12 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (apriltagFieldLayoutFile.exists()) {
|
if (apriltagFieldLayoutFile.exists()) {
|
||||||
try {
|
try (var stream = new FileInputStream(apriltagFieldLayoutFile)) {
|
||||||
atfl =
|
atfl = Jsonb.instance().type(AprilTagFieldLayout.class).fromJson(stream);
|
||||||
JacksonUtils.deserialize(apriltagFieldLayoutFile.toPath(), AprilTagFieldLayout.class);
|
|
||||||
if (atfl == null) {
|
if (atfl == null) {
|
||||||
logger.error("Could not deserialize apriltag field layout! (still null)");
|
logger.error("Could not deserialize apriltag field layout! (still null)");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize apriltag field layout!", e);
|
logger.error("Could not deserialize apriltag field layout!", e);
|
||||||
atfl = null; // not required, nice to be explicit
|
atfl = null; // not required, nice to be explicit
|
||||||
}
|
}
|
||||||
@@ -227,14 +224,14 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
// Delete old configs
|
// Delete old configs
|
||||||
FileUtils.deleteDirectory(camerasFolder.toPath());
|
FileUtils.deleteDirectory(camerasFolder.toPath());
|
||||||
|
|
||||||
try {
|
try (var stream = new FileOutputStream(networkConfigFile)) {
|
||||||
JacksonUtils.serialize(networkConfigFile.toPath(), config.getNetworkConfig());
|
Jsonb.instance().type(NetworkConfig.class).toJson(config.getNetworkConfig(), stream);
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not save network config!", e);
|
logger.error("Could not save network config!", e);
|
||||||
}
|
}
|
||||||
try {
|
try (var stream = new FileOutputStream(hardwareSettingsFile)) {
|
||||||
JacksonUtils.serialize(hardwareSettingsFile.toPath(), config.getHardwareSettings());
|
Jsonb.instance().type(HardwareSettings.class).toJson(config.getHardwareSettings(), stream);
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not save hardware config!", e);
|
logger.error("Could not save hardware config!", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,33 +246,11 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
subdir.toFile().mkdirs();
|
subdir.toFile().mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try (var stream = new FileOutputStream(Path.of(subdir.toString(), "config.json").toFile())) {
|
||||||
JacksonUtils.serialize(Path.of(subdir.toString(), "config.json"), camConfig);
|
Jsonb.instance().type(CameraConfiguration.class).toJson(camConfig, stream);
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not save config.json for " + subdir, e);
|
logger.error("Could not save config.json for " + subdir, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
JacksonUtils.serialize(
|
|
||||||
Path.of(subdir.toString(), "drivermode.json"), camConfig.driveModeSettings);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Could not save drivermode.json for " + subdir, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var pipe : camConfig.pipelineSettings) {
|
|
||||||
var pipePath = Path.of(subdir.toString(), "pipelines", pipe.pipelineNickname + ".json");
|
|
||||||
|
|
||||||
if (!pipePath.getParent().toFile().exists()) {
|
|
||||||
// TODO: check for error
|
|
||||||
pipePath.getParent().toFile().mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
JacksonUtils.serialize(pipePath, pipe);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Could not save " + pipe.pipelineNickname + ".json!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logger.info("Settings saved!");
|
logger.info("Settings saved!");
|
||||||
return false; // TODO, deal with this. Do I need to?
|
return false; // TODO, deal with this. Do I need to?
|
||||||
@@ -289,11 +264,9 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
for (var subdir : subdirectories) {
|
for (var subdir : subdirectories) {
|
||||||
var cameraConfigPath = Path.of(subdir.toString(), "config.json");
|
var cameraConfigPath = Path.of(subdir.toString(), "config.json");
|
||||||
CameraConfiguration loadedConfig = null;
|
CameraConfiguration loadedConfig = null;
|
||||||
try {
|
try (var stream = new FileInputStream(cameraConfigPath.toFile())) {
|
||||||
loadedConfig =
|
loadedConfig = Jsonb.instance().type(CameraConfiguration.class).fromJson(stream);
|
||||||
JacksonUtils.deserialize(
|
} catch (IllegalStateException | JsonException e) {
|
||||||
cameraConfigPath.toAbsolutePath(), CameraConfiguration.class);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
logger.error("Camera config deserialization failed!", e);
|
logger.error("Camera config deserialization failed!", e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -302,63 +275,6 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
continue; // TODO how do we later try to load this camera if it gets reconnected?
|
continue; // TODO how do we later try to load this camera if it gets reconnected?
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we have only loaded the base stuff
|
|
||||||
// We still need to deserialize pipelines, as well as
|
|
||||||
// driver mode settings
|
|
||||||
var driverModeFile = Path.of(subdir.toString(), "drivermode.json");
|
|
||||||
DriverModePipelineSettings driverMode;
|
|
||||||
try {
|
|
||||||
driverMode =
|
|
||||||
JacksonUtils.deserialize(
|
|
||||||
driverModeFile.toAbsolutePath(), DriverModePipelineSettings.class);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
logger.error("Could not deserialize drivermode.json! Loading defaults");
|
|
||||||
logger.debug(Arrays.toString(e.getStackTrace()));
|
|
||||||
driverMode = new DriverModePipelineSettings();
|
|
||||||
}
|
|
||||||
if (driverMode == null) {
|
|
||||||
logger.warn(
|
|
||||||
"Could not load camera " + subdir + "'s drivermode.json! Loading" + " default");
|
|
||||||
driverMode = new DriverModePipelineSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load pipelines by mapping the files within the pipelines subdir
|
|
||||||
// to their deserialized equivalents
|
|
||||||
var pipelineSubdirectory = Path.of(subdir.toString(), "pipelines");
|
|
||||||
List<CVPipelineSettings> settings = Collections.emptyList();
|
|
||||||
if (pipelineSubdirectory.toFile().exists()) {
|
|
||||||
try (Stream<Path> subdirectoryFiles = Files.list(pipelineSubdirectory)) {
|
|
||||||
settings =
|
|
||||||
subdirectoryFiles
|
|
||||||
.filter(p -> p.toFile().isFile())
|
|
||||||
.map(
|
|
||||||
p -> {
|
|
||||||
var relativizedFilePath =
|
|
||||||
configDirectoryFile
|
|
||||||
.toPath()
|
|
||||||
.toAbsolutePath()
|
|
||||||
.relativize(p)
|
|
||||||
.toString();
|
|
||||||
try {
|
|
||||||
return JacksonUtils.deserialize(p, CVPipelineSettings.class);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
logger.error("Exception while deserializing " + relativizedFilePath, e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn(
|
|
||||||
"Could not load pipeline at "
|
|
||||||
+ relativizedFilePath
|
|
||||||
+ "! Skipping...");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedConfig.driveModeSettings = driverMode;
|
|
||||||
loadedConfig.addPipelineSettings(settings);
|
|
||||||
|
|
||||||
loadedConfigurations.put(subdir.toFile().getName(), loadedConfig);
|
loadedConfigurations.put(subdir.toFile().getName(), loadedConfig);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -17,16 +17,17 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import org.photonvision.common.hardware.Platform;
|
import org.photonvision.common.hardware.Platform;
|
||||||
import org.photonvision.common.networking.NetworkMode;
|
import org.photonvision.common.networking.NetworkMode;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class NetworkConfig {
|
public class NetworkConfig {
|
||||||
// Can be an integer team number, or an IP address
|
// Can be an integer team number, or an IP address
|
||||||
|
// MIGRATION: 2023
|
||||||
|
@Json.Alias("teamNumber")
|
||||||
public String ntServerAddress = "0";
|
public String ntServerAddress = "0";
|
||||||
|
|
||||||
public NetworkMode connectionType = NetworkMode.DHCP;
|
public NetworkMode connectionType = NetworkMode.DHCP;
|
||||||
public String staticIp = "";
|
public String staticIp = "";
|
||||||
public String hostname = "photonvision";
|
public String hostname = "photonvision";
|
||||||
@@ -34,8 +35,8 @@ public class NetworkConfig {
|
|||||||
public boolean shouldManage;
|
public boolean shouldManage;
|
||||||
public boolean shouldPublishProto = false;
|
public boolean shouldPublishProto = false;
|
||||||
|
|
||||||
@JsonIgnore public static final String NM_IFACE_STRING = "${interface}";
|
public static final String NM_IFACE_STRING = "${interface}";
|
||||||
@JsonIgnore public static final String NM_IP_STRING = "${ipaddr}";
|
public static final String NM_IP_STRING = "${ipaddr}";
|
||||||
|
|
||||||
public String networkManagerIface = "";
|
public String networkManagerIface = "";
|
||||||
// TODO: remove these strings if no longer needed
|
// TODO: remove these strings if no longer needed
|
||||||
@@ -50,19 +51,17 @@ public class NetworkConfig {
|
|||||||
setShouldManage(deviceCanManageNetwork());
|
setShouldManage(deviceCanManageNetwork());
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonCreator
|
|
||||||
public NetworkConfig(
|
public NetworkConfig(
|
||||||
@JsonProperty("ntServerAddress") @JsonAlias({"ntServerAddress", "teamNumber"})
|
String ntServerAddress,
|
||||||
String ntServerAddress,
|
NetworkMode connectionType,
|
||||||
@JsonProperty("connectionType") NetworkMode connectionType,
|
String staticIp,
|
||||||
@JsonProperty("staticIp") String staticIp,
|
String hostname,
|
||||||
@JsonProperty("hostname") String hostname,
|
boolean runNTServer,
|
||||||
@JsonProperty("runNTServer") boolean runNTServer,
|
boolean shouldManage,
|
||||||
@JsonProperty("shouldManage") boolean shouldManage,
|
boolean shouldPublishProto,
|
||||||
@JsonProperty("shouldPublishProto") boolean shouldPublishProto,
|
String networkManagerIface,
|
||||||
@JsonProperty("networkManagerIface") String networkManagerIface,
|
String setStaticCommand,
|
||||||
@JsonProperty("setStaticCommand") String setStaticCommand,
|
String setDHCPcommand) {
|
||||||
@JsonProperty("setDHCPcommand") String setDHCPcommand) {
|
|
||||||
this.ntServerAddress = ntServerAddress;
|
this.ntServerAddress = ntServerAddress;
|
||||||
this.connectionType = connectionType;
|
this.connectionType = connectionType;
|
||||||
this.staticIp = staticIp;
|
this.staticIp = staticIp;
|
||||||
@@ -89,12 +88,10 @@ public class NetworkConfig {
|
|||||||
config.setDHCPcommand);
|
config.setDHCPcommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public String getPhysicalInterfaceName() {
|
public String getPhysicalInterfaceName() {
|
||||||
return this.networkManagerIface;
|
return this.networkManagerIface;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public String getEscapedInterfaceName() {
|
public String getEscapedInterfaceName() {
|
||||||
return "\"" + networkManagerIface + "\"";
|
return "\"" + networkManagerIface + "\"";
|
||||||
}
|
}
|
||||||
@@ -103,7 +100,6 @@ public class NetworkConfig {
|
|||||||
this.shouldManage = shouldManage && this.deviceCanManageNetwork();
|
this.shouldManage = shouldManage && this.deviceCanManageNetwork();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
protected boolean deviceCanManageNetwork() {
|
protected boolean deviceCanManageNetwork() {
|
||||||
return Platform.isLinux();
|
return Platform.isLinux();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ public class NeuralNetworkModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ModelProperties properties =
|
ModelProperties properties =
|
||||||
ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().getModel(path);
|
ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().getModel(path);
|
||||||
|
|
||||||
if (properties == null) {
|
if (properties == null) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
@@ -332,7 +332,7 @@ public class NeuralNetworkModelManager {
|
|||||||
// NeuralNetworkModelsSettings
|
// NeuralNetworkModelsSettings
|
||||||
ConfigManager.getInstance()
|
ConfigManager.getInstance()
|
||||||
.getConfig()
|
.getConfig()
|
||||||
.neuralNetworkPropertyManager()
|
.getNeuralNetworkProperties()
|
||||||
.addModelProperties(properties);
|
.addModelProperties(properties);
|
||||||
} catch (IllegalArgumentException | IOException e) {
|
} catch (IllegalArgumentException | IOException e) {
|
||||||
logger.error("Failed to translate legacy model filename to properties: " + path, e);
|
logger.error("Failed to translate legacy model filename to properties: " + path, e);
|
||||||
@@ -486,7 +486,7 @@ public class NeuralNetworkModelManager {
|
|||||||
.getConfig()
|
.getConfig()
|
||||||
.setNeuralNetworkProperties(
|
.setNeuralNetworkProperties(
|
||||||
supportedProperties.sum(
|
supportedProperties.sum(
|
||||||
ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager()));
|
ConfigManager.getInstance().getConfig().getNeuralNetworkProperties()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean clearModels() {
|
public boolean clearModels() {
|
||||||
@@ -511,7 +511,7 @@ public class NeuralNetworkModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete model info
|
// Delete model info
|
||||||
return ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().clear();
|
return ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public File exportSingleModel(String modelPath) {
|
public File exportSingleModel(String modelPath) {
|
||||||
@@ -525,7 +525,7 @@ public class NeuralNetworkModelManager {
|
|||||||
ModelProperties properties =
|
ModelProperties properties =
|
||||||
ConfigManager.getInstance()
|
ConfigManager.getInstance()
|
||||||
.getConfig()
|
.getConfig()
|
||||||
.neuralNetworkPropertyManager()
|
.getNeuralNetworkProperties()
|
||||||
.getModel(Path.of(modelPath));
|
.getModel(Path.of(modelPath));
|
||||||
|
|
||||||
String fileName = "";
|
String fileName = "";
|
||||||
|
|||||||
@@ -17,9 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import io.avaje.jsonb.JsonType;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import io.avaje.jsonb.Types;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -27,27 +28,29 @@ import java.nio.file.Paths;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
|
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class NeuralNetworkModelsSettings {
|
public class NeuralNetworkModelsSettings {
|
||||||
/*
|
/*
|
||||||
* The properties of the model. This is used to determine which model to load.
|
* The properties of the model. This is used to determine which model to load.
|
||||||
* The only families currently supported are RKNN and Rubik (custom .tflite)
|
* The only families currently supported are RKNN and Rubik (custom .tflite)
|
||||||
*/
|
*/
|
||||||
|
@Json
|
||||||
public record ModelProperties(
|
public record ModelProperties(
|
||||||
@JsonProperty("modelPath") Path modelPath,
|
Path modelPath,
|
||||||
@JsonProperty("nickname") String nickname,
|
String nickname,
|
||||||
@JsonProperty("labels") List<String> labels,
|
List<String> labels,
|
||||||
@JsonProperty("resolutionWidth") int resolutionWidth,
|
int resolutionWidth,
|
||||||
@JsonProperty("resolutionHeight") int resolutionHeight,
|
int resolutionHeight,
|
||||||
@JsonProperty("family") Family family,
|
Family family,
|
||||||
@JsonProperty("version") Version version) {
|
Version version) {
|
||||||
@JsonCreator
|
|
||||||
public ModelProperties {}
|
|
||||||
|
|
||||||
ModelProperties(ModelProperties other) {
|
ModelProperties(ModelProperties other) {
|
||||||
this(
|
this(
|
||||||
other.modelPath,
|
other.modelPath,
|
||||||
@@ -59,13 +62,6 @@ public class NeuralNetworkModelsSettings {
|
|||||||
other.version);
|
other.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In v2025.3.1, this was single string for the model path. but the first argument
|
|
||||||
// is now nickname
|
|
||||||
public ModelProperties(@JsonProperty("nickname") String filename)
|
|
||||||
throws IllegalArgumentException, IOException {
|
|
||||||
this(createFromFilename(filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============= Migration code from v2025.3.1 ===========
|
// ============= Migration code from v2025.3.1 ===========
|
||||||
|
|
||||||
private static Pattern modelPattern =
|
private static Pattern modelPattern =
|
||||||
@@ -160,25 +156,58 @@ public class NeuralNetworkModelsSettings {
|
|||||||
|
|
||||||
// The path to the model is used as the key in the map because it is unique to
|
// The path to the model is used as the key in the map because it is unique to
|
||||||
// the model, and should not change
|
// the model, and should not change
|
||||||
@JsonProperty("modelPathToProperties")
|
@Json.Ignore
|
||||||
private HashMap<Path, ModelProperties> modelPathToProperties =
|
private HashMap<Path, ModelProperties> modelPathToProperties =
|
||||||
new HashMap<Path, ModelProperties>();
|
new HashMap<Path, ModelProperties>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the NeuralNetworkProperties class.
|
* Constructor for the NeuralNetworkProperties class.
|
||||||
*
|
*
|
||||||
* <p>This object holds a LinkedList of {@link ModelProperties} objects
|
* <p>This object holds a HashMap of {@link ModelProperties} objects
|
||||||
*/
|
*/
|
||||||
public NeuralNetworkModelsSettings() {}
|
public NeuralNetworkModelsSettings() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the NeuralNetworkProperties class.
|
* Constructor for the NeuralNetworkProperties class.
|
||||||
*
|
*
|
||||||
* <p>This object holds a LinkedList of {@link ModelProperties} objects.
|
* <p>This object holds a HashMap of {@link ModelProperties} objects.
|
||||||
|
*
|
||||||
|
* @param modelPropertiesMap When the class is constructed, it will hold the provided map
|
||||||
|
*/
|
||||||
|
public NeuralNetworkModelsSettings(HashMap<Path, ModelProperties> modelPropertiesMap) {
|
||||||
|
modelPathToProperties = modelPropertiesMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the NeuralNetworkProperties class.
|
||||||
|
*
|
||||||
|
* <p>This object holds a HashMap of {@link ModelProperties} objects.
|
||||||
*
|
*
|
||||||
* @param modelPropertiesList When the class is constructed, it will hold the provided list
|
* @param modelPropertiesList When the class is constructed, it will hold the provided list
|
||||||
*/
|
*/
|
||||||
public NeuralNetworkModelsSettings(HashMap<Path, ModelProperties> modelPropertiesList) {}
|
@Json.Creator
|
||||||
|
public NeuralNetworkModelsSettings(
|
||||||
|
ModelProperties[] models, @Json.Unmapped Map<String, Object> unmapped) {
|
||||||
|
JsonType<Map<String, ModelProperties>> modelPropsMapJsonb =
|
||||||
|
Jsonb.instance().type(Types.mapOf(ModelProperties.class));
|
||||||
|
Stream<ModelProperties> modelPropsStream;
|
||||||
|
if (models != null) {
|
||||||
|
modelPropsStream = Arrays.stream(models);
|
||||||
|
} else if (unmapped.containsKey("modelPathToProperties")) {
|
||||||
|
// MIGRATION: 2026
|
||||||
|
modelPropsStream =
|
||||||
|
modelPropsMapJsonb.fromObject(unmapped.get("modelPathToProperties")).values().stream();
|
||||||
|
} else {
|
||||||
|
modelPropsStream = Stream.empty();
|
||||||
|
}
|
||||||
|
this(
|
||||||
|
modelPropsStream.collect(
|
||||||
|
Collectors.toMap(
|
||||||
|
(model) -> model.modelPath(),
|
||||||
|
(model) -> model,
|
||||||
|
(prev, next) -> next,
|
||||||
|
HashMap::new)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
@@ -239,7 +268,7 @@ public class NeuralNetworkModelsSettings {
|
|||||||
*
|
*
|
||||||
* @return A list of all models
|
* @return A list of all models
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
@Json.Property("models")
|
||||||
public ModelProperties[] getModels() {
|
public ModelProperties[] getModels() {
|
||||||
return modelPathToProperties.values().toArray(new ModelProperties[0]);
|
return modelPathToProperties.values().toArray(new ModelProperties[0]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,25 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.photonvision.vision.processes.VisionSource;
|
import org.photonvision.vision.processes.VisionSource;
|
||||||
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class PhotonConfiguration {
|
public class PhotonConfiguration {
|
||||||
private final HardwareConfig hardwareConfig;
|
private final HardwareConfig hardwareConfig;
|
||||||
private final HardwareSettings hardwareSettings;
|
private final HardwareSettings hardwareSettings;
|
||||||
private NetworkConfig networkConfig;
|
private NetworkConfig networkConfig;
|
||||||
private AprilTagFieldLayout atfl;
|
|
||||||
|
@Json.Property("atfl")
|
||||||
|
private AprilTagFieldLayout aprilTagFieldLayout;
|
||||||
|
|
||||||
private NeuralNetworkModelsSettings neuralNetworkProperties;
|
private NeuralNetworkModelsSettings neuralNetworkProperties;
|
||||||
private HashMap<String, CameraConfiguration> cameraConfigurations;
|
private Map<String, CameraConfiguration> cameraConfigurations;
|
||||||
|
|
||||||
public PhotonConfiguration(
|
public PhotonConfiguration(
|
||||||
HardwareConfig hardwareConfig,
|
HardwareConfig hardwareConfig,
|
||||||
@@ -46,19 +52,20 @@ public class PhotonConfiguration {
|
|||||||
new HashMap<>());
|
new HashMap<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Json.Creator
|
||||||
public PhotonConfiguration(
|
public PhotonConfiguration(
|
||||||
HardwareConfig hardwareConfig,
|
HardwareConfig hardwareConfig,
|
||||||
HardwareSettings hardwareSettings,
|
HardwareSettings hardwareSettings,
|
||||||
NetworkConfig networkConfig,
|
NetworkConfig networkConfig,
|
||||||
AprilTagFieldLayout atfl,
|
AprilTagFieldLayout atfl,
|
||||||
NeuralNetworkModelsSettings neuralNetworkProperties,
|
NeuralNetworkModelsSettings neuralNetworkProperties,
|
||||||
HashMap<String, CameraConfiguration> cameraConfigurations) {
|
Map<String, CameraConfiguration> cameraConfigurations) {
|
||||||
this.hardwareConfig = hardwareConfig;
|
this.hardwareConfig = hardwareConfig;
|
||||||
this.hardwareSettings = hardwareSettings;
|
this.hardwareSettings = hardwareSettings;
|
||||||
this.networkConfig = networkConfig;
|
this.networkConfig = networkConfig;
|
||||||
this.neuralNetworkProperties = neuralNetworkProperties;
|
this.neuralNetworkProperties = neuralNetworkProperties;
|
||||||
this.cameraConfigurations = cameraConfigurations;
|
this.cameraConfigurations = cameraConfigurations;
|
||||||
this.atfl = atfl;
|
this.aprilTagFieldLayout = atfl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhotonConfiguration() {
|
public PhotonConfiguration() {
|
||||||
@@ -83,15 +90,15 @@ public class PhotonConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AprilTagFieldLayout getApriltagFieldLayout() {
|
public AprilTagFieldLayout getApriltagFieldLayout() {
|
||||||
return atfl;
|
return aprilTagFieldLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NeuralNetworkModelsSettings neuralNetworkPropertyManager() {
|
public NeuralNetworkModelsSettings getNeuralNetworkProperties() {
|
||||||
return neuralNetworkProperties;
|
return neuralNetworkProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setApriltagFieldLayout(AprilTagFieldLayout atfl) {
|
public void setApriltagFieldLayout(AprilTagFieldLayout atfl) {
|
||||||
this.atfl = atfl;
|
this.aprilTagFieldLayout = atfl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNetworkConfig(NetworkConfig networkConfig) {
|
public void setNetworkConfig(NetworkConfig networkConfig) {
|
||||||
@@ -102,7 +109,7 @@ public class PhotonConfiguration {
|
|||||||
this.neuralNetworkProperties = neuralNetworkProperties;
|
this.neuralNetworkProperties = neuralNetworkProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashMap<String, CameraConfiguration> getCameraConfigurations() {
|
public Map<String, CameraConfiguration> getCameraConfigurations() {
|
||||||
return cameraConfigurations;
|
return cameraConfigurations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +155,8 @@ public class PhotonConfiguration {
|
|||||||
+ hardwareSettings
|
+ hardwareSettings
|
||||||
+ "\n networkConfig="
|
+ "\n networkConfig="
|
||||||
+ networkConfig
|
+ networkConfig
|
||||||
+ "\n atfl="
|
+ "\n aprilTagFieldLayout="
|
||||||
+ atfl
|
+ aprilTagFieldLayout
|
||||||
+ "\n neuralNetworkProperties="
|
+ "\n neuralNetworkProperties="
|
||||||
+ neuralNetworkProperties
|
+ neuralNetworkProperties
|
||||||
+ "\n cameraConfigurations={"
|
+ "\n cameraConfigurations={"
|
||||||
|
|||||||
@@ -17,24 +17,27 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
|
import io.avaje.json.JsonException;
|
||||||
|
import io.avaje.jsonb.JsonType;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import io.avaje.jsonb.Types;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration.LegacyCameraConfigStruct;
|
import org.photonvision.common.configuration.CameraConfiguration.LegacyCameraConfigStruct;
|
||||||
import org.photonvision.common.configuration.DatabaseSchema.Columns;
|
import org.photonvision.common.configuration.DatabaseSchema.Columns;
|
||||||
import org.photonvision.common.configuration.DatabaseSchema.Tables;
|
import org.photonvision.common.configuration.DatabaseSchema.Tables;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
import org.photonvision.vision.camera.PVCameraInfo;
|
||||||
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
||||||
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
||||||
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
@@ -260,16 +263,16 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
T configObj;
|
T configObj;
|
||||||
if (!configString.isBlank()) {
|
if (!configString.isBlank()) {
|
||||||
try {
|
try {
|
||||||
configObj = JacksonUtils.deserialize(configString, ref);
|
configObj = Jsonb.instance().type(ref).fromJson(configString);
|
||||||
logger.info("Loaded " + ref.getSimpleName() + " from database");
|
logger.info("Loaded " + ref.getSimpleName() + " from database");
|
||||||
return configObj;
|
return configObj;
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize " + ref.getSimpleName() + " from database!", e);
|
logger.error("Could not deserialize " + ref.getSimpleName() + " from database!", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("No " + ref.getSimpleName() + " in database");
|
logger.debug("No " + ref.getSimpleName() + " in database");
|
||||||
}
|
}
|
||||||
// either the config entry is empty or Jackson threw an exception
|
// either the config entry is empty or Jsonb threw an exception
|
||||||
try {
|
try {
|
||||||
configObj = factory.get();
|
configObj = factory.get();
|
||||||
logger.info("Loaded default " + ref.getSimpleName());
|
logger.info("Loaded default " + ref.getSimpleName());
|
||||||
@@ -390,30 +393,16 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
|
|
||||||
var config = c.getValue();
|
var config = c.getValue();
|
||||||
statement.setString(1, c.getKey());
|
statement.setString(1, c.getKey());
|
||||||
statement.setString(2, JacksonUtils.serializeToString(config));
|
statement.setString(2, Jsonb.instance().type(CameraConfiguration.class).toJson(config));
|
||||||
statement.setString(3, JacksonUtils.serializeToString(config.driveModeSettings));
|
|
||||||
|
|
||||||
// Serializing a list of abstract classes sucks. Instead, make it into an array
|
// MIGRATION: 2026
|
||||||
// of strings, which we can later unpack back into individual settings
|
// We used to serialize pipelines separately, but don't anymore
|
||||||
List<String> settings =
|
statement.setString(3, "null");
|
||||||
config.pipelineSettings.stream()
|
statement.setString(4, "[]");
|
||||||
.map(
|
|
||||||
it -> {
|
|
||||||
try {
|
|
||||||
return JacksonUtils.serializeToString(it);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList();
|
|
||||||
statement.setString(4, JacksonUtils.serializeToString(settings));
|
|
||||||
|
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
|
} catch (SQLException | IllegalStateException | JsonException e) {
|
||||||
} catch (SQLException | IOException e) {
|
|
||||||
logger.error("Err saving cameras", e);
|
logger.error("Err saving cameras", e);
|
||||||
try {
|
try {
|
||||||
conn.rollback();
|
conn.rollback();
|
||||||
@@ -469,7 +458,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
addFile(
|
addFile(
|
||||||
statement1,
|
statement1,
|
||||||
GlobalKeys.HARDWARE_SETTINGS,
|
GlobalKeys.HARDWARE_SETTINGS,
|
||||||
JacksonUtils.serializeToString(config.getHardwareSettings()));
|
Jsonb.instance().type(HardwareSettings.class).toJson(config.getHardwareSettings()));
|
||||||
statement1.executeUpdate();
|
statement1.executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +467,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
addFile(
|
addFile(
|
||||||
statement2,
|
statement2,
|
||||||
GlobalKeys.NETWORK_CONFIG,
|
GlobalKeys.NETWORK_CONFIG,
|
||||||
JacksonUtils.serializeToString(config.getNetworkConfig()));
|
Jsonb.instance().type(NetworkConfig.class).toJson(config.getNetworkConfig()));
|
||||||
statement2.executeUpdate();
|
statement2.executeUpdate();
|
||||||
statement2.close();
|
statement2.close();
|
||||||
}
|
}
|
||||||
@@ -488,7 +477,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
addFile(
|
addFile(
|
||||||
statement3,
|
statement3,
|
||||||
GlobalKeys.HARDWARE_CONFIG,
|
GlobalKeys.HARDWARE_CONFIG,
|
||||||
JacksonUtils.serializeToString(config.getHardwareConfig()));
|
Jsonb.instance().type(HardwareConfig.class).toJson(config.getHardwareConfig()));
|
||||||
statement3.executeUpdate();
|
statement3.executeUpdate();
|
||||||
statement3.close();
|
statement3.close();
|
||||||
}
|
}
|
||||||
@@ -498,12 +487,14 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
addFile(
|
addFile(
|
||||||
statement3,
|
statement3,
|
||||||
GlobalKeys.NEURAL_NETWORK_PROPERTIES,
|
GlobalKeys.NEURAL_NETWORK_PROPERTIES,
|
||||||
JacksonUtils.serializeToString(config.neuralNetworkPropertyManager()));
|
Jsonb.instance()
|
||||||
|
.type(NeuralNetworkModelsSettings.class)
|
||||||
|
.toJson(config.getNeuralNetworkProperties()));
|
||||||
statement3.executeUpdate();
|
statement3.executeUpdate();
|
||||||
statement3.close();
|
statement3.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SQLException | IOException e) {
|
} catch (SQLException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Err saving global", e);
|
logger.error("Err saving global", e);
|
||||||
try {
|
try {
|
||||||
conn.rollback();
|
conn.rollback();
|
||||||
@@ -594,6 +585,12 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn) {
|
private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn) {
|
||||||
HashMap<String, CameraConfiguration> loadedConfigurations = new HashMap<>();
|
HashMap<String, CameraConfiguration> loadedConfigurations = new HashMap<>();
|
||||||
|
|
||||||
|
// MIGRATION: 2026
|
||||||
|
// This is designed to always match for efficiency reasons, so that the whole camera config
|
||||||
|
// isn't scanned. The second capture group determines if the camera info is in the old or new
|
||||||
|
// format
|
||||||
|
final var cameraInfoPattern = Pattern.compile("\"(PV[\\w.]*CameraInfo)\"\\s*(:?)");
|
||||||
|
|
||||||
// Query every single row of the cameras db
|
// Query every single row of the cameras db
|
||||||
PreparedStatement query = null;
|
PreparedStatement query = null;
|
||||||
try {
|
try {
|
||||||
@@ -614,57 +611,82 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
while (result.next()) {
|
while (result.next()) {
|
||||||
String uniqueName = "";
|
String uniqueName = "";
|
||||||
try {
|
try {
|
||||||
List<String> dummyList = new ArrayList<>();
|
JsonType<List<String>> strListJsonb = Jsonb.instance().type(Types.listOf(String.class));
|
||||||
|
|
||||||
uniqueName = result.getString(Columns.CAM_UNIQUE_NAME);
|
uniqueName = result.getString(Columns.CAM_UNIQUE_NAME);
|
||||||
|
|
||||||
// A horrifying hack to keep backward compat with otherpaths
|
// A horrifying hack to keep backward compat with otherpaths
|
||||||
// We -really- need to delete this -stupid- otherpaths column. I hate it.
|
// We -really- need to delete this -stupid- otherpaths column. I hate it.
|
||||||
var configStr = result.getString(Columns.CAM_CONFIG_JSON);
|
// MIGRATION: 2024
|
||||||
CameraConfiguration config =
|
var configJson = result.getString(Columns.CAM_CONFIG_JSON);
|
||||||
JacksonUtils.deserialize(configStr, CameraConfiguration.class);
|
|
||||||
|
|
||||||
|
// MIGRATION: 2026
|
||||||
|
var cameraInfoMatcher = cameraInfoPattern.matcher(configJson);
|
||||||
|
if (cameraInfoMatcher.find() && cameraInfoMatcher.group(2).equals(":")) {
|
||||||
|
logger.info("Legacy type-wrapper PVCameraInfo being migrated");
|
||||||
|
configJson = PVCameraInfo.remapConfigJson(configJson, cameraInfoMatcher.group(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraConfiguration config =
|
||||||
|
Jsonb.instance().type(CameraConfiguration.class).fromJson(configJson);
|
||||||
|
|
||||||
|
// MIGRATION: 2024
|
||||||
if (config.matchedCameraInfo == null) {
|
if (config.matchedCameraInfo == null) {
|
||||||
logger.info("Legacy CameraConfiguration detected - upgrading");
|
logger.info("Legacy CameraConfiguration detected - upgrading");
|
||||||
|
|
||||||
// manually create the matchedCameraInfo ourselves. Need to upgrade:
|
// manually create the matchedCameraInfo ourselves. Need to upgrade:
|
||||||
// baseName, path, otherPaths, cameraType, usbvid/pid -> matchedCameraInfo
|
// baseName, path, otherPaths, cameraType, usbvid/pid -> matchedCameraInfo
|
||||||
config.matchedCameraInfo =
|
config.matchedCameraInfo =
|
||||||
JacksonUtils.deserialize(configStr, LegacyCameraConfigStruct.class)
|
Jsonb.instance()
|
||||||
|
.type(LegacyCameraConfigStruct.class)
|
||||||
|
.fromJson(configJson)
|
||||||
.matchedCameraInfo;
|
.matchedCameraInfo;
|
||||||
|
|
||||||
// Except that otherPaths used to be its own column. so hack that in here as well
|
// Except that otherPaths used to be its own column. so hack that in here as well
|
||||||
var otherPaths =
|
var otherPaths =
|
||||||
JacksonUtils.deserialize(
|
Jsonb.instance()
|
||||||
result.getString(Columns.CAM_OTHERPATHS_JSON), String[].class);
|
.type(String[].class)
|
||||||
|
.fromJson(result.getString(Columns.CAM_OTHERPATHS_JSON));
|
||||||
if (config.matchedCameraInfo instanceof UsbCameraInfo usbInfo) {
|
if (config.matchedCameraInfo instanceof UsbCameraInfo usbInfo) {
|
||||||
usbInfo.otherPaths = otherPaths;
|
usbInfo.otherPaths = otherPaths;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var driverMode =
|
// MIGRATION: 2026
|
||||||
JacksonUtils.deserialize(
|
List<String> legacyPipelineSettings =
|
||||||
result.getString(Columns.CAM_DRIVERMODE_JSON), DriverModePipelineSettings.class);
|
strListJsonb.fromJson(result.getString(Columns.CAM_PIPELINE_JSONS));
|
||||||
List<?> pipelineSettings =
|
|
||||||
JacksonUtils.deserialize(
|
|
||||||
result.getString(Columns.CAM_PIPELINE_JSONS), dummyList.getClass());
|
|
||||||
|
|
||||||
List<CVPipelineSettings> loadedSettings = new ArrayList<>();
|
for (var pipelineJson : legacyPipelineSettings) {
|
||||||
for (var setting : pipelineSettings) {
|
logger.info("Importing pipeline JSON into camera settings");
|
||||||
if (setting instanceof String str) {
|
if (pipelineJson.startsWith("[")) {
|
||||||
try {
|
logger.info("Legacy type-wrapper CVPipelineSettings being migrated");
|
||||||
loadedSettings.add(JacksonUtils.deserialize(str, CVPipelineSettings.class));
|
pipelineJson = CVPipelineSettings.remapSettingsJson(pipelineJson);
|
||||||
} catch (IOException e) {
|
}
|
||||||
logger.error(
|
|
||||||
"Could not deserialize pipeline setting for camera " + config.nickname, e);
|
try {
|
||||||
}
|
config.pipelineSettings.add(
|
||||||
|
Jsonb.instance().type(CVPipelineSettings.class).fromJson(pipelineJson));
|
||||||
|
} catch (IllegalStateException | JsonException e) {
|
||||||
|
logger.error(
|
||||||
|
"Could not deserialize pipeline setting for camera " + config.nickname, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.pipelineSettings = loadedSettings;
|
// MIGRATION: 2026
|
||||||
config.driveModeSettings = driverMode;
|
if (config.driveModeSettings == null) {
|
||||||
|
logger.info("Importing driver mode JSON into camera settings");
|
||||||
|
var driverModeJson = result.getString(Columns.CAM_DRIVERMODE_JSON);
|
||||||
|
if (driverModeJson.startsWith("[")) {
|
||||||
|
logger.info("Legacy type-wrapper CVPipelineSettings being migrated");
|
||||||
|
driverModeJson = CVPipelineSettings.remapSettingsJson(driverModeJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.driveModeSettings =
|
||||||
|
Jsonb.instance().type(DriverModePipelineSettings.class).fromJson(driverModeJson);
|
||||||
|
}
|
||||||
|
|
||||||
loadedConfigurations.put(uniqueName, config);
|
loadedConfigurations.put(uniqueName, config);
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Could not deserialize camera configuration " + uniqueName + " from database!", e);
|
"Could not deserialize camera configuration " + uniqueName + " from database!", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public abstract class DataChangeSubscriber {
|
|||||||
this(DataChangeSource.AllSources, wantedDestinations);
|
this(DataChangeSource.AllSources, wantedDestinations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void onDataChangeEvent(DataChangeEvent<?> event);
|
public abstract <T> void onDataChangeEvent(DataChangeEvent<T> event);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.networktables;
|
package org.photonvision.common.dataflow.networktables;
|
||||||
|
|
||||||
import java.io.IOException;
|
import io.avaje.json.JsonException;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import org.photonvision.PhotonVersion;
|
import org.photonvision.PhotonVersion;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
@@ -34,7 +36,6 @@ import org.photonvision.common.logging.LogLevel;
|
|||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.networking.NetworkUtils;
|
import org.photonvision.common.networking.NetworkUtils;
|
||||||
import org.photonvision.common.util.TimedTaskManager;
|
import org.photonvision.common.util.TimedTaskManager;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.wpilib.driverstation.Alert;
|
import org.wpilib.driverstation.Alert;
|
||||||
import org.wpilib.driverstation.Alert.Level;
|
import org.wpilib.driverstation.Alert.Level;
|
||||||
import org.wpilib.networktables.LogMessage;
|
import org.wpilib.networktables.LogMessage;
|
||||||
@@ -199,7 +200,7 @@ public class NetworkTablesManager {
|
|||||||
var atfl_json = event.valueData.value.getString();
|
var atfl_json = event.valueData.value.getString();
|
||||||
try {
|
try {
|
||||||
System.out.println("Got new field layout!");
|
System.out.println("Got new field layout!");
|
||||||
var atfl = JacksonUtils.deserialize(atfl_json, AprilTagFieldLayout.class);
|
var atfl = Jsonb.instance().type(AprilTagFieldLayout.class).fromJson(atfl_json);
|
||||||
ConfigManager.getInstance().getConfig().setApriltagFieldLayout(atfl);
|
ConfigManager.getInstance().getConfig().setApriltagFieldLayout(atfl);
|
||||||
ConfigManager.getInstance().requestSave();
|
ConfigManager.getInstance().requestSave();
|
||||||
DataChangeService.getInstance()
|
DataChangeService.getInstance()
|
||||||
@@ -207,7 +208,7 @@ public class NetworkTablesManager {
|
|||||||
new OutgoingUIEvent<>(
|
new OutgoingUIEvent<>(
|
||||||
"fullsettings",
|
"fullsettings",
|
||||||
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
logger.error("Error deserializing atfl!");
|
logger.error("Error deserializing atfl!");
|
||||||
logger.error(atfl_json);
|
logger.error(atfl_json);
|
||||||
}
|
}
|
||||||
@@ -270,7 +271,7 @@ public class NetworkTablesManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
HashMap<String, CameraConfiguration> cameraConfigs =
|
Map<String, CameraConfiguration> cameraConfigs =
|
||||||
ConfigManager.getInstance().getConfig().getCameraConfigurations();
|
ConfigManager.getInstance().getConfig().getCameraConfigurations();
|
||||||
String[] cameraNames =
|
String[] cameraNames =
|
||||||
cameraConfigs.entrySet().stream()
|
cameraConfigs.entrySet().stream()
|
||||||
|
|||||||
@@ -17,13 +17,15 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.websocket;
|
package org.photonvision.common.dataflow.websocket;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.vision.calibration.UICameraCalibrationCoefficients;
|
import org.photonvision.vision.calibration.UICameraCalibrationCoefficients;
|
||||||
import org.photonvision.vision.camera.PVCameraInfo;
|
import org.photonvision.vision.camera.PVCameraInfo;
|
||||||
import org.photonvision.vision.camera.QuirkyCamera;
|
import org.photonvision.vision.camera.QuirkyCamera;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class UICameraConfiguration {
|
public class UICameraConfiguration {
|
||||||
// Path to the camera device. On Linux, this is a special file in /dev/v4l/by-id
|
// Path to the camera device. On Linux, this is a special file in /dev/v4l/by-id
|
||||||
// or /dev/videoN.
|
// or /dev/videoN.
|
||||||
@@ -37,10 +39,10 @@ public class UICameraConfiguration {
|
|||||||
public String uniqueName;
|
public String uniqueName;
|
||||||
|
|
||||||
public double fov;
|
public double fov;
|
||||||
public HashMap<String, Object> currentPipelineSettings;
|
public Map<String, Object> currentPipelineSettings;
|
||||||
public int currentPipelineIndex;
|
public int currentPipelineIndex;
|
||||||
public List<String> pipelineNicknames;
|
public List<String> pipelineNicknames;
|
||||||
public HashMap<Integer, HashMap<String, Object>> videoFormatList;
|
public List<Map<String, Object>> videoFormatList;
|
||||||
public int outputStreamPort;
|
public int outputStreamPort;
|
||||||
public int inputStreamPort;
|
public int inputStreamPort;
|
||||||
public List<UICameraCalibrationCoefficients> calibrations;
|
public List<UICameraCalibrationCoefficients> calibrations;
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.websocket;
|
package org.photonvision.common.dataflow.websocket;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelsSettings;
|
import org.photonvision.common.configuration.NeuralNetworkModelsSettings;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class UIGeneralSettings {
|
public class UIGeneralSettings {
|
||||||
public UIGeneralSettings(
|
public UIGeneralSettings(
|
||||||
String version,
|
String version,
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.websocket;
|
package org.photonvision.common.dataflow.websocket;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class UILightingConfig {
|
public class UILightingConfig {
|
||||||
public UILightingConfig(int brightness, boolean supported) {
|
public UILightingConfig(int brightness, boolean supported) {
|
||||||
this.brightness = brightness;
|
this.brightness = brightness;
|
||||||
|
|||||||
@@ -17,11 +17,18 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.websocket;
|
package org.photonvision.common.dataflow.websocket;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.photonvision.common.configuration.NetworkConfig;
|
import org.photonvision.common.configuration.NetworkConfig;
|
||||||
import org.photonvision.common.networking.NetworkUtils.NMDeviceInfo;
|
import org.photonvision.common.networking.NetworkUtils.NMDeviceInfo;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class UINetConfig extends NetworkConfig {
|
public class UINetConfig extends NetworkConfig {
|
||||||
|
// Constructor for JSON to allow all properties to be set directly
|
||||||
|
public UINetConfig() {
|
||||||
|
this.canManage = this.deviceCanManageNetwork();
|
||||||
|
}
|
||||||
|
|
||||||
public UINetConfig(
|
public UINetConfig(
|
||||||
NetworkConfig config, List<NMDeviceInfo> networkInterfaceNames, boolean networkingDisabled) {
|
NetworkConfig config, List<NMDeviceInfo> networkInterfaceNames, boolean networkingDisabled) {
|
||||||
super(config);
|
super(config);
|
||||||
@@ -32,5 +39,7 @@ public class UINetConfig extends NetworkConfig {
|
|||||||
|
|
||||||
public List<NMDeviceInfo> networkInterfaceNames;
|
public List<NMDeviceInfo> networkInterfaceNames;
|
||||||
public boolean networkingDisabled;
|
public boolean networkingDisabled;
|
||||||
|
|
||||||
|
@Json.Ignore(serialize = true)
|
||||||
public boolean canManage;
|
public boolean canManage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.websocket;
|
package org.photonvision.common.dataflow.websocket;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.photonvision.PhotonVersion;
|
import org.photonvision.PhotonVersion;
|
||||||
import org.photonvision.common.LoadJNI;
|
import org.photonvision.common.LoadJNI;
|
||||||
@@ -31,6 +32,7 @@ import org.photonvision.common.networking.NetworkUtils;
|
|||||||
import org.photonvision.vision.processes.VisionModule;
|
import org.photonvision.vision.processes.VisionModule;
|
||||||
import org.photonvision.vision.processes.VisionSourceManager;
|
import org.photonvision.vision.processes.VisionSourceManager;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class UIPhotonConfiguration {
|
public class UIPhotonConfiguration {
|
||||||
public List<UICameraConfiguration> cameraSettings;
|
public List<UICameraConfiguration> cameraSettings;
|
||||||
public UIProgramSettings settings;
|
public UIProgramSettings settings;
|
||||||
@@ -59,7 +61,7 @@ public class UIPhotonConfiguration {
|
|||||||
// TODO add support for other types of GPU accel
|
// TODO add support for other types of GPU accel
|
||||||
LoadJNI.hasLoaded(JNITypes.LIBCAMERA) ? "Zerocopy Libcamera Working" : "",
|
LoadJNI.hasLoaded(JNITypes.LIBCAMERA) ? "Zerocopy Libcamera Working" : "",
|
||||||
LoadJNI.hasLoaded(JNITypes.MRCAL),
|
LoadJNI.hasLoaded(JNITypes.MRCAL),
|
||||||
c.neuralNetworkPropertyManager().getModels(),
|
c.getNeuralNetworkProperties().getModels(),
|
||||||
NeuralNetworkModelManager.getInstance().getSupportedBackends(),
|
NeuralNetworkModelManager.getInstance().getSupportedBackends(),
|
||||||
c.getHardwareConfig().deviceName.isEmpty()
|
c.getHardwareConfig().deviceName.isEmpty()
|
||||||
? Platform.getHardwareModel()
|
? Platform.getHardwareModel()
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.websocket;
|
package org.photonvision.common.dataflow.websocket;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class UIProgramSettings {
|
public class UIProgramSettings {
|
||||||
public UIProgramSettings(
|
public UIProgramSettings(
|
||||||
UINetConfig networkSettings,
|
UINetConfig networkSettings,
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
|
|
||||||
package org.photonvision.common.hardware;
|
package org.photonvision.common.hardware;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
@@ -35,26 +37,22 @@ import org.photonvision.common.logging.Logger;
|
|||||||
public class OsImageData {
|
public class OsImageData {
|
||||||
private static final Logger logger = new Logger(OsImageData.class, LogGroup.General);
|
private static final Logger logger = new Logger(OsImageData.class, LogGroup.General);
|
||||||
|
|
||||||
private static Path imageMetadataFile = Path.of("/opt/photonvision/image-version.json");
|
private static File imageMetadataFile = Path.of("/opt/photonvision/image-version.json").toFile();
|
||||||
|
|
||||||
public static final Optional<ImageMetadata> IMAGE_METADATA = getImageMetadata();
|
public static final Optional<ImageMetadata> IMAGE_METADATA = getImageMetadata();
|
||||||
|
|
||||||
|
@Json(naming = Json.Naming.LowerUnderscore)
|
||||||
public static record ImageMetadata(
|
public static record ImageMetadata(
|
||||||
String buildDate, String commitSha, String commitTag, String imageName, String imageSource) {}
|
String buildDate, String commitSha, String commitTag, String imageName, String imageSource) {}
|
||||||
|
|
||||||
private static Optional<ImageMetadata> getImageMetadata() {
|
private static Optional<ImageMetadata> getImageMetadata() {
|
||||||
if (!imageMetadataFile.toFile().exists()) {
|
if (!imageMetadataFile.exists()) {
|
||||||
logger.warn("Photon cannot locate OS image metadata at " + imageMetadataFile.toString());
|
logger.warn("Photon cannot locate OS image metadata at " + imageMetadataFile.toString());
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try (InputStream stream = new FileInputStream(imageMetadataFile)) {
|
||||||
String content = Files.readString(imageMetadataFile).strip();
|
ImageMetadata md = Jsonb.instance().type(ImageMetadata.class).fromJson(stream);
|
||||||
|
|
||||||
ObjectMapper mapper =
|
|
||||||
new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
|
||||||
|
|
||||||
ImageMetadata md = mapper.readValue(content, ImageMetadata.class);
|
|
||||||
|
|
||||||
if (md.buildDate() == null
|
if (md.buildDate() == null
|
||||||
&& md.commitSha() == null
|
&& md.commitSha() == null
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.common.hardware.metrics;
|
package org.photonvision.common.hardware.metrics;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import org.photonvision.common.hardware.metrics.proto.DeviceMetricsProto;
|
import org.photonvision.common.hardware.metrics.proto.DeviceMetricsProto;
|
||||||
|
|
||||||
|
@Json
|
||||||
public record DeviceMetrics(
|
public record DeviceMetrics(
|
||||||
double cpuTemp,
|
double cpuTemp,
|
||||||
double cpuUtil,
|
double cpuUtil,
|
||||||
|
|||||||
@@ -17,13 +17,14 @@
|
|||||||
|
|
||||||
package org.photonvision.common.networking;
|
package org.photonvision.common.networking;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
import io.avaje.jsonb.Json;
|
||||||
|
|
||||||
|
@Json
|
||||||
public enum NetworkMode {
|
public enum NetworkMode {
|
||||||
DHCP,
|
DHCP,
|
||||||
STATIC;
|
STATIC;
|
||||||
|
|
||||||
@JsonValue
|
@Json.Value
|
||||||
public int toValue() {
|
public int toValue() {
|
||||||
return ordinal();
|
return ordinal();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.common.networking;
|
package org.photonvision.common.networking;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.NetworkInterface;
|
import java.net.NetworkInterface;
|
||||||
@@ -62,9 +63,10 @@ public class NetworkUtils {
|
|||||||
* @param devName The underlying device name, used by dhclient
|
* @param devName The underlying device name, used by dhclient
|
||||||
* @param nmType The NetworkManager device type
|
* @param nmType The NetworkManager device type
|
||||||
*/
|
*/
|
||||||
|
@Json
|
||||||
public static record NMDeviceInfo(String connName, String devName, NMType nmType) {
|
public static record NMDeviceInfo(String connName, String devName, NMType nmType) {
|
||||||
public NMDeviceInfo(String c, String d, String type) {
|
public NMDeviceInfo(String connName, String devName, String nmType) {
|
||||||
this(c, d, NMType.typeForString(type));
|
this(connName, devName, NMType.typeForString(nmType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
|
|
||||||
package org.photonvision.common.util;
|
package org.photonvision.common.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.json.JsonException;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.awt.HeadlessException;
|
import java.awt.HeadlessException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import org.opencv.core.Mat;
|
import org.opencv.core.Mat;
|
||||||
@@ -342,12 +344,10 @@ public class TestUtils {
|
|||||||
public static final String LIMELIGHT_480P_CAL_FILE = "limelight_1280_720.json";
|
public static final String LIMELIGHT_480P_CAL_FILE = "limelight_1280_720.json";
|
||||||
|
|
||||||
public static CameraCalibrationCoefficients getCoeffs(String filename, boolean testMode) {
|
public static CameraCalibrationCoefficients getCoeffs(String filename, boolean testMode) {
|
||||||
try {
|
try (var stream =
|
||||||
return new ObjectMapper()
|
new FileInputStream(Path.of(getCalibrationPath(testMode).toString(), filename).toFile())) {
|
||||||
.readValue(
|
return Jsonb.instance().type(CameraCalibrationCoefficients.class).fromJson(stream);
|
||||||
(Path.of(getCalibrationPath(testMode).toString(), filename).toFile()),
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
CameraCalibrationCoefficients.class);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.common.util.file;
|
||||||
|
|
||||||
|
import io.avaje.json.JsonAdapter;
|
||||||
|
import io.avaje.json.JsonReader;
|
||||||
|
import io.avaje.json.JsonWriter;
|
||||||
|
import io.avaje.json.PropertyNames;
|
||||||
|
import io.avaje.jsonb.CustomAdapter;
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import io.avaje.jsonb.Types;
|
||||||
|
import java.util.List;
|
||||||
|
import org.wpilib.vision.apriltag.AprilTag;
|
||||||
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
|
|
||||||
|
@Json.Import(AprilTag.class)
|
||||||
|
@CustomAdapter
|
||||||
|
public class AprilTagFieldLayoutJsonAdapter implements JsonAdapter<AprilTagFieldLayout> {
|
||||||
|
@Json
|
||||||
|
record FieldDimensions(double length, double width) {}
|
||||||
|
|
||||||
|
JsonAdapter<List<AprilTag>> aprilTagListJsonAdapter;
|
||||||
|
JsonAdapter<FieldDimensions> fieldDimensionsJsonAdapter;
|
||||||
|
PropertyNames names;
|
||||||
|
|
||||||
|
public AprilTagFieldLayoutJsonAdapter(Jsonb jsonb) {
|
||||||
|
aprilTagListJsonAdapter = jsonb.adapter(Types.listOf(AprilTag.class));
|
||||||
|
fieldDimensionsJsonAdapter = jsonb.adapter(FieldDimensions.class);
|
||||||
|
names = jsonb.properties("tags", "field");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toJson(JsonWriter writer, AprilTagFieldLayout value) {
|
||||||
|
writer.beginObject(names);
|
||||||
|
writer.name(0);
|
||||||
|
aprilTagListJsonAdapter.toJson(writer, value.getTags());
|
||||||
|
writer.name(1);
|
||||||
|
fieldDimensionsJsonAdapter.toJson(
|
||||||
|
writer, new FieldDimensions(value.getFieldLength(), value.getFieldWidth()));
|
||||||
|
writer.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AprilTagFieldLayout fromJson(JsonReader reader) {
|
||||||
|
List<AprilTag> tags = null;
|
||||||
|
FieldDimensions field = null;
|
||||||
|
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNextField()) {
|
||||||
|
final String fieldName = reader.nextField();
|
||||||
|
switch (fieldName) {
|
||||||
|
case "tags":
|
||||||
|
tags = aprilTagListJsonAdapter.fromJson(reader);
|
||||||
|
break;
|
||||||
|
case "field":
|
||||||
|
field = fieldDimensionsJsonAdapter.fromJson(reader);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reader.unmappedField(fieldName);
|
||||||
|
reader.skipValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
|
return new AprilTagFieldLayout(tags, field.length, field.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) Photon Vision.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.photonvision.common.util.file;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
|
||||||
import com.fasterxml.jackson.core.StreamReadFeature;
|
|
||||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
|
||||||
import com.fasterxml.jackson.databind.ext.NioPathDeserializer;
|
|
||||||
import com.fasterxml.jackson.databind.ext.NioPathSerializer;
|
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
|
||||||
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
|
|
||||||
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
|
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.eclipse.jetty.io.EofException;
|
|
||||||
|
|
||||||
public class JacksonUtils {
|
|
||||||
public static class UIMap extends HashMap<String, Object> {}
|
|
||||||
|
|
||||||
// Custom Path key deserializer for Maps with Path keys
|
|
||||||
public static class PathKeySerializer
|
|
||||||
extends com.fasterxml.jackson.databind.JsonSerializer<Path> {
|
|
||||||
@Override
|
|
||||||
public void serialize(Path value, JsonGenerator gen, SerializerProvider serializers)
|
|
||||||
throws IOException {
|
|
||||||
if (value == null) {
|
|
||||||
gen.writeNull();
|
|
||||||
} else {
|
|
||||||
gen.writeFieldName(value.toUri().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom Path key deserializer for Maps with Path keys
|
|
||||||
public static class PathKeyDeserializer extends com.fasterxml.jackson.databind.KeyDeserializer {
|
|
||||||
@Override
|
|
||||||
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
|
|
||||||
if (key == null || key.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Paths.get(URI.create(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method to create ObjectMapper with Path serialization support
|
|
||||||
private static ObjectMapper createObjectMapperWithPathSupport(Class<?> baseType) {
|
|
||||||
PolymorphicTypeValidator ptv =
|
|
||||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(baseType).build();
|
|
||||||
|
|
||||||
SimpleModule pathModule = new SimpleModule();
|
|
||||||
pathModule.addSerializer(Path.class, new NioPathSerializer());
|
|
||||||
pathModule.addKeySerializer(Path.class, new PathKeySerializer());
|
|
||||||
pathModule.addDeserializer(Path.class, new NioPathDeserializer());
|
|
||||||
pathModule.addKeyDeserializer(Path.class, new PathKeyDeserializer());
|
|
||||||
|
|
||||||
return JsonMapper.builder()
|
|
||||||
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
|
|
||||||
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
|
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
|
||||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
|
||||||
.addModule(pathModule)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> void serialize(Path path, T object) throws IOException {
|
|
||||||
serialize(path, object, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> String serializeToString(T object) throws IOException {
|
|
||||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
|
|
||||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> void serialize(Path path, T object, boolean forceSync) throws IOException {
|
|
||||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
|
|
||||||
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
|
||||||
saveJsonString(json, path, forceSync);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T deserialize(Map<?, ?> s, Class<T> ref) throws IOException {
|
|
||||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
|
|
||||||
return objectMapper.convertValue(s, ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T deserialize(String s, Class<T> ref) throws IOException {
|
|
||||||
if (s.length() == 0) {
|
|
||||||
throw new EofException("Provided empty string for class " + ref.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
|
|
||||||
objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
|
|
||||||
|
|
||||||
return objectMapper.readValue(s, ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T deserialize(Path path, Class<T> ref) throws IOException {
|
|
||||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
|
|
||||||
File jsonFile = new File(path.toString());
|
|
||||||
if (jsonFile.exists() && jsonFile.length() > 0) {
|
|
||||||
return objectMapper.readValue(jsonFile, ref);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void saveJsonString(String json, Path path, boolean forceSync) throws IOException {
|
|
||||||
var file = path.toFile();
|
|
||||||
if (file.getParentFile() != null && !file.getParentFile().exists()) {
|
|
||||||
file.getParentFile().mkdirs();
|
|
||||||
}
|
|
||||||
if (!file.exists()) {
|
|
||||||
if (!file.canWrite()) {
|
|
||||||
file.setWritable(true);
|
|
||||||
}
|
|
||||||
file.createNewFile();
|
|
||||||
}
|
|
||||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
|
||||||
fileOutputStream.write(json.getBytes());
|
|
||||||
fileOutputStream.flush();
|
|
||||||
if (forceSync) {
|
|
||||||
FileDescriptor fileDescriptor = fileOutputStream.getFD();
|
|
||||||
fileDescriptor.sync();
|
|
||||||
}
|
|
||||||
fileOutputStream.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.common.util.file;
|
||||||
|
|
||||||
|
import io.avaje.json.JsonAdapter;
|
||||||
|
import io.avaje.json.JsonReader;
|
||||||
|
import io.avaje.json.JsonWriter;
|
||||||
|
import io.avaje.jsonb.CustomAdapter;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@CustomAdapter
|
||||||
|
public class PathAdapter implements JsonAdapter<Path> {
|
||||||
|
public PathAdapter(Jsonb jsonb) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path fromJson(JsonReader reader) {
|
||||||
|
return Path.of(URI.create(reader.readString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toJson(JsonWriter writer, Path value) {
|
||||||
|
writer.value(value.toUri().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.common.util.file;
|
||||||
|
|
||||||
|
import io.avaje.json.JsonAdapter;
|
||||||
|
import io.avaje.json.JsonReader;
|
||||||
|
import io.avaje.json.JsonWriter;
|
||||||
|
import io.avaje.json.PropertyNames;
|
||||||
|
import io.avaje.json.view.ViewBuilder;
|
||||||
|
import io.avaje.json.view.ViewBuilderAware;
|
||||||
|
import io.avaje.jsonb.CustomAdapter;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import org.wpilib.math.geometry.Pose3d;
|
||||||
|
import org.wpilib.math.geometry.Rotation3d;
|
||||||
|
import org.wpilib.math.geometry.Translation3d;
|
||||||
|
|
||||||
|
@CustomAdapter
|
||||||
|
public class Pose3dJsonAdapter implements JsonAdapter<Pose3d>, ViewBuilderAware {
|
||||||
|
private final JsonAdapter<Translation3d> translation3dJsonAdapter;
|
||||||
|
private final JsonAdapter<Rotation3d> rotation3dJsonAdapter;
|
||||||
|
private final PropertyNames names;
|
||||||
|
|
||||||
|
public Pose3dJsonAdapter(Jsonb jsonb) {
|
||||||
|
this.translation3dJsonAdapter = jsonb.adapter(Translation3d.class);
|
||||||
|
this.rotation3dJsonAdapter = jsonb.adapter(Rotation3d.class);
|
||||||
|
this.names = jsonb.properties("translation", "rotation");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toJson(JsonWriter writer, Pose3d value) {
|
||||||
|
writer.beginObject(names);
|
||||||
|
writer.name(0);
|
||||||
|
translation3dJsonAdapter.toJson(writer, value.getTranslation());
|
||||||
|
writer.name(1);
|
||||||
|
rotation3dJsonAdapter.toJson(writer, value.getRotation());
|
||||||
|
writer.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pose3d fromJson(JsonReader reader) {
|
||||||
|
Translation3d translation = null;
|
||||||
|
Rotation3d rotation = null;
|
||||||
|
|
||||||
|
reader.beginObject(names);
|
||||||
|
while (reader.hasNextField()) {
|
||||||
|
final String fieldName = reader.nextField();
|
||||||
|
switch (fieldName) {
|
||||||
|
case "translation":
|
||||||
|
translation = translation3dJsonAdapter.fromJson(reader);
|
||||||
|
break;
|
||||||
|
case "rotation":
|
||||||
|
rotation = rotation3dJsonAdapter.fromJson(reader);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reader.unmappedField(fieldName);
|
||||||
|
reader.skipValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
|
return new Pose3d(translation, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isViewBuilderAware() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewBuilderAware viewBuild() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(ViewBuilder builder, String name, MethodHandle handle) {
|
||||||
|
builder.beginObject(name, handle);
|
||||||
|
builder.add(
|
||||||
|
"translation",
|
||||||
|
translation3dJsonAdapter,
|
||||||
|
builder.method(Pose3d.class, "getTranslation", Translation3d.class));
|
||||||
|
builder.add(
|
||||||
|
"rotation",
|
||||||
|
rotation3dJsonAdapter,
|
||||||
|
builder.method(Pose3d.class, "getRotation", Rotation3d.class));
|
||||||
|
builder.endObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.common.util.file;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
|
import org.wpilib.math.geometry.Quaternion;
|
||||||
|
|
||||||
|
@Json.MixIn(Quaternion.class)
|
||||||
|
public class QuaternionMixIn {
|
||||||
|
@Json.Ignore(deserialize = true)
|
||||||
|
@Json.Property("W")
|
||||||
|
double m_w;
|
||||||
|
|
||||||
|
@Json.Ignore(deserialize = true)
|
||||||
|
@Json.Property("X")
|
||||||
|
double m_x;
|
||||||
|
|
||||||
|
@Json.Ignore(deserialize = true)
|
||||||
|
@Json.Property("Y")
|
||||||
|
double m_y;
|
||||||
|
|
||||||
|
@Json.Ignore(deserialize = true)
|
||||||
|
@Json.Property("Z")
|
||||||
|
double m_z;
|
||||||
|
|
||||||
|
@Json.Creator
|
||||||
|
public static Quaternion construct(double m_w, double m_x, double m_y, double m_z) {
|
||||||
|
return new Quaternion(m_w, m_x, m_y, m_z);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.common.util.file;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
|
import org.wpilib.math.geometry.Quaternion;
|
||||||
|
import org.wpilib.math.geometry.Rotation3d;
|
||||||
|
|
||||||
|
@Json.MixIn(Rotation3d.class)
|
||||||
|
public class Rotation3dMixIn {
|
||||||
|
@Json.Ignore(deserialize = true)
|
||||||
|
@Json.Property("quaternion")
|
||||||
|
Quaternion m_q;
|
||||||
|
|
||||||
|
@Json.Creator
|
||||||
|
public static Rotation3d construct(Quaternion m_q) {
|
||||||
|
return new Rotation3d(m_q);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.common.util.file;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
|
import org.wpilib.math.geometry.Translation3d;
|
||||||
|
|
||||||
|
@Json.MixIn(Translation3d.class)
|
||||||
|
public class Translation3dMixIn {
|
||||||
|
@Json.Ignore(deserialize = true)
|
||||||
|
@Json.Property("x")
|
||||||
|
double m_x;
|
||||||
|
|
||||||
|
@Json.Ignore(deserialize = true)
|
||||||
|
@Json.Property("y")
|
||||||
|
double m_y;
|
||||||
|
|
||||||
|
@Json.Ignore(deserialize = true)
|
||||||
|
@Json.Property("z")
|
||||||
|
double m_z;
|
||||||
|
|
||||||
|
@Json.Creator
|
||||||
|
public static Translation3d construct(double m_x, double m_y, double m_z) {
|
||||||
|
return new Translation3d(m_x, m_y, m_z);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.common.util.numbers;
|
package org.photonvision.common.util.numbers;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import org.opencv.core.Point;
|
import org.opencv.core.Point;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class DoubleCouple extends NumberCouple<Double> {
|
public class DoubleCouple extends NumberCouple<Double> {
|
||||||
public DoubleCouple() {
|
public DoubleCouple() {
|
||||||
super(0.0, 0.0);
|
super(0.0, 0.0);
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
package org.photonvision.common.util.numbers;
|
package org.photonvision.common.util.numbers;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class IntegerCouple extends NumberCouple<Integer> {
|
public class IntegerCouple extends NumberCouple<Integer> {
|
||||||
public IntegerCouple() {
|
public IntegerCouple() {
|
||||||
super(0, 0);
|
super(0, 0);
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
|
|
||||||
package org.photonvision.common.util.numbers;
|
package org.photonvision.common.util.numbers;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
|
|
||||||
public abstract class NumberCouple<T extends Number> {
|
public abstract class NumberCouple<T extends Number> {
|
||||||
protected T first;
|
protected T first;
|
||||||
protected T second;
|
protected T second;
|
||||||
@@ -56,7 +54,6 @@ public abstract class NumberCouple<T extends Number> {
|
|||||||
&& couple.second.equals(second);
|
&& couple.second.equals(second);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return first.intValue() == 0 && second.intValue() == 0;
|
return first.intValue() == 0 && second.intValue() == 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.calibration;
|
package org.photonvision.vision.calibration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -36,45 +33,36 @@ import org.opencv.imgproc.Imgproc;
|
|||||||
import org.photonvision.common.util.ColorHelper;
|
import org.photonvision.common.util.ColorHelper;
|
||||||
import org.wpilib.math.geometry.Pose3d;
|
import org.wpilib.math.geometry.Pose3d;
|
||||||
|
|
||||||
|
@Json
|
||||||
// Ignore the previous calibration data that was stored in the json file.
|
// Ignore the previous calibration data that was stored in the json file.
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
public final class BoardObservation implements Cloneable {
|
public final class BoardObservation implements Cloneable {
|
||||||
// Expected feature 3d location in the camera frame
|
// Expected feature 3d location in the camera frame
|
||||||
@JsonProperty("locationInObjectSpace")
|
|
||||||
public List<Point3> locationInObjectSpace;
|
public List<Point3> locationInObjectSpace;
|
||||||
|
|
||||||
// Observed location in pixel space
|
// Observed location in pixel space
|
||||||
@JsonProperty("locationInImageSpace")
|
|
||||||
public List<Point> locationInImageSpace;
|
public List<Point> locationInImageSpace;
|
||||||
|
|
||||||
// (measured location in pixels) - (expected from FK)
|
// (measured location in pixels) - (expected from FK)
|
||||||
@JsonProperty("reprojectionErrors")
|
|
||||||
public List<Point> reprojectionErrors;
|
public List<Point> reprojectionErrors;
|
||||||
|
|
||||||
// Solver optimized board poses
|
// Solver optimized board poses
|
||||||
@JsonProperty("optimisedCameraToObject")
|
|
||||||
public Pose3d optimisedCameraToObject;
|
public Pose3d optimisedCameraToObject;
|
||||||
|
|
||||||
// If we should use this observation when re-calculating camera calibration
|
// If we should use this observation when re-calculating camera calibration
|
||||||
@JsonProperty("cornersUsed")
|
|
||||||
public boolean[] cornersUsed;
|
public boolean[] cornersUsed;
|
||||||
|
|
||||||
@JsonProperty("snapshotName")
|
|
||||||
public String snapshotName;
|
public String snapshotName;
|
||||||
|
|
||||||
@JsonProperty("snapshotDataLocation")
|
@Nullable public Path snapshotDataLocation;
|
||||||
@Nullable
|
|
||||||
public Path snapshotDataLocation;
|
|
||||||
|
|
||||||
@JsonCreator
|
|
||||||
public BoardObservation(
|
public BoardObservation(
|
||||||
@JsonProperty("locationInObjectSpace") List<Point3> locationInObjectSpace,
|
List<Point3> locationInObjectSpace,
|
||||||
@JsonProperty("locationInImageSpace") List<Point> locationInImageSpace,
|
List<Point> locationInImageSpace,
|
||||||
@JsonProperty("reprojectionErrors") List<Point> reprojectionErrors,
|
List<Point> reprojectionErrors,
|
||||||
@JsonProperty("optimisedCameraToObject") Pose3d optimisedCameraToObject,
|
Pose3d optimisedCameraToObject,
|
||||||
@JsonProperty("cornersUsed") boolean[] cornersUsed,
|
boolean[] cornersUsed,
|
||||||
@JsonProperty("snapshotName") String snapshotName,
|
String snapshotName,
|
||||||
@JsonProperty("snapshotDataLocation") Path snapshotDataLocation) {
|
Path snapshotDataLocation) {
|
||||||
this.locationInObjectSpace = locationInObjectSpace;
|
this.locationInObjectSpace = locationInObjectSpace;
|
||||||
this.locationInImageSpace = locationInImageSpace;
|
this.locationInImageSpace = locationInImageSpace;
|
||||||
this.reprojectionErrors = reprojectionErrors;
|
this.reprojectionErrors = reprojectionErrors;
|
||||||
@@ -119,7 +107,6 @@ public final class BoardObservation implements Cloneable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
/**
|
/**
|
||||||
* Load the captured board image from disk. Allocates a new Mat, which the caller is responsible
|
* Load the captured board image from disk. Allocates a new Mat, which the caller is responsible
|
||||||
* for releasing.
|
* for releasing.
|
||||||
@@ -141,7 +128,6 @@ public final class BoardObservation implements Cloneable {
|
|||||||
* @return Annotated image, or null if the image could not be loaded. Caller is responsible for
|
* @return Annotated image, or null if the image could not be loaded. Caller is responsible for
|
||||||
* releasing the Mat.
|
* releasing the Mat.
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
|
||||||
public Mat annotateImage() {
|
public Mat annotateImage() {
|
||||||
var image = loadImage();
|
var image = loadImage();
|
||||||
|
|
||||||
@@ -179,7 +165,6 @@ public final class BoardObservation implements Cloneable {
|
|||||||
*
|
*
|
||||||
* @return Mean reprojection error in pixels.
|
* @return Mean reprojection error in pixels.
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
|
||||||
double meanReprojectionError() {
|
double meanReprojectionError() {
|
||||||
return reprojectionErrors.stream()
|
return reprojectionErrors.stream()
|
||||||
.filter(pt -> cornersUsed[reprojectionErrors.indexOf(pt)])
|
.filter(pt -> cornersUsed[reprojectionErrors.indexOf(pt)])
|
||||||
|
|||||||
@@ -17,11 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.calibration;
|
package org.photonvision.vision.calibration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.opencv.core.Mat;
|
import org.opencv.core.Mat;
|
||||||
@@ -30,31 +26,23 @@ import org.opencv.core.Size;
|
|||||||
import org.photonvision.vision.opencv.ImageRotationMode;
|
import org.photonvision.vision.opencv.ImageRotationMode;
|
||||||
import org.photonvision.vision.opencv.Releasable;
|
import org.photonvision.vision.opencv.Releasable;
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@Json
|
||||||
public class CameraCalibrationCoefficients implements Releasable {
|
public class CameraCalibrationCoefficients implements Releasable {
|
||||||
@JsonProperty("resolution")
|
/** The unrotated resolution of the calibration */
|
||||||
public final Size unrotatedImageSize;
|
public final Size resolution;
|
||||||
|
|
||||||
@JsonProperty("cameraIntrinsics")
|
|
||||||
public final JsonMatOfDouble cameraIntrinsics;
|
public final JsonMatOfDouble cameraIntrinsics;
|
||||||
|
|
||||||
@JsonProperty("distCoeffs")
|
|
||||||
@JsonAlias({"distCoeffs", "distCoeffs"})
|
|
||||||
public final JsonMatOfDouble distCoeffs;
|
public final JsonMatOfDouble distCoeffs;
|
||||||
|
|
||||||
@JsonProperty("observations")
|
|
||||||
public final List<BoardObservation> observations;
|
public final List<BoardObservation> observations;
|
||||||
|
|
||||||
@JsonProperty("calobjectWarp")
|
|
||||||
public final double[] calobjectWarp;
|
public final double[] calobjectWarp;
|
||||||
|
|
||||||
@JsonProperty("calobjectSize")
|
|
||||||
public final Size calobjectSize;
|
public final Size calobjectSize;
|
||||||
|
|
||||||
@JsonProperty("calobjectSpacing")
|
|
||||||
public final double calobjectSpacing;
|
public final double calobjectSpacing;
|
||||||
|
|
||||||
@JsonProperty("lensmodel")
|
|
||||||
public final CameraLensModel lensmodel;
|
public final CameraLensModel lensmodel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,17 +63,16 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
* width/height
|
* width/height
|
||||||
* @param calobjectSpacing Spacing between adjacent squares, in meters
|
* @param calobjectSpacing Spacing between adjacent squares, in meters
|
||||||
*/
|
*/
|
||||||
@JsonCreator
|
|
||||||
public CameraCalibrationCoefficients(
|
public CameraCalibrationCoefficients(
|
||||||
@JsonProperty("resolution") Size resolution,
|
Size resolution,
|
||||||
@JsonProperty("cameraIntrinsics") JsonMatOfDouble cameraIntrinsics,
|
JsonMatOfDouble cameraIntrinsics,
|
||||||
@JsonProperty("distCoeffs") JsonMatOfDouble distCoeffs,
|
JsonMatOfDouble distCoeffs,
|
||||||
@JsonProperty("calobjectWarp") double[] calobjectWarp,
|
double[] calobjectWarp,
|
||||||
@JsonProperty("observations") List<BoardObservation> observations,
|
List<BoardObservation> observations,
|
||||||
@JsonProperty("calobjectSize") Size calobjectSize,
|
Size calobjectSize,
|
||||||
@JsonProperty("calobjectSpacing") double calobjectSpacing,
|
double calobjectSpacing,
|
||||||
@JsonProperty("lensmodel") CameraLensModel lensmodel) {
|
CameraLensModel lensmodel) {
|
||||||
this.unrotatedImageSize = resolution;
|
this.resolution = resolution;
|
||||||
this.cameraIntrinsics = cameraIntrinsics;
|
this.cameraIntrinsics = cameraIntrinsics;
|
||||||
this.distCoeffs = distCoeffs;
|
this.distCoeffs = distCoeffs;
|
||||||
this.calobjectWarp = calobjectWarp;
|
this.calobjectWarp = calobjectWarp;
|
||||||
@@ -129,7 +116,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
rotatedIntrinsics.put(1, 1, fx);
|
rotatedIntrinsics.put(1, 1, fx);
|
||||||
|
|
||||||
// CX
|
// CX
|
||||||
rotatedIntrinsics.put(0, 2, unrotatedImageSize.height - cy);
|
rotatedIntrinsics.put(0, 2, resolution.height - cy);
|
||||||
// CY
|
// CY
|
||||||
rotatedIntrinsics.put(1, 2, cx);
|
rotatedIntrinsics.put(1, 2, cx);
|
||||||
|
|
||||||
@@ -139,14 +126,14 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
rotatedDistCoeffs.put(0, 3, -p1);
|
rotatedDistCoeffs.put(0, 3, -p1);
|
||||||
|
|
||||||
// The rotated image size is the same as the unrotated image size, but the width and height
|
// The rotated image size is the same as the unrotated image size, but the width and height
|
||||||
// are flipped
|
// are swapped
|
||||||
rotatedImageSize = new Size(unrotatedImageSize.height, unrotatedImageSize.width);
|
rotatedImageSize = new Size(resolution.height, resolution.width);
|
||||||
break;
|
break;
|
||||||
case DEG_180_CCW:
|
case DEG_180_CCW:
|
||||||
// CX
|
// CX
|
||||||
rotatedIntrinsics.put(0, 2, unrotatedImageSize.width - cx);
|
rotatedIntrinsics.put(0, 2, resolution.width - cx);
|
||||||
// CY
|
// CY
|
||||||
rotatedIntrinsics.put(1, 2, unrotatedImageSize.height - cy);
|
rotatedIntrinsics.put(1, 2, resolution.height - cy);
|
||||||
|
|
||||||
// P1
|
// P1
|
||||||
rotatedDistCoeffs.put(0, 2, -p1);
|
rotatedDistCoeffs.put(0, 2, -p1);
|
||||||
@@ -154,7 +141,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
rotatedDistCoeffs.put(0, 3, -p2);
|
rotatedDistCoeffs.put(0, 3, -p2);
|
||||||
|
|
||||||
// The rotated image size is the same as the unrotated image size
|
// The rotated image size is the same as the unrotated image size
|
||||||
rotatedImageSize = unrotatedImageSize;
|
rotatedImageSize = resolution;
|
||||||
break;
|
break;
|
||||||
case DEG_90_CCW:
|
case DEG_90_CCW:
|
||||||
// FX
|
// FX
|
||||||
@@ -165,7 +152,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
// CX
|
// CX
|
||||||
rotatedIntrinsics.put(0, 2, cy);
|
rotatedIntrinsics.put(0, 2, cy);
|
||||||
// CY
|
// CY
|
||||||
rotatedIntrinsics.put(1, 2, unrotatedImageSize.width - cx);
|
rotatedIntrinsics.put(1, 2, resolution.width - cx);
|
||||||
|
|
||||||
// P1
|
// P1
|
||||||
rotatedDistCoeffs.put(0, 2, -p2);
|
rotatedDistCoeffs.put(0, 2, -p2);
|
||||||
@@ -173,8 +160,8 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
rotatedDistCoeffs.put(0, 3, p1);
|
rotatedDistCoeffs.put(0, 3, p1);
|
||||||
|
|
||||||
// The rotated image size is the same as the unrotated image size, but the width and height
|
// The rotated image size is the same as the unrotated image size, but the width and height
|
||||||
// are flipped
|
// are swapped
|
||||||
rotatedImageSize = new Size(unrotatedImageSize.height, unrotatedImageSize.width);
|
rotatedImageSize = new Size(resolution.height, resolution.width);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,27 +183,22 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
lensmodel);
|
lensmodel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Mat getCameraIntrinsicsMat() {
|
public Mat getCameraIntrinsicsMat() {
|
||||||
return cameraIntrinsics.getAsMatOfDouble();
|
return cameraIntrinsics.getAsMatOfDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public MatOfDouble getDistCoeffsMat() {
|
public MatOfDouble getDistCoeffsMat() {
|
||||||
return distCoeffs.getAsMatOfDouble();
|
return distCoeffs.getAsMatOfDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public double[] getIntrinsicsArr() {
|
public double[] getIntrinsicsArr() {
|
||||||
return cameraIntrinsics.data;
|
return cameraIntrinsics.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public double[] getDistCoeffsArr() {
|
public double[] getDistCoeffsArr() {
|
||||||
return distCoeffs.data;
|
return distCoeffs.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public List<BoardObservation> getObservations() {
|
public List<BoardObservation> getObservations() {
|
||||||
return observations;
|
return observations;
|
||||||
}
|
}
|
||||||
@@ -230,7 +212,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "CameraCalibrationCoefficients [resolution="
|
return "CameraCalibrationCoefficients [resolution="
|
||||||
+ unrotatedImageSize
|
+ resolution
|
||||||
+ ", cameraIntrinsics="
|
+ ", cameraIntrinsics="
|
||||||
+ cameraIntrinsics
|
+ cameraIntrinsics
|
||||||
+ ", distCoeffs="
|
+ ", distCoeffs="
|
||||||
@@ -244,7 +226,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
|
|
||||||
public UICameraCalibrationCoefficients cloneWithoutObservations() {
|
public UICameraCalibrationCoefficients cloneWithoutObservations() {
|
||||||
return new UICameraCalibrationCoefficients(
|
return new UICameraCalibrationCoefficients(
|
||||||
unrotatedImageSize,
|
resolution,
|
||||||
cameraIntrinsics,
|
cameraIntrinsics,
|
||||||
distCoeffs,
|
distCoeffs,
|
||||||
calobjectWarp,
|
calobjectWarp,
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.calibration;
|
package org.photonvision.vision.calibration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import org.ejml.simple.SimpleMatrix;
|
import org.ejml.simple.SimpleMatrix;
|
||||||
import org.opencv.core.CvType;
|
import org.opencv.core.CvType;
|
||||||
@@ -28,6 +27,7 @@ import org.photonvision.vision.opencv.Releasable;
|
|||||||
import org.wpilib.math.linalg.Matrix;
|
import org.wpilib.math.linalg.Matrix;
|
||||||
import org.wpilib.math.util.Num;
|
import org.wpilib.math.util.Num;
|
||||||
|
|
||||||
|
@Json
|
||||||
/** JSON-serializable image. Data is stored as a raw JSON array. */
|
/** JSON-serializable image. Data is stored as a raw JSON array. */
|
||||||
public class JsonMatOfDouble implements Releasable {
|
public class JsonMatOfDouble implements Releasable {
|
||||||
public final int rows;
|
public final int rows;
|
||||||
@@ -36,28 +36,23 @@ public class JsonMatOfDouble implements Releasable {
|
|||||||
public final double[] data;
|
public final double[] data;
|
||||||
|
|
||||||
// Cached matrices to avoid object recreation
|
// Cached matrices to avoid object recreation
|
||||||
@JsonIgnore private Mat wrappedMat = null;
|
@Json.Ignore private Mat wrappedMat = null;
|
||||||
@JsonIgnore private Matrix wpilibMat = null;
|
@Json.Ignore private Matrix wpilibMat = null;
|
||||||
|
|
||||||
@JsonIgnore private MatOfDouble wrappedMatOfDouble;
|
@Json.Ignore private MatOfDouble wrappedMatOfDouble;
|
||||||
private boolean released = false;
|
@Json.Ignore private boolean released = false;
|
||||||
|
|
||||||
public JsonMatOfDouble(int rows, int cols, double[] data) {
|
public JsonMatOfDouble(int rows, int cols, double[] data) {
|
||||||
this(rows, cols, CvType.CV_64FC1, data);
|
this(rows, cols, CvType.CV_64FC1, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonMatOfDouble(
|
public JsonMatOfDouble(int rows, int cols, int type, double[] data) {
|
||||||
@JsonProperty("rows") int rows,
|
|
||||||
@JsonProperty("cols") int cols,
|
|
||||||
@JsonProperty("type") int type,
|
|
||||||
@JsonProperty("data") double[] data) {
|
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
this.cols = cols;
|
this.cols = cols;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private static double[] getDataFromMat(Mat mat) {
|
private static double[] getDataFromMat(Mat mat) {
|
||||||
double[] data = new double[(int) mat.total()];
|
double[] data = new double[(int) mat.total()];
|
||||||
mat.get(0, 0, data);
|
mat.get(0, 0, data);
|
||||||
@@ -75,7 +70,6 @@ public class JsonMatOfDouble implements Releasable {
|
|||||||
return new JsonMatOfDouble(mat.rows(), mat.cols(), getDataFromMat(mat));
|
return new JsonMatOfDouble(mat.rows(), mat.cols(), getDataFromMat(mat));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private Mat getAsMat() {
|
private Mat getAsMat() {
|
||||||
if (this.type != CvType.CV_64FC1) return null;
|
if (this.type != CvType.CV_64FC1) return null;
|
||||||
|
|
||||||
@@ -91,7 +85,6 @@ public class JsonMatOfDouble implements Releasable {
|
|||||||
return this.wrappedMat;
|
return this.wrappedMat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public MatOfDouble getAsMatOfDouble() {
|
public MatOfDouble getAsMatOfDouble() {
|
||||||
if (this.released) {
|
if (this.released) {
|
||||||
throw new RuntimeException("This calibration object was already released");
|
throw new RuntimeException("This calibration object was already released");
|
||||||
@@ -105,7 +98,6 @@ public class JsonMatOfDouble implements Releasable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@JsonIgnore
|
|
||||||
public <R extends Num, C extends Num> Matrix<R, C> getAsWpilibMat() {
|
public <R extends Num, C extends Num> Matrix<R, C> getAsWpilibMat() {
|
||||||
if (wpilibMat == null) {
|
if (wpilibMat == null) {
|
||||||
wpilibMat = new Matrix<R, C>(new SimpleMatrix(rows, cols, true, data));
|
wpilibMat = new Matrix<R, C>(new SimpleMatrix(rows, cols, true, data));
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.calibration;
|
package org.photonvision.vision.calibration;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import org.opencv.core.Size;
|
import org.opencv.core.Size;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class UICameraCalibrationCoefficients extends CameraCalibrationCoefficients {
|
public class UICameraCalibrationCoefficients extends CameraCalibrationCoefficients {
|
||||||
public int numSnapshots;
|
public int numSnapshots;
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
package org.photonvision.vision.camera;
|
package org.photonvision.vision.camera;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.vision.frame.FrameProvider;
|
import org.photonvision.vision.frame.FrameProvider;
|
||||||
import org.photonvision.vision.frame.FrameStaticProperties;
|
import org.photonvision.vision.frame.FrameStaticProperties;
|
||||||
@@ -100,7 +101,7 @@ public class FileVisionSource extends VisionSource {
|
|||||||
public static class FileSourceSettables extends VisionSourceSettables {
|
public static class FileSourceSettables extends VisionSourceSettables {
|
||||||
private final VideoMode videoMode;
|
private final VideoMode videoMode;
|
||||||
|
|
||||||
private final HashMap<Integer, VideoMode> videoModes = new HashMap<>();
|
private final ArrayList<VideoMode> videoModes = new ArrayList<>();
|
||||||
|
|
||||||
FileSourceSettables(
|
FileSourceSettables(
|
||||||
CameraConfiguration cameraConfiguration, FrameStaticProperties frameStaticProperties) {
|
CameraConfiguration cameraConfiguration, FrameStaticProperties frameStaticProperties) {
|
||||||
@@ -112,7 +113,7 @@ public class FileVisionSource extends VisionSource {
|
|||||||
frameStaticProperties.imageWidth,
|
frameStaticProperties.imageWidth,
|
||||||
frameStaticProperties.imageHeight,
|
frameStaticProperties.imageHeight,
|
||||||
30);
|
30);
|
||||||
videoModes.put(0, videoMode);
|
videoModes.add(videoMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -137,7 +138,7 @@ public class FileVisionSource extends VisionSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashMap<Integer, VideoMode> getAllVideoModes() {
|
public List<VideoMode> getAllVideoModes() {
|
||||||
return videoModes;
|
return videoModes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,33 +17,31 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.camera;
|
package org.photonvision.vision.camera;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonGetter;
|
import io.avaje.jsonb.JsonType;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import io.avaje.jsonb.Jsonb;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import io.avaje.jsonb.Types;
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.wpilib.vision.camera.UsbCameraInfo;
|
import org.wpilib.vision.camera.UsbCameraInfo;
|
||||||
|
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
|
@Json(typeProperty = "type")
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@Json.SubType(type = PVCameraInfo.PVUsbCameraInfo.class)
|
||||||
@JsonSubTypes({
|
@Json.SubType(type = PVCameraInfo.PVCSICameraInfo.class)
|
||||||
@JsonSubTypes.Type(value = PVCameraInfo.PVUsbCameraInfo.class),
|
@Json.SubType(type = PVCameraInfo.PVFileCameraInfo.class)
|
||||||
@JsonSubTypes.Type(value = PVCameraInfo.PVCSICameraInfo.class),
|
|
||||||
@JsonSubTypes.Type(value = PVCameraInfo.PVFileCameraInfo.class)
|
|
||||||
})
|
|
||||||
public sealed interface PVCameraInfo {
|
public sealed interface PVCameraInfo {
|
||||||
/**
|
/**
|
||||||
* @return The path of the camera.
|
* @return The path of the camera.
|
||||||
*/
|
*/
|
||||||
|
@Json.Property("path")
|
||||||
String path();
|
String path();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The base name of the camera aka the name as just ascii.
|
* @return The base name of the camera aka the name as just ascii.
|
||||||
*/
|
*/
|
||||||
|
@Json.Property("name")
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,7 +62,7 @@ public sealed interface PVCameraInfo {
|
|||||||
*
|
*
|
||||||
* @return The unique path of the camera
|
* @return The unique path of the camera
|
||||||
*/
|
*/
|
||||||
@JsonGetter(value = "uniquePath")
|
@Json.Property("uniquePath")
|
||||||
String uniquePath();
|
String uniquePath();
|
||||||
|
|
||||||
String[] otherPaths();
|
String[] otherPaths();
|
||||||
@@ -82,16 +80,9 @@ public sealed interface PVCameraInfo {
|
|||||||
return this.equals((Object) other);
|
return this.equals((Object) other);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonTypeName("PVUsbCameraInfo")
|
|
||||||
public static final class PVUsbCameraInfo extends UsbCameraInfo implements PVCameraInfo {
|
public static final class PVUsbCameraInfo extends UsbCameraInfo implements PVCameraInfo {
|
||||||
@JsonCreator
|
|
||||||
public PVUsbCameraInfo(
|
public PVUsbCameraInfo(
|
||||||
@JsonProperty("dev") int dev,
|
int dev, String path, String name, String[] otherPaths, int vendorId, int productId) {
|
||||||
@JsonProperty("path") String path,
|
|
||||||
@JsonProperty("name") String name,
|
|
||||||
@JsonProperty("otherPaths") String[] otherPaths,
|
|
||||||
@JsonProperty("vendorId") int vendorId,
|
|
||||||
@JsonProperty("productId") int productId) {
|
|
||||||
super(dev, path, name, otherPaths, vendorId, productId);
|
super(dev, path, name, otherPaths, vendorId, productId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,14 +159,11 @@ public sealed interface PVCameraInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonTypeName("PVCSICameraInfo")
|
|
||||||
public static final class PVCSICameraInfo implements PVCameraInfo {
|
public static final class PVCSICameraInfo implements PVCameraInfo {
|
||||||
public final String path;
|
public final String path;
|
||||||
public final String baseName;
|
public final String baseName;
|
||||||
|
|
||||||
@JsonCreator
|
public PVCSICameraInfo(String path, String baseName) {
|
||||||
public PVCSICameraInfo(
|
|
||||||
@JsonProperty("path") String path, @JsonProperty("baseName") String baseName) {
|
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.baseName = baseName;
|
this.baseName = baseName;
|
||||||
}
|
}
|
||||||
@@ -233,13 +221,11 @@ public sealed interface PVCameraInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonTypeName("PVFileCameraInfo")
|
|
||||||
public static final class PVFileCameraInfo implements PVCameraInfo {
|
public static final class PVFileCameraInfo implements PVCameraInfo {
|
||||||
public final String path;
|
public final String path;
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
@JsonCreator
|
public PVFileCameraInfo(String path, String name) {
|
||||||
public PVFileCameraInfo(@JsonProperty("path") String path, @JsonProperty("name") String name) {
|
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
@@ -300,4 +286,25 @@ public sealed interface PVCameraInfo {
|
|||||||
public static PVCameraInfo fromFileInfo(String path, String baseName) {
|
public static PVCameraInfo fromFileInfo(String path, String baseName) {
|
||||||
return new PVFileCameraInfo(path, baseName);
|
return new PVFileCameraInfo(path, baseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MIGRATION: 2026
|
||||||
|
public static String remapConfigJson(String configJson, String cameraType) {
|
||||||
|
final JsonType<Map<String, Object>> objMapJsonb =
|
||||||
|
Jsonb.instance().type(Types.mapOf(Object.class));
|
||||||
|
|
||||||
|
Map<String, Object> cameraMigration = objMapJsonb.fromJson(configJson);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
var cameraMigrationIn = (Map<String, Object>) cameraMigration.get("matchedCameraInfo");
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
var cameraData = (Map<String, Object>) cameraMigrationIn.get(cameraType);
|
||||||
|
|
||||||
|
Map<String, Object> cameraMigrationOut = new HashMap<>();
|
||||||
|
cameraMigrationOut.putAll(cameraData);
|
||||||
|
cameraMigrationOut.put("type", "PVCameraInfo." + cameraType);
|
||||||
|
|
||||||
|
cameraMigration.put("matchedCameraInfo", cameraMigrationOut);
|
||||||
|
return objMapJsonb.toJson(cameraMigration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.camera;
|
package org.photonvision.vision.camera;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class QuirkyCamera {
|
public class QuirkyCamera {
|
||||||
private static final List<QuirkyCamera> quirkyCameras =
|
private static final List<QuirkyCamera> quirkyCameras =
|
||||||
List.of(
|
List.of(
|
||||||
@@ -98,19 +98,14 @@ public class QuirkyCamera {
|
|||||||
CameraQuirk.Gain,
|
CameraQuirk.Gain,
|
||||||
CameraQuirk.AwbRedBlueGain); // PiCam (using libcamera GPU Driver on raspberry pi)
|
CameraQuirk.AwbRedBlueGain); // PiCam (using libcamera GPU Driver on raspberry pi)
|
||||||
|
|
||||||
@JsonProperty("baseName")
|
|
||||||
public final String baseName;
|
public final String baseName;
|
||||||
|
|
||||||
@JsonProperty("usbVid")
|
|
||||||
public final int usbVid;
|
public final int usbVid;
|
||||||
|
|
||||||
@JsonProperty("usbPid")
|
|
||||||
public final int usbPid;
|
public final int usbPid;
|
||||||
|
|
||||||
@JsonProperty("displayName")
|
|
||||||
public final String displayName;
|
public final String displayName;
|
||||||
|
|
||||||
@JsonProperty("quirks")
|
|
||||||
public final Map<CameraQuirk, Boolean> quirks;
|
public final Map<CameraQuirk, Boolean> quirks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,13 +160,12 @@ public class QuirkyCamera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonCreator
|
|
||||||
public QuirkyCamera(
|
public QuirkyCamera(
|
||||||
@JsonProperty("baseName") String baseName,
|
String baseName,
|
||||||
@JsonProperty("usbVid") int usbVid,
|
int usbVid,
|
||||||
@JsonProperty("usbPid") int usbPid,
|
int usbPid,
|
||||||
@JsonProperty("displayName") String displayName,
|
String displayName,
|
||||||
@JsonProperty("quirks") Map<CameraQuirk, Boolean> quirks) {
|
Map<CameraQuirk, Boolean> quirks) {
|
||||||
this.baseName = baseName;
|
this.baseName = baseName;
|
||||||
this.usbPid = usbPid;
|
this.usbPid = usbPid;
|
||||||
this.usbVid = usbVid;
|
this.usbVid = usbVid;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ package org.photonvision.vision.camera.USBCameras;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -286,7 +285,6 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void cacheVideoModes() {
|
private void cacheVideoModes() {
|
||||||
videoModes = new HashMap<>();
|
|
||||||
List<VideoMode> videoModesList = new ArrayList<>();
|
List<VideoMode> videoModesList = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
for (VideoMode videoMode : camera.enumerateVideoModes()) {
|
for (VideoMode videoMode : camera.enumerateVideoModes()) {
|
||||||
@@ -316,10 +314,7 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
|
|||||||
// The ordering is usually more logical when done like this. It typically puts higher FPSes
|
// The ordering is usually more logical when done like this. It typically puts higher FPSes
|
||||||
// closer to the bottom.
|
// closer to the bottom.
|
||||||
Collections.reverse(sortedList);
|
Collections.reverse(sortedList);
|
||||||
|
videoModes = sortedList;
|
||||||
for (int i = 0; i < sortedList.size(); i++) {
|
|
||||||
videoModes.put(i, sortedList.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If after all that we still have no video modes, not much we can do besides
|
// If after all that we still have no video modes, not much we can do besides
|
||||||
// throw up our hands
|
// throw up our hands
|
||||||
@@ -329,11 +324,11 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashMap<Integer, VideoMode> getAllVideoModes() {
|
public List<VideoMode> getAllVideoModes() {
|
||||||
if (!cameraPropertiesCached) {
|
if (!cameraPropertiesCached) {
|
||||||
// Device hasn't connected at least once, best I can do is given up
|
// Device hasn't connected at least once, best I can do is given up
|
||||||
logger.warn("Device hasn't connected, cannot enumerate video modes");
|
logger.warn("Device hasn't connected, cannot enumerate video modes");
|
||||||
return new HashMap<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoModes;
|
return videoModes;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.camera.csi;
|
package org.photonvision.vision.camera.csi;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.util.math.MathUtils;
|
import org.photonvision.common.util.math.MathUtils;
|
||||||
import org.photonvision.raspi.LibCameraJNI;
|
import org.photonvision.raspi.LibCameraJNI;
|
||||||
@@ -61,7 +62,7 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
|
|||||||
public LibcameraGpuSettables(CameraConfiguration configuration) {
|
public LibcameraGpuSettables(CameraConfiguration configuration) {
|
||||||
super(configuration);
|
super(configuration);
|
||||||
|
|
||||||
videoModes = new HashMap<>();
|
videoModes = new ArrayList<>();
|
||||||
|
|
||||||
LibCameraJNI.SensorModel tempSensorModel;
|
LibCameraJNI.SensorModel tempSensorModel;
|
||||||
try {
|
try {
|
||||||
@@ -74,18 +75,18 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
|
|||||||
|
|
||||||
if (sensorModel == LibCameraJNI.SensorModel.IMX219) {
|
if (sensorModel == LibCameraJNI.SensorModel.IMX219) {
|
||||||
// Settings for the IMX219 sensor, which is used on the Pi Camera Module v2
|
// Settings for the IMX219 sensor, which is used on the Pi Camera Module v2
|
||||||
videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 120, 120, .39));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 120, 120, .39));
|
||||||
videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 30, 30, .39));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 30, 30, .39));
|
||||||
videoModes.put(2, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 65, 90, .39));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 65, 90, .39));
|
||||||
videoModes.put(3, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 30, 30, .39));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 30, 30, .39));
|
||||||
// TODO: fix 1280x720 in the native code and re-add it
|
// TODO: fix 1280x720 in the native code and re-add it
|
||||||
videoModes.put(4, new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, .53));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, .53));
|
||||||
videoModes.put(5, new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 2, 2464 / 2, 15, 20, 1));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 2, 2464 / 2, 15, 20, 1));
|
||||||
videoModes.put(6, new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 4, 2464 / 4, 15, 20, 1));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 4, 2464 / 4, 15, 20, 1));
|
||||||
} else if (sensorModel == LibCameraJNI.SensorModel.OV9281) {
|
} else if (sensorModel == LibCameraJNI.SensorModel.OV9281) {
|
||||||
// Taken from https://www.ovt.com/wp-content/uploads/2022/01/OV9281-OV9282-PB-v1.3-WEB.pdf
|
// Taken from https://www.ovt.com/wp-content/uploads/2022/01/OV9281-OV9282-PB-v1.3-WEB.pdf
|
||||||
videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 400, 120, 240, 1));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 400, 120, 240, 1));
|
||||||
videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 120, 120, 1));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 120, 120, 1));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (sensorModel == LibCameraJNI.SensorModel.IMX477) {
|
if (sensorModel == LibCameraJNI.SensorModel.IMX477) {
|
||||||
@@ -100,13 +101,13 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Settings for the OV5647 sensor, which is used by the Pi Camera Module v1
|
// Settings for the OV5647 sensor, which is used by the Pi Camera Module v1
|
||||||
videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 90, 90, 1));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 90, 90, 1));
|
||||||
videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 85, 90, 1));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 85, 90, 1));
|
||||||
videoModes.put(2, new FPSRatedVideoMode(PixelFormat.kUnknown, 960, 720, 45, 49, 0.74));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 960, 720, 45, 49, 0.74));
|
||||||
// Half the size of the active areas on the OV5647
|
// Half the size of the active areas on the OV5647
|
||||||
videoModes.put(3, new FPSRatedVideoMode(PixelFormat.kUnknown, 2592 / 2, 1944 / 2, 20, 20, 1));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 2592 / 2, 1944 / 2, 20, 20, 1));
|
||||||
videoModes.put(4, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 720, 30, 45, 0.91));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 720, 30, 45, 0.91));
|
||||||
videoModes.put(5, new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, 0.72));
|
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, 0.72));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO need to add more video modes for new sensors here
|
// TODO need to add more video modes for new sensors here
|
||||||
@@ -253,7 +254,7 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashMap<Integer, VideoMode> getAllVideoModes() {
|
public List<VideoMode> getAllVideoModes() {
|
||||||
return videoModes;
|
return videoModes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.opencv.core.Point;
|
import org.opencv.core.Point;
|
||||||
import org.photonvision.common.util.numbers.DoubleCouple;
|
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||||
@@ -91,19 +91,16 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
|
|||||||
public int cornerDetectionSideCount = 4;
|
public int cornerDetectionSideCount = 4;
|
||||||
public double cornerDetectionAccuracyPercentage = 10;
|
public double cornerDetectionAccuracyPercentage = 10;
|
||||||
|
|
||||||
|
// MIGRATION: 2025
|
||||||
/**
|
/**
|
||||||
* Handles backward compatibility for the deprecated outputShowMultipleTargets property. When
|
* Handles backward compatibility for the deprecated outputShowMultipleTargets property. When
|
||||||
* outputShowMultipleTargets is encountered during deserialization, it sets outputMaximumTargets
|
* outputShowMultipleTargets is encountered during deserialization, it sets outputMaximumTargets
|
||||||
* appropriately. If outputShowMultipleTargets is false, outputMaximumTargets is set to 1.
|
* appropriately. If outputShowMultipleTargets is false, outputMaximumTargets is set to 1.
|
||||||
*/
|
*/
|
||||||
@JsonAnySetter
|
@Json.Property("outputShowMultipleTargets")
|
||||||
public void handleUnknownProperty(String name, Object value) {
|
public void importShowMultipleTargets(boolean showMultipleTargets) {
|
||||||
// Handle the old showMultipleTargets property for backward compatibility
|
if (!showMultipleTargets) {
|
||||||
if ("outputShowMultipleTargets".equals(name) && value instanceof Boolean showMultipleTargets) {
|
outputMaximumTargets = 1;
|
||||||
if (!showMultipleTargets) {
|
|
||||||
// If showMultipleTargets is false, limit to 1 target
|
|
||||||
outputMaximumTargets = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,9 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
|
||||||
import org.photonvision.vision.apriltag.AprilTagFamily;
|
import org.photonvision.vision.apriltag.AprilTagFamily;
|
||||||
import org.photonvision.vision.target.TargetModel;
|
import org.photonvision.vision.target.TargetModel;
|
||||||
|
|
||||||
@JsonTypeName("AprilTagPipelineSettings")
|
|
||||||
public class AprilTagPipelineSettings extends AdvancedPipelineSettings {
|
public class AprilTagPipelineSettings extends AdvancedPipelineSettings {
|
||||||
public AprilTagFamily tagFamily = AprilTagFamily.kTag36h11;
|
public AprilTagFamily tagFamily = AprilTagFamily.kTag36h11;
|
||||||
public int decimate = 1;
|
public int decimate = 1;
|
||||||
|
|||||||
@@ -17,12 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
|
||||||
import org.photonvision.common.util.numbers.IntegerCouple;
|
import org.photonvision.common.util.numbers.IntegerCouple;
|
||||||
import org.photonvision.vision.apriltag.AprilTagFamily;
|
import org.photonvision.vision.apriltag.AprilTagFamily;
|
||||||
import org.photonvision.vision.target.TargetModel;
|
import org.photonvision.vision.target.TargetModel;
|
||||||
|
|
||||||
@JsonTypeName("ArucoPipelineSettings")
|
|
||||||
public class ArucoPipelineSettings extends AdvancedPipelineSettings {
|
public class ArucoPipelineSettings extends AdvancedPipelineSettings {
|
||||||
public AprilTagFamily tagFamily = AprilTagFamily.kTag36h11;
|
public AprilTagFamily tagFamily = AprilTagFamily.kTag36h11;
|
||||||
|
|
||||||
|
|||||||
@@ -17,23 +17,25 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import io.avaje.jsonb.JsonType;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import io.avaje.jsonb.Types;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.photonvision.vision.frame.FrameDivisor;
|
import org.photonvision.vision.frame.FrameDivisor;
|
||||||
import org.photonvision.vision.opencv.ImageRotationMode;
|
import org.photonvision.vision.opencv.ImageRotationMode;
|
||||||
|
|
||||||
@JsonTypeInfo(
|
@Json(typeProperty = "type")
|
||||||
use = JsonTypeInfo.Id.NAME,
|
@Json.SubTypes({
|
||||||
include = JsonTypeInfo.As.WRAPPER_ARRAY,
|
@Json.SubType(type = ColoredShapePipelineSettings.class),
|
||||||
property = "type")
|
@Json.SubType(type = ReflectivePipelineSettings.class),
|
||||||
@JsonSubTypes({
|
@Json.SubType(type = DriverModePipelineSettings.class),
|
||||||
@JsonSubTypes.Type(value = ColoredShapePipelineSettings.class),
|
@Json.SubType(type = AprilTagPipelineSettings.class),
|
||||||
@JsonSubTypes.Type(value = ReflectivePipelineSettings.class),
|
@Json.SubType(type = ArucoPipelineSettings.class),
|
||||||
@JsonSubTypes.Type(value = DriverModePipelineSettings.class),
|
@Json.SubType(type = ObjectDetectionPipelineSettings.class)
|
||||||
@JsonSubTypes.Type(value = AprilTagPipelineSettings.class),
|
|
||||||
@JsonSubTypes.Type(value = ArucoPipelineSettings.class),
|
|
||||||
@JsonSubTypes.Type(value = ObjectDetectionPipelineSettings.class)
|
|
||||||
})
|
})
|
||||||
public class CVPipelineSettings implements Cloneable {
|
public class CVPipelineSettings implements Cloneable {
|
||||||
public int pipelineIndex = 0;
|
public int pipelineIndex = 0;
|
||||||
@@ -153,4 +155,22 @@ public class CVPipelineSettings implements Cloneable {
|
|||||||
+ outputShouldShow
|
+ outputShouldShow
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MIGRATION: 2026
|
||||||
|
public static String remapSettingsJson(String pipelineJson) {
|
||||||
|
final JsonType<List<Object>> objListJsonb = Jsonb.instance().type(Types.listOf(Object.class));
|
||||||
|
final JsonType<Map<String, Object>> objMapJsonb =
|
||||||
|
Jsonb.instance().type(Types.mapOf(Object.class));
|
||||||
|
|
||||||
|
List<Object> pipelineMigrationIn = objListJsonb.fromJson(pipelineJson);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
var pipelineData = (Map<String, Object>) pipelineMigrationIn.get(1);
|
||||||
|
|
||||||
|
Map<String, Object> pipelineMigrationOut = new HashMap<>();
|
||||||
|
pipelineMigrationOut.putAll(pipelineData);
|
||||||
|
pipelineMigrationOut.put("type", pipelineMigrationIn.get(0));
|
||||||
|
|
||||||
|
return objMapJsonb.toJson(pipelineMigrationOut);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,12 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.photonvision.common.util.numbers.DoubleCouple;
|
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||||
import org.photonvision.common.util.numbers.IntegerCouple;
|
import org.photonvision.common.util.numbers.IntegerCouple;
|
||||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||||
import org.photonvision.vision.opencv.ContourShape;
|
import org.photonvision.vision.opencv.ContourShape;
|
||||||
|
|
||||||
@JsonTypeName("ColoredShapePipelineSettings")
|
|
||||||
public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
|
public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
|
||||||
public ContourShape contourShape = ContourShape.Triangle;
|
public ContourShape contourShape = ContourShape.Triangle;
|
||||||
public DoubleCouple contourPerimeter = new DoubleCouple(0, Double.MAX_VALUE);
|
public DoubleCouple contourPerimeter = new DoubleCouple(0, Double.MAX_VALUE);
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
import io.avaje.jsonb.Json;
|
||||||
import org.photonvision.common.util.numbers.DoubleCouple;
|
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||||
import org.photonvision.vision.processes.PipelineManager;
|
import org.photonvision.vision.processes.PipelineManager;
|
||||||
|
|
||||||
@JsonTypeName("DriverModePipelineSettings")
|
@Json
|
||||||
public class DriverModePipelineSettings extends CVPipelineSettings {
|
public class DriverModePipelineSettings extends CVPipelineSettings {
|
||||||
public DoubleCouple offsetPoint = new DoubleCouple();
|
public DoubleCouple offsetPoint = new DoubleCouple();
|
||||||
public boolean crosshair = true;
|
public boolean crosshair = true;
|
||||||
|
|||||||
@@ -17,10 +17,8 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
|
||||||
import org.photonvision.vision.processes.PipelineManager;
|
import org.photonvision.vision.processes.PipelineManager;
|
||||||
|
|
||||||
@JsonTypeName("FocusPipelineSettings")
|
|
||||||
public class FocusPipelineSettings extends CVPipelineSettings {
|
public class FocusPipelineSettings extends CVPipelineSettings {
|
||||||
public FocusPipelineSettings() {
|
public FocusPipelineSettings() {
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -17,9 +17,6 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
|
||||||
|
|
||||||
@JsonTypeName("ReflectivePipelineSettings")
|
|
||||||
public class ReflectivePipelineSettings extends AdvancedPipelineSettings {
|
public class ReflectivePipelineSettings extends AdvancedPipelineSettings {
|
||||||
public double contourFilterRangeX = 2;
|
public double contourFilterRangeX = 2;
|
||||||
public double contourFilterRangeY = 2;
|
public double contourFilterRangeY = 2;
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import org.opencv.objdetect.Objdetect;
|
import org.opencv.objdetect.Objdetect;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class UICalibrationData {
|
public class UICalibrationData {
|
||||||
public int videoModeIndex;
|
public int videoModeIndex;
|
||||||
public int count;
|
public int count;
|
||||||
@@ -53,11 +55,18 @@ public class UICalibrationData {
|
|||||||
this.tagFamily = tagFamily;
|
this.tagFamily = tagFamily;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Json
|
||||||
public enum BoardType {
|
public enum BoardType {
|
||||||
CHESSBOARD,
|
CHESSBOARD,
|
||||||
CHARUCOBOARD,
|
CHARUCOBOARD;
|
||||||
|
|
||||||
|
@Json.Value
|
||||||
|
int toValue() {
|
||||||
|
return ordinal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Json
|
||||||
public enum TagFamily {
|
public enum TagFamily {
|
||||||
Dict_4X4_1000(Objdetect.DICT_4X4_1000),
|
Dict_4X4_1000(Objdetect.DICT_4X4_1000),
|
||||||
Dict_5X5_1000(Objdetect.DICT_5X5_1000),
|
Dict_5X5_1000(Objdetect.DICT_5X5_1000),
|
||||||
@@ -75,6 +84,11 @@ public class UICalibrationData {
|
|||||||
private TagFamily(int value) {
|
private TagFamily(int value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Json.Value
|
||||||
|
int toValue() {
|
||||||
|
return ordinal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import org.opencv.core.Size;
|
import org.opencv.core.Size;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
@@ -591,23 +592,23 @@ public class VisionModule {
|
|||||||
ret.fpsLimit = this.fpsLimit;
|
ret.fpsLimit = this.fpsLimit;
|
||||||
|
|
||||||
// TODO refactor into helper method
|
// TODO refactor into helper method
|
||||||
var temp = new HashMap<Integer, HashMap<String, Object>>();
|
var temp = new ArrayList<Map<String, Object>>();
|
||||||
var videoModes = visionSource.getSettables().getAllVideoModes();
|
var videoModes = visionSource.getSettables().getAllVideoModes();
|
||||||
|
|
||||||
for (var k : videoModes.entrySet()) {
|
for (var videoMode : videoModes) {
|
||||||
var internalMap = new HashMap<String, Object>();
|
var internalMap = new HashMap<String, Object>();
|
||||||
|
|
||||||
internalMap.put("width", k.getValue().width);
|
internalMap.put("width", videoMode.width);
|
||||||
internalMap.put("height", k.getValue().height);
|
internalMap.put("height", videoMode.height);
|
||||||
internalMap.put("fps", k.getValue().fps);
|
internalMap.put("fps", videoMode.fps);
|
||||||
internalMap.put(
|
internalMap.put(
|
||||||
"pixelFormat",
|
"pixelFormat",
|
||||||
((k.getValue() instanceof LibcameraGpuSource.FPSRatedVideoMode)
|
((videoMode instanceof LibcameraGpuSource.FPSRatedVideoMode)
|
||||||
? "kPicam"
|
? "kPicam"
|
||||||
: k.getValue().pixelFormat.toString())
|
: videoMode.pixelFormat.toString())
|
||||||
.substring(1)); // Remove the k prefix
|
.substring(1)); // Remove the k prefix
|
||||||
|
|
||||||
temp.put(k.getKey(), internalMap);
|
temp.add(internalMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoModes.size() == 0) {
|
if (videoModes.size() == 0) {
|
||||||
@@ -728,7 +729,7 @@ public class VisionModule {
|
|||||||
|
|
||||||
public void addCalibrationToConfig(CameraCalibrationCoefficients newCalibration) {
|
public void addCalibrationToConfig(CameraCalibrationCoefficients newCalibration) {
|
||||||
if (newCalibration != null) {
|
if (newCalibration != null) {
|
||||||
logger.info("Got new calibration for " + newCalibration.unrotatedImageSize);
|
logger.info("Got new calibration for " + newCalibration.resolution);
|
||||||
visionSource.getSettables().addCalibration(newCalibration);
|
visionSource.getSettables().addCalibration(newCalibration);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Got null calibration?");
|
logger.error("Got null calibration?");
|
||||||
@@ -737,9 +738,9 @@ public class VisionModule {
|
|||||||
saveAndBroadcastAll();
|
saveAndBroadcastAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeCalibrationFromConfig(Size unrotatedImageSize) {
|
public void removeCalibrationFromConfig(Size resolution) {
|
||||||
if (unrotatedImageSize != null) {
|
if (resolution != null) {
|
||||||
visionSource.getSettables().removeCalibration(unrotatedImageSize);
|
visionSource.getSettables().removeCalibration(resolution);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Got null size?");
|
logger.error("Got null size?");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.processes;
|
package org.photonvision.vision.processes;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -30,7 +30,6 @@ import org.photonvision.common.dataflow.events.DataChangeEvent;
|
|||||||
import org.photonvision.common.dataflow.events.IncomingWebSocketEvent;
|
import org.photonvision.common.dataflow.events.IncomingWebSocketEvent;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.photonvision.common.util.numbers.DoubleCouple;
|
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||||
import org.photonvision.common.util.numbers.IntegerCouple;
|
import org.photonvision.common.util.numbers.IntegerCouple;
|
||||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||||
@@ -57,10 +56,10 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataChangeEvent(DataChangeEvent<?> event) {
|
public <T> void onDataChangeEvent(DataChangeEvent<T> event) {
|
||||||
// Camera index -1 means a "multicast event" (i.e. the event is received by all
|
// Camera index -1 means a "multicast event" (i.e. the event is received by all
|
||||||
// cameras)
|
// cameras)
|
||||||
if (event instanceof IncomingWebSocketEvent wsEvent
|
if (event instanceof IncomingWebSocketEvent<T> wsEvent
|
||||||
&& wsEvent.cameraUniqueName != null
|
&& wsEvent.cameraUniqueName != null
|
||||||
&& wsEvent.cameraUniqueName.equals(parentModule.uniqueName())) {
|
&& wsEvent.cameraUniqueName.equals(parentModule.uniqueName())) {
|
||||||
logger.trace("Got PSC event - propName: " + wsEvent.propertyName);
|
logger.trace("Got PSC event - propName: " + wsEvent.propertyName);
|
||||||
@@ -68,7 +67,7 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
|||||||
try {
|
try {
|
||||||
getSettingChanges()
|
getSettingChanges()
|
||||||
.add(
|
.add(
|
||||||
new VisionModuleChange(
|
new VisionModuleChange<T>(
|
||||||
wsEvent.propertyName,
|
wsEvent.propertyName,
|
||||||
wsEvent.data,
|
wsEvent.data,
|
||||||
parentModule.pipelineManager.getCurrentPipeline().getSettings(),
|
parentModule.pipelineManager.getCurrentPipeline().getSettings(),
|
||||||
@@ -92,6 +91,10 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
|||||||
var newPropValue = change.getNewPropValue();
|
var newPropValue = change.getNewPropValue();
|
||||||
var currentSettings = change.getCurrentSettings();
|
var currentSettings = change.getCurrentSettings();
|
||||||
var originContext = change.getOriginContext();
|
var originContext = change.getOriginContext();
|
||||||
|
if (newPropValue instanceof Long) {
|
||||||
|
newPropValue = ((Long) newPropValue).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
switch (propName) {
|
switch (propName) {
|
||||||
case "pipelineName" -> newPipelineNickname((String) newPropValue);
|
case "pipelineName" -> newPipelineNickname((String) newPropValue);
|
||||||
case "newPipelineInfo" -> newPipelineInfo((Pair<String, PipelineType>) newPropValue);
|
case "newPipelineInfo" -> newPipelineInfo((Pair<String, PipelineType>) newPropValue);
|
||||||
@@ -199,7 +202,7 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
|||||||
|
|
||||||
public void startCalibration(Map<String, Object> data) {
|
public void startCalibration(Map<String, Object> data) {
|
||||||
try {
|
try {
|
||||||
var deserialized = JacksonUtils.deserialize(data, UICalibrationData.class);
|
var deserialized = Jsonb.instance().type(UICalibrationData.class).fromObject(data);
|
||||||
parentModule.startCalibration(deserialized);
|
parentModule.startCalibration(deserialized);
|
||||||
parentModule.saveAndBroadcastAll();
|
parentModule.saveAndBroadcastAll();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -295,8 +298,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
|||||||
}
|
}
|
||||||
} else if (propField.getType() == ModelProperties.class
|
} else if (propField.getType() == ModelProperties.class
|
||||||
&& newPropValue instanceof LinkedHashMap) {
|
&& newPropValue instanceof LinkedHashMap) {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ModelProperties modelProps =
|
||||||
ModelProperties modelProps = mapper.convertValue(newPropValue, ModelProperties.class);
|
Jsonb.instance().type(ModelProperties.class).fromObject(newPropValue);
|
||||||
propField.set(currentSettings, modelProps);
|
propField.set(currentSettings, modelProps);
|
||||||
} else {
|
} else {
|
||||||
propField.set(currentSettings, newPropValue);
|
propField.set(currentSettings, newPropValue);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.processes;
|
package org.photonvision.vision.processes;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -72,7 +73,7 @@ public class VisionSourceManager {
|
|||||||
return SingletonHolder.INSTANCE;
|
return SingletonHolder.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jackson does use these members even if your IDE claims otherwise
|
@Json
|
||||||
public static class VisionSourceManagerState {
|
public static class VisionSourceManagerState {
|
||||||
public List<UICameraConfiguration> disabledConfigs;
|
public List<UICameraConfiguration> disabledConfigs;
|
||||||
public List<PVCameraInfo> allConnectedCameras;
|
public List<PVCameraInfo> allConnectedCameras;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.processes;
|
package org.photonvision.vision.processes;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import org.opencv.core.Size;
|
import org.opencv.core.Size;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
@@ -38,7 +39,7 @@ public abstract class VisionSourceSettables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected FrameStaticProperties frameStaticProperties = null;
|
protected FrameStaticProperties frameStaticProperties = null;
|
||||||
protected HashMap<Integer, VideoMode> videoModes = new HashMap<>();
|
protected List<VideoMode> videoModes = new ArrayList<>();
|
||||||
|
|
||||||
public CameraConfiguration getConfiguration() {
|
public CameraConfiguration getConfiguration() {
|
||||||
return configuration;
|
return configuration;
|
||||||
@@ -99,12 +100,11 @@ public abstract class VisionSourceSettables {
|
|||||||
|
|
||||||
protected abstract void setVideoModeInternal(VideoMode videoMode);
|
protected abstract void setVideoModeInternal(VideoMode videoMode);
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void setVideoModeIndex(int index) {
|
public void setVideoModeIndex(int index) {
|
||||||
setVideoMode(videoModes.get(index));
|
setVideoMode(videoModes.get(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract HashMap<Integer, VideoMode> getAllVideoModes();
|
public abstract List<VideoMode> getAllVideoModes();
|
||||||
|
|
||||||
public double getFOV() {
|
public double getFOV() {
|
||||||
return configuration.FOV;
|
return configuration.FOV;
|
||||||
@@ -121,8 +121,8 @@ public abstract class VisionSourceSettables {
|
|||||||
calculateFrameStaticProps();
|
calculateFrameStaticProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeCalibration(Size unrotatedImageSize) {
|
public void removeCalibration(Size resolution) {
|
||||||
configuration.removeCalibration(unrotatedImageSize);
|
configuration.removeCalibration(resolution);
|
||||||
calculateFrameStaticProps();
|
calculateFrameStaticProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,8 +135,8 @@ public abstract class VisionSourceSettables {
|
|||||||
configuration.calibrations.stream()
|
configuration.calibrations.stream()
|
||||||
.filter(
|
.filter(
|
||||||
it ->
|
it ->
|
||||||
it.unrotatedImageSize.width == videoMode.width
|
it.resolution.width == videoMode.width
|
||||||
&& it.unrotatedImageSize.height == videoMode.height)
|
&& it.resolution.height == videoMode.height)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null));
|
.orElse(null));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.target;
|
package org.photonvision.vision.target;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.opencv.core.MatOfPoint3f;
|
import org.opencv.core.MatOfPoint3f;
|
||||||
@@ -49,6 +46,7 @@ import org.wpilib.math.util.Units;
|
|||||||
*
|
*
|
||||||
* <p>AprilTag models are currently only used for drawing on the output stream.
|
* <p>AprilTag models are currently only used for drawing on the output stream.
|
||||||
*/
|
*/
|
||||||
|
@Json
|
||||||
public enum TargetModel implements Releasable {
|
public enum TargetModel implements Releasable {
|
||||||
k2016HighGoal(
|
k2016HighGoal(
|
||||||
List.of(
|
List.of(
|
||||||
@@ -129,7 +127,8 @@ public enum TargetModel implements Releasable {
|
|||||||
-Units.inchesToMeters(16.25) / 2)),
|
-Units.inchesToMeters(16.25) / 2)),
|
||||||
0),
|
0),
|
||||||
// 2023 AprilTag, with 6 inch marker width (inner black square).
|
// 2023 AprilTag, with 6 inch marker width (inner black square).
|
||||||
@JsonAlias({"k6in_16h5"})
|
// MIGRATION: 2023
|
||||||
|
@Json.Alias({"k6in_16h5"})
|
||||||
kAprilTag6in_16h5(
|
kAprilTag6in_16h5(
|
||||||
// Corners of the tag's inner black square (excluding white border)
|
// Corners of the tag's inner black square (excluding white border)
|
||||||
List.of(
|
List.of(
|
||||||
@@ -139,7 +138,8 @@ public enum TargetModel implements Releasable {
|
|||||||
new Point3(Units.inchesToMeters(3), -Units.inchesToMeters(3), 0)),
|
new Point3(Units.inchesToMeters(3), -Units.inchesToMeters(3), 0)),
|
||||||
Units.inchesToMeters(3 * 2)),
|
Units.inchesToMeters(3 * 2)),
|
||||||
// 2024 AprilTag, with 6.5 inch marker width (inner black square).
|
// 2024 AprilTag, with 6.5 inch marker width (inner black square).
|
||||||
@JsonAlias({"k6p5in_36h11", "k200mmAprilTag", "kAruco6p5in_36h11"})
|
// MIGRATION: 2023
|
||||||
|
@Json.Alias({"k6p5in_36h11", "k200mmAprilTag", "kAruco6p5in_36h11"})
|
||||||
kAprilTag6p5in_36h11(
|
kAprilTag6p5in_36h11(
|
||||||
// Corners of the tag's inner black square (excluding white border)
|
// Corners of the tag's inner black square (excluding white border)
|
||||||
List.of(
|
List.of(
|
||||||
@@ -149,18 +149,13 @@ public enum TargetModel implements Releasable {
|
|||||||
new Point3(-Units.inchesToMeters(6.5 / 2.0), -Units.inchesToMeters(6.5 / 2.0), 0)),
|
new Point3(-Units.inchesToMeters(6.5 / 2.0), -Units.inchesToMeters(6.5 / 2.0), 0)),
|
||||||
Units.inchesToMeters(6.5));
|
Units.inchesToMeters(6.5));
|
||||||
|
|
||||||
@JsonIgnore private MatOfPoint3f realWorldTargetCoordinates;
|
@Json.Ignore private final MatOfPoint3f realWorldTargetCoordinates;
|
||||||
@JsonIgnore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
|
@Json.Ignore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
|
||||||
@JsonIgnore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
|
@Json.Ignore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
|
||||||
|
|
||||||
@JsonProperty("realWorldCoordinatesArray")
|
|
||||||
private List<Point3> realWorldCoordinatesArray;
|
private List<Point3> realWorldCoordinatesArray;
|
||||||
|
|
||||||
@JsonProperty("boxHeight")
|
|
||||||
private double boxHeight;
|
private double boxHeight;
|
||||||
|
|
||||||
TargetModel() {}
|
|
||||||
|
|
||||||
TargetModel(MatOfPoint3f realWorldTargetCoordinates, double boxHeight) {
|
TargetModel(MatOfPoint3f realWorldTargetCoordinates, double boxHeight) {
|
||||||
this.realWorldTargetCoordinates = realWorldTargetCoordinates;
|
this.realWorldTargetCoordinates = realWorldTargetCoordinates;
|
||||||
this.realWorldCoordinatesArray = realWorldTargetCoordinates.toList();
|
this.realWorldCoordinatesArray = realWorldTargetCoordinates.toList();
|
||||||
@@ -176,11 +171,8 @@ public enum TargetModel implements Releasable {
|
|||||||
this.visualizationBoxTop.fromList(topList);
|
this.visualizationBoxTop.fromList(topList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonCreator
|
TargetModel(List<Point3> realWorldCoordinatesArray, double boxHeight) {
|
||||||
TargetModel(
|
this(listToMat(realWorldCoordinatesArray), boxHeight);
|
||||||
@JsonProperty(value = "realWorldCoordinatesArray") List<Point3> points,
|
|
||||||
@JsonProperty(value = "boxHeight") double boxHeight) {
|
|
||||||
this(listToMat(points), boxHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Point3> getRealWorldCoordinatesArray() {
|
public List<Point3> getRealWorldCoordinatesArray() {
|
||||||
@@ -227,6 +219,12 @@ public enum TargetModel implements Releasable {
|
|||||||
// return new TargetModel(corners, 0);
|
// return new TargetModel(corners, 0);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@Json.Value
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
realWorldTargetCoordinates.release();
|
realWorldTargetCoordinates.release();
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ package org.photonvision.common.configuration;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -32,7 +32,6 @@ import org.photonvision.common.LoadJNI;
|
|||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.LogLevel;
|
import org.photonvision.common.logging.LogLevel;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.photonvision.vision.camera.PVCameraInfo;
|
import org.photonvision.vision.camera.PVCameraInfo;
|
||||||
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
|
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
|
||||||
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
||||||
@@ -135,60 +134,31 @@ public class ConfigTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testJacksonHandlesOldVersions() throws IOException {
|
public void testJsonbHandlesOldVersions() throws IOException {
|
||||||
var str =
|
var json =
|
||||||
"{\"baseName\":\"aaaaaa\",\"uniqueName\":\"aaaaaa\",\"nickname\":\"aaaaaa\",\"FOV\":70.0,\"path\":\"dev/vid\",\"cameraType\":\"UsbCamera\",\"currentPipelineIndex\":0,\"camPitch\":{\"radians\":0.0},\"calibrations\":[], \"cameraLEDs\":[]}";
|
"{\"baseName\":\"aaaaaa\",\"uniqueName\":\"aaaaaa\",\"nickname\":\"aaaaaa\",\"FOV\":70.0,\"path\":\"dev/vid\",\"cameraType\":\"UsbCamera\",\"currentPipelineIndex\":0,\"camPitch\":{\"radians\":0.0},\"calibrations\":[], \"cameraLEDs\":[]}";
|
||||||
File tempFile = File.createTempFile("test", ".json");
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
var writer = new FileWriter(tempFile);
|
|
||||||
writer.write(str);
|
|
||||||
writer.flush();
|
|
||||||
writer.close();
|
|
||||||
CameraConfiguration result =
|
|
||||||
JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
|
|
||||||
|
|
||||||
tempFile.delete();
|
CameraConfiguration result = Jsonb.instance().type(CameraConfiguration.class).fromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testJacksonAddUSBVIDPID() throws IOException {
|
public void testJsonbAddUSBVIDPID() throws IOException {
|
||||||
var str =
|
var json =
|
||||||
"{\"baseName\":\"aaaaaa\",\"uniqueName\":\"aaaaaa\",\"nickname\":\"aaaaaa\",\"FOV\":70.0,\"path\":\"dev/vid\",\"cameraType\":\"UsbCamera\",\"currentPipelineIndex\":0,\"camPitch\":{\"radians\":0.0},\"calibrations\":[], \"usbVID\":3, \"usbPID\":4, \"cameraLEDs\":[]}";
|
"{\"baseName\":\"aaaaaa\",\"uniqueName\":\"aaaaaa\",\"nickname\":\"aaaaaa\",\"FOV\":70.0,\"path\":\"dev/vid\",\"cameraType\":\"UsbCamera\",\"currentPipelineIndex\":0,\"camPitch\":{\"radians\":0.0},\"calibrations\":[], \"usbVID\":3, \"usbPID\":4, \"cameraLEDs\":[]}";
|
||||||
File tempFile = File.createTempFile("test", ".json");
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
var writer = new FileWriter(tempFile);
|
|
||||||
writer.write(str);
|
|
||||||
writer.flush();
|
|
||||||
writer.close();
|
|
||||||
|
|
||||||
try {
|
CameraConfiguration result = Jsonb.instance().type(CameraConfiguration.class).fromJson(json);
|
||||||
CameraConfiguration result =
|
String ser = Jsonb.instance().toJson(result);
|
||||||
JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
|
System.out.println(ser);
|
||||||
String ser = JacksonUtils.serializeToString(result);
|
|
||||||
System.out.println(ser);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
tempFile.delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testJacksonHandlesOldTargetEnum() throws IOException {
|
public void testJsonbHandlesOldTargetEnum() throws IOException {
|
||||||
var str = "[ \"AprilTagPipelineSettings\", {\n \"targetModel\" : \"k6in_16h5\"\n} ]\n";
|
var json = "[ \"AprilTagPipelineSettings\", {\n \"targetModel\" : \"k6in_16h5\"\n} ]\n";
|
||||||
|
|
||||||
File tempFile = File.createTempFile("test", ".json");
|
json = CVPipelineSettings.remapSettingsJson(json);
|
||||||
tempFile.deleteOnExit();
|
|
||||||
var writer = new FileWriter(tempFile);
|
|
||||||
writer.write(str);
|
|
||||||
writer.flush();
|
|
||||||
writer.close();
|
|
||||||
|
|
||||||
AprilTagPipelineSettings settings =
|
AprilTagPipelineSettings settings =
|
||||||
(AprilTagPipelineSettings)
|
(AprilTagPipelineSettings) Jsonb.instance().type(CVPipelineSettings.class).fromJson(json);
|
||||||
JacksonUtils.deserialize(tempFile.toPath(), CVPipelineSettings.class);
|
|
||||||
assertEquals(TargetModel.kAprilTag6in_16h5, settings.targetModel);
|
assertEquals(TargetModel.kAprilTag6in_16h5, settings.targetModel);
|
||||||
|
|
||||||
tempFile.delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ package org.photonvision.common.configuration;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.JsonType;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -30,26 +33,30 @@ import org.photonvision.common.util.TestUtils;
|
|||||||
public class NetworkConfigTest {
|
public class NetworkConfigTest {
|
||||||
@Test
|
@Test
|
||||||
public void testSerialization() throws IOException {
|
public void testSerialization() throws IOException {
|
||||||
var mapper = new ObjectMapper();
|
|
||||||
var path = Path.of("netTest.json");
|
var path = Path.of("netTest.json");
|
||||||
mapper.writeValue(path.toFile(), new NetworkConfig());
|
JsonType<NetworkConfig> jsonb = Jsonb.instance().type(NetworkConfig.class);
|
||||||
assertDoesNotThrow(() -> mapper.readValue(path.toFile(), NetworkConfig.class));
|
try (var outputStream = new FileOutputStream(path.toFile())) {
|
||||||
|
jsonb.toJson(new NetworkConfig(), outputStream);
|
||||||
|
}
|
||||||
|
try (var inputStream = new FileInputStream(path.toFile())) {
|
||||||
|
assertDoesNotThrow(() -> jsonb.fromJson(inputStream));
|
||||||
|
}
|
||||||
new File("netTest.json").delete();
|
new File("netTest.json").delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeserializeTeamNumberOrNtServerAddress() {
|
public void testDeserializeTeamNumberOrNtServerAddress() {
|
||||||
{
|
{
|
||||||
var folder = TestUtils.getResourcesFolderPath(true).resolve("network-old-team-number");
|
var folder = TestUtils.getResourcesFolderPath(true).resolve("network-team-number");
|
||||||
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
|
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
|
||||||
configMgr.load();
|
configMgr.load();
|
||||||
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
var folder = TestUtils.getResourcesFolderPath(true).resolve("network-new-team-number");
|
var folder = TestUtils.getResourcesFolderPath(true).resolve("network-ip-addr");
|
||||||
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
|
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
|
||||||
configMgr.load();
|
configMgr.load();
|
||||||
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
assertEquals("127.0.0.1", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ package org.photonvision.common.configuration;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.JsonType;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
|
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties;
|
import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
|
|
||||||
public class NeuralNetworkPropertyManagerTest {
|
public class NeuralNetworkPropertyManagerTest {
|
||||||
@Test
|
@Test
|
||||||
@@ -42,10 +43,12 @@ public class NeuralNetworkPropertyManagerTest {
|
|||||||
640,
|
640,
|
||||||
Family.RKNN,
|
Family.RKNN,
|
||||||
Version.YOLOV8));
|
Version.YOLOV8));
|
||||||
String result = assertDoesNotThrow(() -> JacksonUtils.serializeToString(nnpm));
|
JsonType<NeuralNetworkModelsSettings> jsonb =
|
||||||
var deserializedNnpm =
|
Jsonb.instance().type(NeuralNetworkModelsSettings.class);
|
||||||
assertDoesNotThrow(
|
String result = assertDoesNotThrow(() -> jsonb.toJson(nnpm));
|
||||||
() -> JacksonUtils.deserialize(result, NeuralNetworkModelsSettings.class));
|
System.out.println(result);
|
||||||
|
var deserializedNnpm = assertDoesNotThrow(() -> jsonb.fromJson(result));
|
||||||
|
assertEquals(nnpm.getModels().length, deserializedNnpm.getModels().length);
|
||||||
assertEquals(nnpm.getModels()[0], deserializedNnpm.getModels()[0]);
|
assertEquals(nnpm.getModels()[0], deserializedNnpm.getModels()[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ package org.photonvision.common.configuration;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import io.avaje.json.JsonDataException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -32,8 +33,8 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.photonvision.common.LoadJNI;
|
import org.photonvision.common.LoadJNI;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
||||||
|
import org.photonvision.common.hardware.Platform;
|
||||||
import org.photonvision.common.util.TestUtils;
|
import org.photonvision.common.util.TestUtils;
|
||||||
import org.photonvision.vision.camera.CameraQuirk;
|
|
||||||
import org.photonvision.vision.camera.PVCameraInfo;
|
import org.photonvision.vision.camera.PVCameraInfo;
|
||||||
import org.photonvision.vision.pipeline.AdvancedPipelineSettings;
|
import org.photonvision.vision.pipeline.AdvancedPipelineSettings;
|
||||||
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
|
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
|
||||||
@@ -92,32 +93,6 @@ public class SQLConfigTest {
|
|||||||
assertEquals(cfgLoader.getConfig().getNetworkConfig().ntServerAddress, "5940");
|
assertEquals(cfgLoader.getConfig().getNetworkConfig().ntServerAddress, "5940");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoad2024_3_1() throws IOException {
|
|
||||||
// Copy the 2024.3.1 config to a temp dir
|
|
||||||
FileUtils.copyDirectory(
|
|
||||||
TestUtils.getConfigDirectoriesPath(false)
|
|
||||||
.resolve("photonvision_config_from_v2024.3.1")
|
|
||||||
.toFile(),
|
|
||||||
tmpDir.resolve("photonvision_config_from_v2024.3.1").toFile());
|
|
||||||
|
|
||||||
var cfgLoader = new SqlConfigProvider(tmpDir.resolve("photonvision_config_from_v2024.3.1"));
|
|
||||||
|
|
||||||
assertDoesNotThrow(cfgLoader::load);
|
|
||||||
|
|
||||||
System.out.println(cfgLoader.getConfig());
|
|
||||||
for (var c : CameraQuirk.values()) {
|
|
||||||
assertDoesNotThrow(
|
|
||||||
() ->
|
|
||||||
cfgLoader
|
|
||||||
.config
|
|
||||||
.getCameraConfigurations()
|
|
||||||
.get("Microsoft_LifeCam_HD-3000")
|
|
||||||
.cameraQuirks
|
|
||||||
.hasQuirk(c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void common2025p3p1Assertions(PhotonConfiguration config) {
|
void common2025p3p1Assertions(PhotonConfiguration config) {
|
||||||
// Make sure we got 8 cameras
|
// Make sure we got 8 cameras
|
||||||
assertEquals(8, config.getCameraConfigurations().size());
|
assertEquals(8, config.getCameraConfigurations().size());
|
||||||
@@ -134,7 +109,7 @@ public class SQLConfigTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoadNewNNMM() throws JsonProcessingException, IOException {
|
public void testLoadNewNNMM() throws JsonDataException, IOException {
|
||||||
var folder = tmpDir.resolve("2025.3.1-old-nnmm");
|
var folder = tmpDir.resolve("2025.3.1-old-nnmm");
|
||||||
FileUtils.copyDirectory(
|
FileUtils.copyDirectory(
|
||||||
TestUtils.getConfigDirectoriesPath(false).resolve("2025.3.1-old-nnmm").toFile(),
|
TestUtils.getConfigDirectoriesPath(false).resolve("2025.3.1-old-nnmm").toFile(),
|
||||||
@@ -165,7 +140,7 @@ public class SQLConfigTest {
|
|||||||
common2025p3p1Assertions(reloadedProvider.getConfig());
|
common2025p3p1Assertions(reloadedProvider.getConfig());
|
||||||
|
|
||||||
// And make sure NNPM has all 5 models
|
// And make sure NNPM has all 5 models
|
||||||
assertEquals(5, reloadedProvider.getConfig().neuralNetworkPropertyManager().getModels().length);
|
assertEquals(5, reloadedProvider.getConfig().getNeuralNetworkProperties().getModels().length);
|
||||||
|
|
||||||
ConfigManager.INSTANCE = null;
|
ConfigManager.INSTANCE = null;
|
||||||
}
|
}
|
||||||
@@ -210,4 +185,31 @@ public class SQLConfigTest {
|
|||||||
|
|
||||||
ConfigManager.INSTANCE = null;
|
ConfigManager.INSTANCE = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testV2026p3p4WindowsPaths() throws JsonDataException, IOException {
|
||||||
|
assumeTrue(
|
||||||
|
Platform.isWindows(), "This test is only relevant on Windows, skipping on other platforms");
|
||||||
|
|
||||||
|
var configName = "2026.3.4-windows";
|
||||||
|
var folder = tmpDir.resolve(configName);
|
||||||
|
FileUtils.copyDirectory(
|
||||||
|
TestUtils.getConfigDirectoriesPath(false).resolve(configName).toFile(), folder.toFile());
|
||||||
|
|
||||||
|
var cfgManager = new ConfigManager(folder, new SqlConfigProvider(folder));
|
||||||
|
|
||||||
|
cfgManager.load();
|
||||||
|
|
||||||
|
// Make sure we have calibrated 1280x720, and the board observation paths matches
|
||||||
|
var camCfg =
|
||||||
|
cfgManager
|
||||||
|
.getConfig()
|
||||||
|
.getCameraConfigurations()
|
||||||
|
.get("1414304b-6812-487a-ab5c-89ee70704fae");
|
||||||
|
assertEquals(1280, camCfg.calibrations.get(0).resolution.width);
|
||||||
|
assertEquals(720, camCfg.calibrations.get(0).resolution.height);
|
||||||
|
assertEquals(
|
||||||
|
"C:\\Users\\matth\\Documents\\GitHub\\photonvision\\test\\photonvision_config\\calibration\\1414304b-6812-487a-ab5c-89ee70704fae\\imgs\\1280x720\\img0.png",
|
||||||
|
camCfg.calibrations.get(0).observations.get(0).snapshotDataLocation.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import com.diozero.internal.spi.NativeDeviceFactoryInterface;
|
import com.diozero.internal.spi.NativeDeviceFactoryInterface;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.photonvision.common.configuration.HardwareConfig;
|
import org.photonvision.common.configuration.HardwareConfig;
|
||||||
@@ -33,10 +34,9 @@ import org.photonvision.common.util.TestUtils;
|
|||||||
public class HardwareConfigTest {
|
public class HardwareConfigTest {
|
||||||
@Test
|
@Test
|
||||||
public void loadJson() {
|
public void loadJson() {
|
||||||
try {
|
System.out.println("Loading Hardware configs...");
|
||||||
System.out.println("Loading Hardware configs...");
|
try (var stream = new FileInputStream(TestUtils.getHardwareConfigJson())) {
|
||||||
var config =
|
var config = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
|
||||||
new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class);
|
|
||||||
assertEquals(config.deviceName, "PhotonVision");
|
assertEquals(config.deviceName, "PhotonVision");
|
||||||
// Ensure defaults are not null
|
// Ensure defaults are not null
|
||||||
assertArrayEquals(config.ledPins.stream().mapToInt(i -> i).toArray(), new int[] {2, 13});
|
assertArrayEquals(config.ledPins.stream().mapToInt(i -> i).toArray(), new int[] {2, 13});
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||||||
|
|
||||||
import com.diozero.internal.provider.builtin.DefaultDeviceFactory;
|
import com.diozero.internal.provider.builtin.DefaultDeviceFactory;
|
||||||
import com.diozero.internal.spi.NativeDeviceFactoryInterface;
|
import com.diozero.internal.spi.NativeDeviceFactoryInterface;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -68,8 +69,9 @@ public class HardwareTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() throws IOException {
|
void setup() throws IOException {
|
||||||
System.out.println("Loading Hardware configs...");
|
System.out.println("Loading Hardware configs...");
|
||||||
hardwareConfig =
|
try (var stream = new FileInputStream(TestUtils.getHardwareConfigJson())) {
|
||||||
new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class);
|
hardwareConfig = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
|
||||||
|
}
|
||||||
deviceFactory = HardwareManager.configureCustomGPIO(hardwareConfig);
|
deviceFactory = HardwareManager.configureCustomGPIO(hardwareConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,10 +128,7 @@ public class CalibrationRotationPipeTest {
|
|||||||
|
|
||||||
FrameStaticProperties frameProps =
|
FrameStaticProperties frameProps =
|
||||||
new FrameStaticProperties(
|
new FrameStaticProperties(
|
||||||
(int) coeffs.unrotatedImageSize.width,
|
(int) coeffs.resolution.width, (int) coeffs.resolution.height, 66, coeffs);
|
||||||
(int) coeffs.unrotatedImageSize.height,
|
|
||||||
66,
|
|
||||||
coeffs);
|
|
||||||
|
|
||||||
FrameStaticProperties rotatedFrameProps = frameProps.rotate(rot);
|
FrameStaticProperties rotatedFrameProps = frameProps.rotate(rot);
|
||||||
|
|
||||||
@@ -210,7 +207,7 @@ public class CalibrationRotationPipeTest {
|
|||||||
double[] rotatedCamMat = {fx, 0, res.width - cx, 0, fy, res.height - cy, 0, 0, 1};
|
double[] rotatedCamMat = {fx, 0, res.width - cx, 0, fy, res.height - cy, 0, 0, 1};
|
||||||
assertArrayEquals(rotatedCamMat, coeffs2.cameraIntrinsics.data);
|
assertArrayEquals(rotatedCamMat, coeffs2.cameraIntrinsics.data);
|
||||||
// AND the image size should be the same
|
// AND the image size should be the same
|
||||||
assertEquals(res, coeffs2.unrotatedImageSize);
|
assertEquals(res, coeffs2.resolution);
|
||||||
|
|
||||||
// WHEN the camera calibration is rotated 180 degrees
|
// WHEN the camera calibration is rotated 180 degrees
|
||||||
var coeffs3 = coeffs2.rotateCoefficients(rot);
|
var coeffs3 = coeffs2.rotateCoefficients(rot);
|
||||||
@@ -218,7 +215,7 @@ public class CalibrationRotationPipeTest {
|
|||||||
// THEN the camera matrix will be the same as the original
|
// THEN the camera matrix will be the same as the original
|
||||||
assertArrayEquals(intrinsics, coeffs3.cameraIntrinsics.data);
|
assertArrayEquals(intrinsics, coeffs3.cameraIntrinsics.data);
|
||||||
// AND the image size should be the same
|
// AND the image size should be the same
|
||||||
assertEquals(res, coeffs2.unrotatedImageSize);
|
assertEquals(res, coeffs2.resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CartesianTest
|
@CartesianTest
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -115,9 +115,9 @@ public class VisionModuleManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashMap<Integer, VideoMode> getAllVideoModes() {
|
public List<VideoMode> getAllVideoModes() {
|
||||||
var ret = new HashMap<Integer, VideoMode>();
|
var ret = new ArrayList<VideoMode>();
|
||||||
ret.put(0, getCurrentVideoMode());
|
ret.add(getCurrentVideoMode());
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -31,8 +32,8 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.photonvision.common.LoadJNI;
|
import org.photonvision.common.LoadJNI;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
|
import org.photonvision.common.configuration.PhotonConfiguration;
|
||||||
import org.photonvision.common.util.TestUtils;
|
import org.photonvision.common.util.TestUtils;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.photonvision.vision.camera.PVCameraInfo;
|
import org.photonvision.vision.camera.PVCameraInfo;
|
||||||
import org.wpilib.vision.camera.UsbCameraInfo;
|
import org.wpilib.vision.camera.UsbCameraInfo;
|
||||||
|
|
||||||
@@ -94,17 +95,18 @@ public class VisionSourceManagerTest {
|
|||||||
7,
|
7,
|
||||||
8));
|
8));
|
||||||
|
|
||||||
var str = JacksonUtils.serializeToString(usb);
|
var str = Jsonb.instance().type(PVCameraInfo.class).toJson(usb);
|
||||||
System.out.println(str);
|
System.out.println(str);
|
||||||
System.out.println(JacksonUtils.deserialize(str, PVCameraInfo.class));
|
System.out.println(Jsonb.instance().type(PVCameraInfo.class).fromJson(str));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
var csi =
|
var csi =
|
||||||
PVCameraInfo.fromCSICameraInfo(
|
PVCameraInfo.fromCSICameraInfo(
|
||||||
"/dev/v4l/by-path/platform-1f00110000.csi-video-index0", "rp1-cfe");
|
"/dev/v4l/by-path/platform-1f00110000.csi-video-index0", "rp1-cfe");
|
||||||
var str = JacksonUtils.serializeToString(csi);
|
|
||||||
|
var str = Jsonb.instance().type(PVCameraInfo.class).toJson(csi);
|
||||||
System.out.println(str);
|
System.out.println(str);
|
||||||
System.out.println(JacksonUtils.deserialize(str, PVCameraInfo.class));
|
System.out.println(Jsonb.instance().type(PVCameraInfo.class).fromJson(str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +139,10 @@ public class VisionSourceManagerTest {
|
|||||||
|
|
||||||
vsm.assignUnmatchedCamera(fileCamera1);
|
vsm.assignUnmatchedCamera(fileCamera1);
|
||||||
|
|
||||||
System.out.println(JacksonUtils.serializeToString(ConfigManager.getInstance().getConfig()));
|
System.out.println(
|
||||||
|
Jsonb.instance()
|
||||||
|
.type(PhotonConfiguration.class)
|
||||||
|
.toJson(ConfigManager.getInstance().getConfig()));
|
||||||
|
|
||||||
// And make assertions about the current matching state
|
// And make assertions about the current matching state
|
||||||
assertEquals(1, vsm.getVsmState().allConnectedCameras.size());
|
assertEquals(1, vsm.getVsmState().allConnectedCameras.size());
|
||||||
@@ -268,7 +273,10 @@ public class VisionSourceManagerTest {
|
|||||||
|
|
||||||
vsm.assignUnmatchedCamera(fileCamera3);
|
vsm.assignUnmatchedCamera(fileCamera3);
|
||||||
|
|
||||||
System.out.println(JacksonUtils.serializeToString(ConfigManager.getInstance().getConfig()));
|
System.out.println(
|
||||||
|
Jsonb.instance()
|
||||||
|
.type(PhotonConfiguration.class)
|
||||||
|
.toJson(ConfigManager.getInstance().getConfig()));
|
||||||
|
|
||||||
// And make assertions about the current matching state
|
// And make assertions about the current matching state
|
||||||
assertEquals(3, vsm.getVsmState().allConnectedCameras.size());
|
assertEquals(3, vsm.getVsmState().allConnectedCameras.size());
|
||||||
|
|||||||
@@ -24,7 +24,10 @@
|
|||||||
|
|
||||||
package org.photonvision.simulation;
|
package org.photonvision.simulation;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.json.JsonIoException;
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -66,6 +69,26 @@ import org.wpilib.math.util.Pair;
|
|||||||
* network latency and the latency reported will always be perfect.
|
* network latency and the latency reported will always be perfect.
|
||||||
*/
|
*/
|
||||||
public class SimCameraProperties {
|
public class SimCameraProperties {
|
||||||
|
@Json
|
||||||
|
record SimCameraData(SimCameraCalibrationData[] calibrations) {
|
||||||
|
@Json
|
||||||
|
record SimCameraCalibrationData(
|
||||||
|
ResolutionData resolution,
|
||||||
|
CameraIntrinsicsData cameraIntrinsics,
|
||||||
|
DistortionCoefficients distCoeffs,
|
||||||
|
double[] perViewErrors,
|
||||||
|
double standardDeviation) {
|
||||||
|
@Json
|
||||||
|
record ResolutionData(int width, int height) {}
|
||||||
|
|
||||||
|
@Json
|
||||||
|
record CameraIntrinsicsData(double[] data) {}
|
||||||
|
|
||||||
|
@Json
|
||||||
|
record DistortionCoefficients(double[] data) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final Random rand = new Random();
|
private final Random rand = new Random();
|
||||||
// calibration
|
// calibration
|
||||||
private int resWidth;
|
private int resWidth;
|
||||||
@@ -114,47 +137,26 @@ public class SimCameraProperties {
|
|||||||
* calibrated resolution.
|
* calibrated resolution.
|
||||||
*/
|
*/
|
||||||
public SimCameraProperties(Path path, int width, int height) throws IOException {
|
public SimCameraProperties(Path path, int width, int height) throws IOException {
|
||||||
var mapper = new ObjectMapper();
|
SimCameraData data;
|
||||||
var json = mapper.readTree(path.toFile());
|
try (var stream = new FileInputStream(path.toFile())) {
|
||||||
json = json.get("calibrations");
|
data = Jsonb.instance().type(SimCameraData.class).fromJson(stream);
|
||||||
|
} catch (JsonIoException e) {
|
||||||
|
throw new IOException("Invalid calibration JSON", e);
|
||||||
|
}
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
for (var calib : data.calibrations) {
|
||||||
for (int i = 0; i < json.size() && !success; i++) {
|
// check if this calibration entry is our desired resolution
|
||||||
// check if this calibration entry is our desired resolution
|
if (calib.resolution.width != width || calib.resolution.height != height) continue;
|
||||||
var calib = json.get(i);
|
// get the relevant calibration values
|
||||||
int jsonWidth = calib.get("resolution").get("width").asInt();
|
double avgViewError = Arrays.stream(calib.perViewErrors).average().orElse(0);
|
||||||
int jsonHeight = calib.get("resolution").get("height").asInt();
|
// assign the read JSON values to this CameraProperties
|
||||||
if (jsonWidth != width || jsonHeight != height) continue;
|
setCalibration(
|
||||||
// get the relevant calibration values
|
calib.resolution.width,
|
||||||
var jsonIntrinsicsNode = calib.get("cameraIntrinsics").get("data");
|
calib.resolution.height,
|
||||||
double[] jsonIntrinsics = new double[jsonIntrinsicsNode.size()];
|
MatBuilder.fill(Nat.N3(), Nat.N3(), calib.cameraIntrinsics.data),
|
||||||
for (int j = 0; j < jsonIntrinsicsNode.size(); j++) {
|
MatBuilder.fill(Nat.N8(), Nat.N1(), calib.distCoeffs.data));
|
||||||
jsonIntrinsics[j] = jsonIntrinsicsNode.get(j).asDouble();
|
setCalibError(avgViewError, calib.standardDeviation);
|
||||||
}
|
success = true;
|
||||||
var jsonDistortNode = calib.get("distCoeffs").get("data");
|
|
||||||
double[] jsonDistortion = new double[8];
|
|
||||||
Arrays.fill(jsonDistortion, 0);
|
|
||||||
for (int j = 0; j < jsonDistortNode.size(); j++) {
|
|
||||||
jsonDistortion[j] = jsonDistortNode.get(j).asDouble();
|
|
||||||
}
|
|
||||||
var jsonViewErrors = calib.get("perViewErrors");
|
|
||||||
double jsonAvgError = 0;
|
|
||||||
for (int j = 0; j < jsonViewErrors.size(); j++) {
|
|
||||||
jsonAvgError += jsonViewErrors.get(j).asDouble();
|
|
||||||
}
|
|
||||||
jsonAvgError /= jsonViewErrors.size();
|
|
||||||
double jsonErrorStdDev = calib.get("standardDeviation").asDouble();
|
|
||||||
// assign the read JSON values to this CameraProperties
|
|
||||||
setCalibration(
|
|
||||||
jsonWidth,
|
|
||||||
jsonHeight,
|
|
||||||
MatBuilder.fill(Nat.N3(), Nat.N3(), jsonIntrinsics),
|
|
||||||
MatBuilder.fill(Nat.N8(), Nat.N1(), jsonDistortion));
|
|
||||||
setCalibError(jsonAvgError, jsonErrorStdDev);
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IOException("Invalid calibration JSON");
|
|
||||||
}
|
}
|
||||||
if (!success) throw new IOException("Requested resolution not found in calibration");
|
if (!success) throw new IOException("Requested resolution not found in calibration");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ shadowJar {
|
|||||||
configurations = [
|
configurations = [
|
||||||
project.configurations.runtimeClasspath
|
project.configurations.runtimeClasspath
|
||||||
]
|
]
|
||||||
|
mergeServiceFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
node {
|
node {
|
||||||
|
|||||||
@@ -235,6 +235,8 @@ public class Main {
|
|||||||
Logger.setLevel(LogGroup.General, logLevel);
|
Logger.setLevel(LogGroup.General, logLevel);
|
||||||
logger.info("Logging initialized in debug mode.");
|
logger.info("Logging initialized in debug mode.");
|
||||||
|
|
||||||
|
System.setProperty("jsonb.disableAdapterSpi", "true");
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Starting PhotonVision version "
|
"Starting PhotonVision version "
|
||||||
+ PhotonVersion.versionString
|
+ PhotonVersion.versionString
|
||||||
|
|||||||
@@ -17,22 +17,21 @@
|
|||||||
|
|
||||||
package org.photonvision.server;
|
package org.photonvision.server;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import io.avaje.json.JsonException;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import io.avaje.jsonb.jackson.JacksonAdapter;
|
||||||
import io.javalin.websocket.WsBinaryMessageContext;
|
import io.javalin.websocket.WsBinaryMessageContext;
|
||||||
import io.javalin.websocket.WsCloseContext;
|
import io.javalin.websocket.WsCloseContext;
|
||||||
import io.javalin.websocket.WsConnectContext;
|
import io.javalin.websocket.WsConnectContext;
|
||||||
import io.javalin.websocket.WsContext;
|
import io.javalin.websocket.WsContext;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.msgpack.jackson.dataformat.MessagePackFactory;
|
import org.msgpack.jackson.dataformat.MessagePackFactory;
|
||||||
import org.photonvision.common.dataflow.DataChangeDestination;
|
import org.photonvision.common.dataflow.DataChangeDestination;
|
||||||
import org.photonvision.common.dataflow.DataChangeService;
|
import org.photonvision.common.dataflow.DataChangeService;
|
||||||
@@ -47,7 +46,9 @@ import org.wpilib.math.util.Pair;
|
|||||||
public class DataSocketHandler {
|
public class DataSocketHandler {
|
||||||
private final Logger logger = new Logger(DataSocketHandler.class, LogGroup.WebServer);
|
private final Logger logger = new Logger(DataSocketHandler.class, LogGroup.WebServer);
|
||||||
private final List<WsContext> users = new CopyOnWriteArrayList<>();
|
private final List<WsContext> users = new CopyOnWriteArrayList<>();
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
|
private final JacksonAdapter adapter =
|
||||||
|
JacksonAdapter.builder().jsonFactory(new MessagePackFactory()).serializeEmpty(true).build();
|
||||||
|
private final Jsonb msgpackJsonb = Jsonb.builder().adapter(adapter).build();
|
||||||
private final DataChangeService dcService = DataChangeService.getInstance();
|
private final DataChangeService dcService = DataChangeService.getInstance();
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
@@ -91,20 +92,16 @@ public class DataSocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Json
|
||||||
|
static record WSMessage(
|
||||||
|
@Nullable String cameraUniqueName, @Json.Unmapped Map<String, Object> properties) {}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
@SuppressWarnings({"unchecked"})
|
||||||
public void onBinaryMessage(WsBinaryMessageContext context) {
|
public void onBinaryMessage(WsBinaryMessageContext context) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> deserializedData =
|
var message = msgpackJsonb.type(WSMessage.class).fromJson(context.data());
|
||||||
objectMapper.readValue(context.data(), new TypeReference<>() {});
|
|
||||||
|
|
||||||
// Special case the current camera index
|
for (Map.Entry<String, Object> entry : message.properties.entrySet()) {
|
||||||
String cameraUniqueName = "";
|
|
||||||
if (deserializedData.get("cameraUniqueName") instanceof String camUniqueNameValue) {
|
|
||||||
cameraUniqueName = camUniqueNameValue;
|
|
||||||
deserializedData.remove("cameraUniqueName");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<String, Object> entry : deserializedData.entrySet()) {
|
|
||||||
try {
|
try {
|
||||||
var entryKey = entry.getKey();
|
var entryKey = entry.getKey();
|
||||||
var entryValue = entry.getValue();
|
var entryValue = entry.getValue();
|
||||||
@@ -131,7 +128,7 @@ public class DataSocketHandler {
|
|||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"isDriverMode",
|
"isDriverMode",
|
||||||
(Boolean) entryValue,
|
(Boolean) entryValue,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_CHANGECAMERANAME ->
|
case SMT_CHANGECAMERANAME ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
@@ -139,7 +136,7 @@ public class DataSocketHandler {
|
|||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"cameraNickname",
|
"cameraNickname",
|
||||||
(String) entryValue,
|
(String) entryValue,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_CHANGEPIPELINENAME ->
|
case SMT_CHANGEPIPELINENAME ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
@@ -147,38 +144,39 @@ public class DataSocketHandler {
|
|||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"pipelineName",
|
"pipelineName",
|
||||||
(String) entryValue,
|
(String) entryValue,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_ADDNEWPIPELINE -> {
|
case SMT_ADDNEWPIPELINE -> {
|
||||||
// HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
|
// HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
|
||||||
// var type = (PipelineType) data.get("pipelineType");
|
// var type = (PipelineType) data.get("pipelineType");
|
||||||
// var name = (String) data.get("pipelineName");
|
// var name = (String) data.get("pipelineName");
|
||||||
var arr = (ArrayList<Object>) entryValue;
|
var arr = (List<Object>) entryValue;
|
||||||
var name = (String) arr.get(0);
|
var name = (String) arr.get(0);
|
||||||
var type = PipelineType.values()[(Integer) arr.get(1) + 3];
|
var type = PipelineType.values()[((Long) arr.get(1)).intValue() + 3];
|
||||||
|
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
new IncomingWebSocketEvent<>(
|
new IncomingWebSocketEvent<>(
|
||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"newPipelineInfo",
|
"newPipelineInfo",
|
||||||
Pair.of(name, type),
|
Pair.of(name, type),
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
}
|
}
|
||||||
case SMT_CHANGEBRIGHTNESS ->
|
case SMT_CHANGEBRIGHTNESS ->
|
||||||
HardwareManager.getInstance()
|
HardwareManager.getInstance()
|
||||||
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
|
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
|
||||||
case SMT_DUPLICATEPIPELINE -> {
|
case SMT_DUPLICATEPIPELINE -> {
|
||||||
var pipeIndex = (Integer) entryValue;
|
var pipeIndex = ((Long) entryValue).intValue();
|
||||||
|
|
||||||
logger.info("Duplicating pipe@index" + pipeIndex + " for camera " + cameraUniqueName);
|
logger.info(
|
||||||
|
"Duplicating pipe@index" + pipeIndex + " for camera " + message.cameraUniqueName);
|
||||||
|
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
new IncomingWebSocketEvent<>(
|
new IncomingWebSocketEvent<>(
|
||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"duplicatePipeline",
|
"duplicatePipeline",
|
||||||
pipeIndex,
|
pipeIndex,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
}
|
}
|
||||||
case SMT_DELETECURRENTPIPELINE ->
|
case SMT_DELETECURRENTPIPELINE ->
|
||||||
@@ -187,27 +185,29 @@ public class DataSocketHandler {
|
|||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"deleteCurrPipeline",
|
"deleteCurrPipeline",
|
||||||
0,
|
0,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_ROBOTOFFSETPOINT ->
|
case SMT_ROBOTOFFSETPOINT ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
new IncomingWebSocketEvent<>(
|
new IncomingWebSocketEvent<>(
|
||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"robotOffsetPoint",
|
"robotOffsetPoint",
|
||||||
(Integer) entryValue,
|
((Long) entryValue).intValue(),
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
null));
|
null));
|
||||||
case SMT_CURRENTCAMERA ->
|
case SMT_CURRENTCAMERA ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
new IncomingWebSocketEvent<>(
|
new IncomingWebSocketEvent<>(
|
||||||
DataChangeDestination.DCD_OTHER, "changeUICamera", (Integer) entryValue));
|
DataChangeDestination.DCD_OTHER,
|
||||||
|
"changeUICamera",
|
||||||
|
((Long) entryValue).intValue()));
|
||||||
case SMT_CURRENTPIPELINE ->
|
case SMT_CURRENTPIPELINE ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
new IncomingWebSocketEvent<>(
|
new IncomingWebSocketEvent<>(
|
||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"changePipeline",
|
"changePipeline",
|
||||||
(Integer) entryValue,
|
((Long) entryValue).intValue(),
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_STARTPNPCALIBRATION ->
|
case SMT_STARTPNPCALIBRATION ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
@@ -215,7 +215,7 @@ public class DataSocketHandler {
|
|||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"startCalibration",
|
"startCalibration",
|
||||||
(Map) entryValue,
|
(Map) entryValue,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_SAVEINPUTSNAPSHOT ->
|
case SMT_SAVEINPUTSNAPSHOT ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
@@ -223,7 +223,7 @@ public class DataSocketHandler {
|
|||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"saveInputSnapshot",
|
"saveInputSnapshot",
|
||||||
0,
|
0,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_SAVEOUTPUTSNAPSHOT ->
|
case SMT_SAVEOUTPUTSNAPSHOT ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
@@ -231,7 +231,7 @@ public class DataSocketHandler {
|
|||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"saveOutputSnapshot",
|
"saveOutputSnapshot",
|
||||||
0,
|
0,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_TAKECALIBRATIONSNAPSHOT ->
|
case SMT_TAKECALIBRATIONSNAPSHOT ->
|
||||||
dcService.publishEvent(
|
dcService.publishEvent(
|
||||||
@@ -239,10 +239,10 @@ public class DataSocketHandler {
|
|||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"takeCalSnapshot",
|
"takeCalSnapshot",
|
||||||
0,
|
0,
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
case SMT_PIPELINESETTINGCHANGE -> {
|
case SMT_PIPELINESETTINGCHANGE -> {
|
||||||
HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
|
Map<String, Object> data = (Map) entryValue;
|
||||||
|
|
||||||
if (data.size() >= 2) {
|
if (data.size() >= 2) {
|
||||||
var cameraIndex2 = (String) data.get("cameraUniqueName");
|
var cameraIndex2 = (String) data.get("cameraUniqueName");
|
||||||
@@ -267,28 +267,27 @@ public class DataSocketHandler {
|
|||||||
new IncomingWebSocketEvent<>(
|
new IncomingWebSocketEvent<>(
|
||||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||||
"changePipelineType",
|
"changePipelineType",
|
||||||
(Integer) entryValue,
|
((Long) entryValue).intValue(),
|
||||||
cameraUniqueName,
|
message.cameraUniqueName,
|
||||||
context));
|
context));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to parse message!", e);
|
logger.error("Failed to parse message!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
logger.error("Failed to deserialize message!", e);
|
logger.error("Failed to deserialize message!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(ByteBuffer b, WsContext user) throws JsonProcessingException {
|
private void sendMessage(ByteBuffer b, WsContext user) {
|
||||||
if (user.session.isOpen()) {
|
if (user.session.isOpen()) {
|
||||||
user.send(b);
|
user.send(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void broadcastMessage(Object message, WsContext userToSkip)
|
public void broadcastMessage(Object message, WsContext userToSkip) throws JsonException {
|
||||||
throws JsonProcessingException {
|
ByteBuffer b = ByteBuffer.wrap(msgpackJsonb.toJsonBytes(message));
|
||||||
ByteBuffer b = ByteBuffer.wrap(objectMapper.writeValueAsBytes(message));
|
|
||||||
|
|
||||||
if (userToSkip == null) {
|
if (userToSkip == null) {
|
||||||
for (WsContext user : users) {
|
for (WsContext user : users) {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import java.util.EnumSet;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public enum DataSocketMessageType {
|
public enum DataSocketMessageType {
|
||||||
SMT_DRIVERMODE("driverMode"),
|
SMT_DRIVERMODE("driverMode"),
|
||||||
SMT_CHANGECAMERANAME("changeCameraName"),
|
SMT_CHANGECAMERANAME("changeCameraName"),
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
|
|
||||||
package org.photonvision.server;
|
package org.photonvision.server;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import io.avaje.json.JsonException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.Json;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import io.javalin.http.Context;
|
import io.javalin.http.Context;
|
||||||
import io.javalin.http.UploadedFile;
|
import io.javalin.http.UploadedFile;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@@ -53,7 +54,6 @@ import org.photonvision.common.logging.Logger;
|
|||||||
import org.photonvision.common.networking.NetworkManager;
|
import org.photonvision.common.networking.NetworkManager;
|
||||||
import org.photonvision.common.util.ShellExec;
|
import org.photonvision.common.util.ShellExec;
|
||||||
import org.photonvision.common.util.TimedTaskManager;
|
import org.photonvision.common.util.TimedTaskManager;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.photonvision.common.util.file.ProgramDirectoryUtilities;
|
import org.photonvision.common.util.file.ProgramDirectoryUtilities;
|
||||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||||
import org.photonvision.vision.camera.CameraQuirk;
|
import org.photonvision.vision.camera.CameraQuirk;
|
||||||
@@ -71,8 +71,6 @@ public class RequestHandler {
|
|||||||
|
|
||||||
private static final Logger logger = new Logger(RequestHandler.class, LogGroup.WebServer);
|
private static final Logger logger = new Logger(RequestHandler.class, LogGroup.WebServer);
|
||||||
|
|
||||||
private static final ObjectMapper kObjectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
private static boolean testMode = false;
|
private static boolean testMode = false;
|
||||||
|
|
||||||
public static void onStatusRequest(Context ctx) {
|
public static void onStatusRequest(Context ctx) {
|
||||||
@@ -84,7 +82,8 @@ public class RequestHandler {
|
|||||||
testMode = isTestMode;
|
testMode = isTestMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CommonCameraUniqueName(String cameraUniqueName) {}
|
@Json
|
||||||
|
record CommonCameraUniqueName(String cameraUniqueName) {}
|
||||||
|
|
||||||
public static void onSettingsImportRequest(Context ctx) {
|
public static void onSettingsImportRequest(Context ctx) {
|
||||||
var file = ctx.uploadedFile("data");
|
var file = ctx.uploadedFile("data");
|
||||||
@@ -372,12 +371,12 @@ public class RequestHandler {
|
|||||||
public static void onGeneralSettingsRequest(Context ctx) {
|
public static void onGeneralSettingsRequest(Context ctx) {
|
||||||
NetworkConfig config;
|
NetworkConfig config;
|
||||||
try {
|
try {
|
||||||
config = kObjectMapper.readValue(ctx.bodyInputStream(), NetworkConfig.class);
|
config = Jsonb.instance().type(NetworkConfig.class).fromJson(ctx.bodyInputStream());
|
||||||
|
|
||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
ctx.result("Successfully saved general settings");
|
ctx.result("Successfully saved general settings");
|
||||||
logger.info("Successfully saved general settings");
|
logger.info("Successfully saved general settings");
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
// If the settings can't be parsed, use the default network settings
|
// If the settings can't be parsed, use the default network settings
|
||||||
config = new NetworkConfig();
|
config = new NetworkConfig();
|
||||||
|
|
||||||
@@ -394,13 +393,14 @@ public class RequestHandler {
|
|||||||
NetworkTablesManager.getInstance().setConfig(config);
|
NetworkTablesManager.getInstance().setConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CameraSettingsRequest(
|
@Json
|
||||||
|
record CameraSettingsRequest(
|
||||||
double fov, HashMap<CameraQuirk, Boolean> quirksToChange, String cameraUniqueName) {}
|
double fov, HashMap<CameraQuirk, Boolean> quirksToChange, String cameraUniqueName) {}
|
||||||
|
|
||||||
public static void onCameraSettingsRequest(Context ctx) {
|
public static void onCameraSettingsRequest(Context ctx) {
|
||||||
try {
|
try {
|
||||||
CameraSettingsRequest request =
|
CameraSettingsRequest request =
|
||||||
kObjectMapper.readValue(ctx.body(), CameraSettingsRequest.class);
|
Jsonb.instance().type(CameraSettingsRequest.class).fromJson(ctx.body());
|
||||||
// Extract the settings from the request
|
// Extract the settings from the request
|
||||||
double fov = request.fov;
|
double fov = request.fov;
|
||||||
HashMap<CameraQuirk, Boolean> quirksToChange = request.quirksToChange;
|
HashMap<CameraQuirk, Boolean> quirksToChange = request.quirksToChange;
|
||||||
@@ -489,7 +489,7 @@ public class RequestHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
CommonCameraUniqueName request =
|
CommonCameraUniqueName request =
|
||||||
kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
|
Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
|
||||||
|
|
||||||
var calData =
|
var calData =
|
||||||
VisionSourceManager.getInstance()
|
VisionSourceManager.getInstance()
|
||||||
@@ -509,7 +509,7 @@ public class RequestHandler {
|
|||||||
ctx.result("Camera calibration successfully completed!");
|
ctx.result("Camera calibration successfully completed!");
|
||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
logger.info("Camera calibration successfully completed!");
|
logger.info("Camera calibration successfully completed!");
|
||||||
} catch (JsonProcessingException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
ctx.status(400);
|
ctx.status(400);
|
||||||
ctx.result(
|
ctx.result(
|
||||||
"The 'cameraUniqueName' field was not found in the request. Please make sure the cameraUniqueName of the vision module is specified with the 'cameraUniqueName' key.");
|
"The 'cameraUniqueName' field was not found in the request. Please make sure the cameraUniqueName of the vision module is specified with the 'cameraUniqueName' key.");
|
||||||
@@ -523,13 +523,14 @@ public class RequestHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record DataCalibrationImportRequest(
|
@Json
|
||||||
|
record DataCalibrationImportRequest(
|
||||||
String cameraUniqueName, CameraCalibrationCoefficients calibration) {}
|
String cameraUniqueName, CameraCalibrationCoefficients calibration) {}
|
||||||
|
|
||||||
public static void onDataCalibrationImportRequest(Context ctx) {
|
public static void onDataCalibrationImportRequest(Context ctx) {
|
||||||
try {
|
try (var stream = ctx.req().getInputStream()) {
|
||||||
DataCalibrationImportRequest request =
|
DataCalibrationImportRequest request =
|
||||||
kObjectMapper.readValue(ctx.req().getInputStream(), DataCalibrationImportRequest.class);
|
Jsonb.instance().type(DataCalibrationImportRequest.class).fromJson(stream);
|
||||||
|
|
||||||
var uploadCalibrationEvent =
|
var uploadCalibrationEvent =
|
||||||
new IncomingWebSocketEvent<>(
|
new IncomingWebSocketEvent<>(
|
||||||
@@ -543,7 +544,7 @@ public class RequestHandler {
|
|||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
ctx.result("Calibration imported successfully from imported data!");
|
ctx.result("Calibration imported successfully from imported data!");
|
||||||
logger.info("Calibration imported successfully from imported data!");
|
logger.info("Calibration imported successfully from imported data!");
|
||||||
} catch (JsonProcessingException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
ctx.status(400);
|
ctx.status(400);
|
||||||
ctx.result("The provided calibration data was malformed");
|
ctx.result("The provided calibration data was malformed");
|
||||||
logger.error("The provided calibration data was malformed", e);
|
logger.error("The provided calibration data was malformed", e);
|
||||||
@@ -704,11 +705,10 @@ public class RequestHandler {
|
|||||||
}
|
}
|
||||||
ConfigManager.getInstance()
|
ConfigManager.getInstance()
|
||||||
.getConfig()
|
.getConfig()
|
||||||
.neuralNetworkPropertyManager()
|
.getNeuralNetworkProperties()
|
||||||
.addModelProperties(modelProperties);
|
.addModelProperties(modelProperties);
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().toString());
|
||||||
ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().toString());
|
|
||||||
|
|
||||||
NeuralNetworkModelManager.getInstance().discoverModels();
|
NeuralNetworkModelManager.getInstance().discoverModels();
|
||||||
|
|
||||||
@@ -860,14 +860,15 @@ public class RequestHandler {
|
|||||||
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private record DeleteObjectDetectionModelRequest(Path modelPath) {}
|
@Json
|
||||||
|
record DeleteObjectDetectionModelRequest(Path modelPath) {}
|
||||||
|
|
||||||
public static void onDeleteObjectDetectionModelRequest(Context ctx) {
|
public static void onDeleteObjectDetectionModelRequest(Context ctx) {
|
||||||
logger.info("Deleting object detection model");
|
logger.info("Deleting object detection model");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DeleteObjectDetectionModelRequest request =
|
DeleteObjectDetectionModelRequest request =
|
||||||
JacksonUtils.deserialize(ctx.body(), DeleteObjectDetectionModelRequest.class);
|
Jsonb.instance().type(DeleteObjectDetectionModelRequest.class).fromJson(ctx.body());
|
||||||
|
|
||||||
if (request.modelPath == null) {
|
if (request.modelPath == null) {
|
||||||
ctx.status(400);
|
ctx.status(400);
|
||||||
@@ -892,7 +893,7 @@ public class RequestHandler {
|
|||||||
|
|
||||||
if (!ConfigManager.getInstance()
|
if (!ConfigManager.getInstance()
|
||||||
.getConfig()
|
.getConfig()
|
||||||
.neuralNetworkPropertyManager()
|
.getNeuralNetworkProperties()
|
||||||
.removeModel(request.modelPath)) {
|
.removeModel(request.modelPath)) {
|
||||||
ctx.status(400);
|
ctx.status(400);
|
||||||
ctx.result("The model's information was not found in the config");
|
ctx.result("The model's information was not found in the config");
|
||||||
@@ -917,12 +918,13 @@ public class RequestHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record RenameObjectDetectionModelRequest(Path modelPath, String newName) {}
|
@Json
|
||||||
|
record RenameObjectDetectionModelRequest(Path modelPath, String newName) {}
|
||||||
|
|
||||||
public static void onRenameObjectDetectionModelRequest(Context ctx) {
|
public static void onRenameObjectDetectionModelRequest(Context ctx) {
|
||||||
try {
|
try {
|
||||||
RenameObjectDetectionModelRequest request =
|
RenameObjectDetectionModelRequest request =
|
||||||
JacksonUtils.deserialize(ctx.body(), RenameObjectDetectionModelRequest.class);
|
Jsonb.instance().type(RenameObjectDetectionModelRequest.class).fromJson(ctx.body());
|
||||||
|
|
||||||
if (request.modelPath == null) {
|
if (request.modelPath == null) {
|
||||||
ctx.status(400);
|
ctx.status(400);
|
||||||
@@ -947,7 +949,7 @@ public class RequestHandler {
|
|||||||
|
|
||||||
if (!ConfigManager.getInstance()
|
if (!ConfigManager.getInstance()
|
||||||
.getConfig()
|
.getConfig()
|
||||||
.neuralNetworkPropertyManager()
|
.getNeuralNetworkProperties()
|
||||||
.renameModel(request.modelPath, request.newName)) {
|
.renameModel(request.modelPath, request.newName)) {
|
||||||
ctx.status(400);
|
ctx.status(400);
|
||||||
ctx.result("The model's information was not found in the config");
|
ctx.result("The model's information was not found in the config");
|
||||||
@@ -994,12 +996,13 @@ public class RequestHandler {
|
|||||||
ctx.status(HardwareManager.getInstance().restartDevice() ? 204 : 500);
|
ctx.status(HardwareManager.getInstance().restartDevice() ? 204 : 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CameraNicknameChangeRequest(String name, String cameraUniqueName) {}
|
@Json
|
||||||
|
record CameraNicknameChangeRequest(String name, String cameraUniqueName) {}
|
||||||
|
|
||||||
public static void onCameraNicknameChangeRequest(Context ctx) {
|
public static void onCameraNicknameChangeRequest(Context ctx) {
|
||||||
try {
|
try {
|
||||||
CameraNicknameChangeRequest request =
|
CameraNicknameChangeRequest request =
|
||||||
kObjectMapper.readValue(ctx.body(), CameraNicknameChangeRequest.class);
|
Jsonb.instance().type(CameraNicknameChangeRequest.class).fromJson(ctx.body());
|
||||||
|
|
||||||
VisionSourceManager.getInstance()
|
VisionSourceManager.getInstance()
|
||||||
.vmm
|
.vmm
|
||||||
@@ -1008,7 +1011,7 @@ public class RequestHandler {
|
|||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
ctx.result("Successfully changed the camera name to: " + request.name);
|
ctx.result("Successfully changed the camera name to: " + request.name);
|
||||||
logger.info("Successfully changed the camera name to: " + request.name);
|
logger.info("Successfully changed the camera name to: " + request.name);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
ctx.status(400).result("Invalid JSON format");
|
ctx.status(400).result("Invalid JSON format");
|
||||||
logger.error("Failed to process camera nickname change request", e);
|
logger.error("Failed to process camera nickname change request", e);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -1038,8 +1041,8 @@ public class RequestHandler {
|
|||||||
module.getStateAsCameraConfig().calibrations.stream()
|
module.getStateAsCameraConfig().calibrations.stream()
|
||||||
.filter(
|
.filter(
|
||||||
it ->
|
it ->
|
||||||
Math.abs(it.unrotatedImageSize.width - width) < 1e-4
|
Math.abs(it.resolution.width - width) < 1e-4
|
||||||
&& Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
|
&& Math.abs(it.resolution.height - height) < 1e-4)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
@@ -1052,12 +1055,13 @@ public class RequestHandler {
|
|||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CalibrationRemoveRequest(int width, int height, String cameraUniqueName) {}
|
@Json
|
||||||
|
record CalibrationRemoveRequest(int width, int height, String cameraUniqueName) {}
|
||||||
|
|
||||||
public static void onCalibrationRemoveRequest(Context ctx) {
|
public static void onCalibrationRemoveRequest(Context ctx) {
|
||||||
try {
|
try {
|
||||||
CalibrationRemoveRequest request =
|
CalibrationRemoveRequest request =
|
||||||
kObjectMapper.readValue(ctx.body(), CalibrationRemoveRequest.class);
|
Jsonb.instance().type(CalibrationRemoveRequest.class).fromJson(ctx.body());
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Attempting to remove calibration for camera: "
|
"Attempting to remove calibration for camera: "
|
||||||
@@ -1083,7 +1087,7 @@ public class RequestHandler {
|
|||||||
+ request.width
|
+ request.width
|
||||||
+ "x"
|
+ "x"
|
||||||
+ request.height);
|
+ request.height);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
ctx.status(400).result("Invalid JSON format");
|
ctx.status(400).result("Invalid JSON format");
|
||||||
logger.error("Failed to process calibration removed request", e);
|
logger.error("Failed to process calibration removed request", e);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -1107,8 +1111,8 @@ public class RequestHandler {
|
|||||||
.stream()
|
.stream()
|
||||||
.filter(
|
.filter(
|
||||||
it ->
|
it ->
|
||||||
Math.abs(it.unrotatedImageSize.width - width) < 1e-4
|
Math.abs(it.resolution.width - width) < 1e-4
|
||||||
&& Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
|
&& Math.abs(it.resolution.height - height) < 1e-4)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
@@ -1146,8 +1150,8 @@ public class RequestHandler {
|
|||||||
cc.calibrations.stream()
|
cc.calibrations.stream()
|
||||||
.filter(
|
.filter(
|
||||||
it ->
|
it ->
|
||||||
Math.abs(it.unrotatedImageSize.width - width) < 1e-4
|
Math.abs(it.resolution.width - width) < 1e-4
|
||||||
&& Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
|
&& Math.abs(it.resolution.height - height) < 1e-4)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
@@ -1319,7 +1323,7 @@ public class RequestHandler {
|
|||||||
public static void onNukeOneCamera(Context ctx) {
|
public static void onNukeOneCamera(Context ctx) {
|
||||||
try {
|
try {
|
||||||
CommonCameraUniqueName request =
|
CommonCameraUniqueName request =
|
||||||
kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
|
Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
|
||||||
|
|
||||||
logger.warn("Deleting camera name " + request.cameraUniqueName);
|
logger.warn("Deleting camera name " + request.cameraUniqueName);
|
||||||
|
|
||||||
@@ -1334,7 +1338,7 @@ public class RequestHandler {
|
|||||||
VisionSourceManager.getInstance().deleteVisionSource(request.cameraUniqueName);
|
VisionSourceManager.getInstance().deleteVisionSource(request.cameraUniqueName);
|
||||||
|
|
||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Failed to delete camera", e);
|
logger.error("Failed to delete camera", e);
|
||||||
ctx.status(500);
|
ctx.status(500);
|
||||||
ctx.result("Failed to delete camera");
|
ctx.result("Failed to delete camera");
|
||||||
@@ -1346,7 +1350,7 @@ public class RequestHandler {
|
|||||||
logger.info(ctx.queryString());
|
logger.info(ctx.queryString());
|
||||||
try {
|
try {
|
||||||
CommonCameraUniqueName request =
|
CommonCameraUniqueName request =
|
||||||
kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
|
Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
|
||||||
|
|
||||||
if (VisionSourceManager.getInstance()
|
if (VisionSourceManager.getInstance()
|
||||||
.reactivateDisabledCameraConfig(request.cameraUniqueName)) {
|
.reactivateDisabledCameraConfig(request.cameraUniqueName)) {
|
||||||
@@ -1354,7 +1358,7 @@ public class RequestHandler {
|
|||||||
} else {
|
} else {
|
||||||
ctx.status(403);
|
ctx.status(403);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
ctx.status(401);
|
ctx.status(401);
|
||||||
logger.error("Failed to process activate matched camera request", e);
|
logger.error("Failed to process activate matched camera request", e);
|
||||||
ctx.result("Failed to process activate matched camera request");
|
ctx.result("Failed to process activate matched camera request");
|
||||||
@@ -1362,14 +1366,15 @@ public class RequestHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record AssignUnmatchedCamera(PVCameraInfo cameraInfo) {}
|
@Json
|
||||||
|
record AssignUnmatchedCamera(PVCameraInfo cameraInfo) {}
|
||||||
|
|
||||||
public static void onAssignUnmatchedCameraRequest(Context ctx) {
|
public static void onAssignUnmatchedCameraRequest(Context ctx) {
|
||||||
logger.info(ctx.queryString());
|
logger.info(ctx.queryString());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AssignUnmatchedCamera request =
|
AssignUnmatchedCamera request =
|
||||||
kObjectMapper.readValue(ctx.body(), AssignUnmatchedCamera.class);
|
Jsonb.instance().type(AssignUnmatchedCamera.class).fromJson(ctx.body());
|
||||||
|
|
||||||
if (request.cameraInfo == null) {
|
if (request.cameraInfo == null) {
|
||||||
ctx.status(400);
|
ctx.status(400);
|
||||||
@@ -1385,7 +1390,7 @@ public class RequestHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.result("Successfully assigned camera: " + request.cameraInfo);
|
ctx.result("Successfully assigned camera: " + request.cameraInfo);
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
ctx.status(401);
|
ctx.status(401);
|
||||||
logger.error("Failed to process assign unmatched camera request", e);
|
logger.error("Failed to process assign unmatched camera request", e);
|
||||||
ctx.result("Failed to process assign unmatched camera request");
|
ctx.result("Failed to process assign unmatched camera request");
|
||||||
@@ -1397,14 +1402,14 @@ public class RequestHandler {
|
|||||||
logger.info(ctx.queryString());
|
logger.info(ctx.queryString());
|
||||||
try {
|
try {
|
||||||
CommonCameraUniqueName request =
|
CommonCameraUniqueName request =
|
||||||
kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
|
Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
|
||||||
|
|
||||||
if (VisionSourceManager.getInstance().deactivateVisionSource(request.cameraUniqueName)) {
|
if (VisionSourceManager.getInstance().deactivateVisionSource(request.cameraUniqueName)) {
|
||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
} else {
|
} else {
|
||||||
ctx.status(403);
|
ctx.status(403);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
ctx.status(401);
|
ctx.status(401);
|
||||||
logger.error("Failed to process unassign camera request", e);
|
logger.error("Failed to process unassign camera request", e);
|
||||||
ctx.result("Failed to process unassign camera request");
|
ctx.result("Failed to process unassign camera request");
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataChangeEvent(DataChangeEvent<?> event) {
|
public <T> void onDataChangeEvent(DataChangeEvent<T> event) {
|
||||||
if (event.propertyName.equals("restartServer")) {
|
if (event.propertyName.equals("restartServer")) {
|
||||||
Server.restart();
|
Server.restart();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,14 @@
|
|||||||
|
|
||||||
package org.photonvision.server;
|
package org.photonvision.server;
|
||||||
|
|
||||||
|
import io.avaje.jsonb.Json;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import io.javalin.http.Context;
|
import io.javalin.http.Context;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelManager;
|
import org.photonvision.common.configuration.NeuralNetworkModelManager;
|
||||||
import org.photonvision.common.hardware.Platform;
|
import org.photonvision.common.hardware.Platform;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
|
|
||||||
public class TestRequestHandler {
|
public class TestRequestHandler {
|
||||||
// Treat all 2XX calls as "INFO"
|
// Treat all 2XX calls as "INFO"
|
||||||
@@ -39,12 +40,13 @@ public class TestRequestHandler {
|
|||||||
ConfigManager.getInstance().load();
|
ConfigManager.getInstance().load();
|
||||||
}
|
}
|
||||||
|
|
||||||
private record PlatformOverrideRequest(Platform platform) {}
|
@Json
|
||||||
|
record PlatformOverrideRequest(Platform platform) {}
|
||||||
|
|
||||||
public static void handlePlatformOverrideRequest(Context ctx) {
|
public static void handlePlatformOverrideRequest(Context ctx) {
|
||||||
try {
|
try {
|
||||||
PlatformOverrideRequest request =
|
PlatformOverrideRequest request =
|
||||||
JacksonUtils.deserialize(ctx.body(), PlatformOverrideRequest.class);
|
Jsonb.instance().type(PlatformOverrideRequest.class).fromJson(ctx.body());
|
||||||
Platform platform = request.platform();
|
Platform platform = request.platform();
|
||||||
logger.info("Overriding platform to: " + platform);
|
logger.info("Overriding platform to: " + platform);
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ public class UIInboundSubscriber extends DataChangeSubscriber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataChangeEvent(DataChangeEvent<?> event) {
|
public <T> void onDataChangeEvent(DataChangeEvent<T> event) {
|
||||||
if (event instanceof IncomingWebSocketEvent incomingWSEvent) {
|
if (event instanceof IncomingWebSocketEvent<T> incomingWSEvent) {
|
||||||
if (incomingWSEvent.propertyName.equals("userConnected")
|
if (incomingWSEvent.propertyName.equals("userConnected")
|
||||||
|| incomingWSEvent.propertyName.equals("sendFullSettings")) {
|
|| incomingWSEvent.propertyName.equals("sendFullSettings")) {
|
||||||
// Send full settings
|
// Send full settings
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.server;
|
package org.photonvision.server;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import io.avaje.json.JsonDataException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import org.photonvision.common.dataflow.DataChangeDestination;
|
import org.photonvision.common.dataflow.DataChangeDestination;
|
||||||
@@ -43,15 +43,15 @@ class UIOutboundSubscriber extends DataChangeSubscriber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataChangeEvent(DataChangeEvent event) {
|
public <T> void onDataChangeEvent(DataChangeEvent<T> event) {
|
||||||
if (event instanceof OutgoingUIEvent thisEvent) {
|
if (event instanceof OutgoingUIEvent<T> thisEvent) {
|
||||||
try {
|
try {
|
||||||
if (event.data instanceof HashMap data) {
|
if (event.data instanceof HashMap data) {
|
||||||
socketHandler.broadcastMessage(data, thisEvent.originContext);
|
socketHandler.broadcastMessage(data, thisEvent.originContext);
|
||||||
} else {
|
} else {
|
||||||
socketHandler.broadcastMessage(event.data, thisEvent.originContext);
|
socketHandler.broadcastMessage(event.data, thisEvent.originContext);
|
||||||
}
|
}
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonDataException e) {
|
||||||
logger.error("Failed to process outgoing message!", e);
|
logger.error("Failed to process outgoing message!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
|
|
||||||
package org.photonvision.common.networktables;
|
package org.photonvision.common.networktables;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import io.avaje.json.JsonException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.photonvision.common.dataflow.structures.Packet;
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
@@ -34,12 +34,11 @@ public class PacketPublisher<T> implements AutoCloseable {
|
|||||||
this.publisher = publisher;
|
this.publisher = publisher;
|
||||||
this.photonStruct = photonStruct;
|
this.photonStruct = photonStruct;
|
||||||
|
|
||||||
var mapper = new ObjectMapper();
|
|
||||||
try {
|
try {
|
||||||
this.publisher
|
this.publisher
|
||||||
.getTopic()
|
.getTopic()
|
||||||
.setProperty("message_uuid", mapper.writeValueAsString(photonStruct.getInterfaceUUID()));
|
.setProperty("message_uuid", Jsonb.instance().toJson(photonStruct.getInterfaceUUID()));
|
||||||
} catch (JsonProcessingException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
|
|
||||||
package org.photonvision.jni;
|
package org.photonvision.jni;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import io.avaje.jsonb.Json;
|
||||||
|
import io.avaje.jsonb.JsonType;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@@ -139,15 +141,18 @@ public final class CombinedRuntimeLoader {
|
|||||||
* Architecture-specific information containing file hashes for a specific CPU architecture (e.g.,
|
* Architecture-specific information containing file hashes for a specific CPU architecture (e.g.,
|
||||||
* x86-64, arm64).
|
* x86-64, arm64).
|
||||||
*/
|
*/
|
||||||
|
@Json
|
||||||
public record ArchInfo(Map<String, String> fileHashes) {}
|
public record ArchInfo(Map<String, String> fileHashes) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform-specific information containing architectures for a specific OS platform (e.g., linux,
|
* Platform-specific information containing architectures for a specific OS platform (e.g., linux,
|
||||||
* windows).
|
* windows).
|
||||||
*/
|
*/
|
||||||
|
@Json
|
||||||
public record PlatformInfo(Map<String, ArchInfo> architectures) {}
|
public record PlatformInfo(Map<String, ArchInfo> architectures) {}
|
||||||
|
|
||||||
/** Overall resource information to be serialized */
|
/** Overall resource information to be serialized */
|
||||||
|
@Json
|
||||||
public record ResourceInformation(
|
public record ResourceInformation(
|
||||||
// Combined MD5 hash of all native resource files
|
// Combined MD5 hash of all native resource files
|
||||||
String hash,
|
String hash,
|
||||||
@@ -167,10 +172,10 @@ public final class CombinedRuntimeLoader {
|
|||||||
*/
|
*/
|
||||||
public static <T> List<String> extractLibraries(Class<T> clazz, String resourceName)
|
public static <T> List<String> extractLibraries(Class<T> clazz, String resourceName)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
JsonType<ResourceInformation> jsonb = Jsonb.instance().type(ResourceInformation.class);
|
||||||
ResourceInformation resourceInfo;
|
ResourceInformation resourceInfo;
|
||||||
try (var stream = clazz.getResourceAsStream(resourceName)) {
|
try (var stream = clazz.getResourceAsStream(resourceName)) {
|
||||||
resourceInfo = mapper.readValue(stream, ResourceInformation.class);
|
resourceInfo = jsonb.fromJson(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
var platformPath = Paths.get(getPlatformPath());
|
var platformPath = Paths.get(getPlatformPath());
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation "io.javalin:javalin:$javalinVersion"
|
implementation "io.javalin:javalin:$javalinVersion"
|
||||||
|
|
||||||
implementation 'org.msgpack:msgpack-core:0.9.0'
|
implementation "org.msgpack:msgpack-core:$msgpackVersion"
|
||||||
implementation 'org.msgpack:jackson-dataformat-msgpack:0.9.0'
|
implementation "org.msgpack:jackson-dataformat-msgpack:$msgpackVersion"
|
||||||
|
|
||||||
implementation "org.wpilib.wpiutil:wpiutil-java:$wpilibVersion"
|
implementation "org.wpilib.wpiutil:wpiutil-java:$wpilibVersion"
|
||||||
implementation "org.wpilib.datalog:datalog-java:$wpilibVersion"
|
implementation "org.wpilib.datalog:datalog-java:$wpilibVersion"
|
||||||
@@ -34,9 +34,11 @@ dependencies {
|
|||||||
implementation "org.wpilib.wpiunits:wpiunits-java:$wpilibVersion"
|
implementation "org.wpilib.wpiunits:wpiunits-java:$wpilibVersion"
|
||||||
implementation wpilibTools.deps.wpilibOpenCvJava("frc" + openCVYear, openCVversion)
|
implementation wpilibTools.deps.wpilibOpenCvJava("frc" + openCVYear, openCVversion)
|
||||||
|
|
||||||
implementation group: "com.fasterxml.jackson.core", name: "jackson-annotations", version: jacksonVersion
|
|
||||||
implementation group: "com.fasterxml.jackson.core", name: "jackson-core", version: jacksonVersion
|
implementation group: "com.fasterxml.jackson.core", name: "jackson-core", version: jacksonVersion
|
||||||
implementation group: "com.fasterxml.jackson.core", name: "jackson-databind", version: jacksonVersion
|
implementation group: "com.fasterxml.jackson.core", name: "jackson-databind", version: jacksonVersion
|
||||||
|
implementation group: "io.avaje", name: "avaje-jsonb", version: avajeJsonbVersion
|
||||||
|
annotationProcessor group: "io.avaje", name: "avaje-jsonb-generator", version: avajeJsonbVersion
|
||||||
|
implementation group: "io.avaje", name: "avaje-jsonb-jackson", version: avajeJsonbVersion
|
||||||
|
|
||||||
implementation group: "org.ejml", name: "ejml-simple", version: ejmlVersion
|
implementation group: "org.ejml", name: "ejml-simple", version: ejmlVersion
|
||||||
implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion;
|
implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion;
|
||||||
@@ -53,6 +55,7 @@ dependencies {
|
|||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
systemProperty("java.awt.headless", !project.hasProperty("enableTestUi"))
|
systemProperty("java.awt.headless", !project.hasProperty("enableTestUi"))
|
||||||
|
systemProperty 'jsonb.disableAdapterSpi', 'true'
|
||||||
testLogging {
|
testLogging {
|
||||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||||
exceptionFormat = "full"
|
exceptionFormat = "full"
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ publishing {
|
|||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'
|
systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'
|
||||||
|
systemProperty 'jsonb.disableAdapterSpi', 'true'
|
||||||
testLogging {
|
testLogging {
|
||||||
events "failed"
|
events "failed"
|
||||||
exceptionFormat = "full"
|
exceptionFormat = "full"
|
||||||
@@ -149,9 +150,10 @@ dependencies {
|
|||||||
implementation "org.wpilib.wpiunits:wpiunits-java:$wpilibVersion"
|
implementation "org.wpilib.wpiunits:wpiunits-java:$wpilibVersion"
|
||||||
implementation wpilibTools.deps.wpilibOpenCvJava("frc" + openCVYear, openCVversion)
|
implementation wpilibTools.deps.wpilibOpenCvJava("frc" + openCVYear, openCVversion)
|
||||||
|
|
||||||
implementation group: "com.fasterxml.jackson.core", name: "jackson-annotations", version: jacksonVersion
|
|
||||||
implementation group: "com.fasterxml.jackson.core", name: "jackson-core", version: jacksonVersion
|
implementation group: "com.fasterxml.jackson.core", name: "jackson-core", version: jacksonVersion
|
||||||
implementation group: "com.fasterxml.jackson.core", name: "jackson-databind", version: jacksonVersion
|
implementation group: "com.fasterxml.jackson.core", name: "jackson-databind", version: jacksonVersion
|
||||||
|
implementation group: "io.avaje", name: "avaje-jsonb", version: avajeJsonbVersion
|
||||||
|
annotationProcessor group: "io.avaje", name: "avaje-jsonb-generator", version: avajeJsonbVersion
|
||||||
|
|
||||||
implementation group: "org.ejml", name: "ejml-simple", version: ejmlVersion
|
implementation group: "org.ejml", name: "ejml-simple", version: ejmlVersion
|
||||||
implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion;
|
implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion;
|
||||||
|
|||||||
3
test-resources/network-ip-addr/networkSettings.json
Normal file
3
test-resources/network-ip-addr/networkSettings.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"ntServerAddress" : "127.0.0.1"
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"teamNumber" : 9999
|
|
||||||
}
|
|
||||||
BIN
test-resources/old_configs/2026.3.4-windows/photon.sqlite
Normal file
BIN
test-resources/old_configs/2026.3.4-windows/photon.sqlite
Normal file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user