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
|
||||
uses: actions/checkout@v6
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10
|
||||
- name: Setup Node.js
|
||||
@@ -239,7 +239,7 @@ jobs:
|
||||
include:
|
||||
# - os: windows-2022
|
||||
# artifact-name: Win64
|
||||
- os: macos-26
|
||||
- os: macos-15 # TODO: Restore to macos-26 with WPILib alpha-6
|
||||
artifact-name: macOS
|
||||
- os: ubuntu-24.04
|
||||
artifact-name: Linux
|
||||
|
||||
@@ -2,7 +2,7 @@ cppHeaderFileInclude {
|
||||
\.h$
|
||||
}
|
||||
|
||||
modifiableFileExclude {
|
||||
generatedFileExclude {
|
||||
photon-lib/py/photonlibpy/generated/
|
||||
photon-targeting/src/generated/
|
||||
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)
|
||||
* [Javalin](https://javalin.io/)
|
||||
* [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)
|
||||
* [OSHI](https://github.com/oshi/oshi)
|
||||
* [QuickBuffers](https://github.com/HebiRobotics/QuickBuffers)
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
id "cpp"
|
||||
id "com.diffplug.spotless" version "8.1.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.photonvision.tools.WpilibTools' version '3.0.0-photon'
|
||||
id 'com.google.protobuf' version '0.9.5' apply false
|
||||
@@ -40,6 +40,8 @@ ext {
|
||||
openCVversion = "4.10.0-3"
|
||||
ejmlVersion = "0.43.1";
|
||||
jacksonVersion = "2.15.2";
|
||||
avajeJsonbVersion = "3.14-RC4";
|
||||
msgpackVersion = "0.9.0";
|
||||
quickbufVersion = "1.3.3";
|
||||
jacocoVersion = "0.8.14";
|
||||
|
||||
@@ -69,7 +71,7 @@ spotless {
|
||||
java {
|
||||
target fileTree('.') {
|
||||
include '**/*.java'
|
||||
exclude '**/build/**', '**/build-*/**', '**/src/generated/**'
|
||||
exclude '**/build/**', '**/build-*/**', '**/src/generated/**', "**/bin/generated-sources/**"
|
||||
}
|
||||
toggleOffOn()
|
||||
googleJavaFormat()
|
||||
|
||||
@@ -1,57 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import { PVCameraInfo } from "@/types/SettingTypes";
|
||||
import { cameraInfoFor } from "@/lib/PhotonUtils";
|
||||
import type { PVCameraInfo } from "@/types/SettingTypes";
|
||||
|
||||
const { camera } = defineProps({
|
||||
camera: {
|
||||
type: PVCameraInfo,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const { camera } = defineProps<{ camera: PVCameraInfo }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-table density="compact" :style="{ backgroundColor: 'var(--v-primary-base)' }">
|
||||
<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>{{ cameraInfoFor(camera).dev }}</td>
|
||||
<td>{{ camera.dev }}</td>
|
||||
</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>{{ cameraInfoFor(camera).name }}</td>
|
||||
<td>{{ camera.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type:</td>
|
||||
<td v-if="camera.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
||||
<td v-else-if="camera.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
||||
<td v-else-if="camera.PVFileCameraInfo" class="mb-3">File Camera</td>
|
||||
<td v-if="camera.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||
<td v-else-if="camera.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||
<td v-else-if="camera.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||
<td v-else>Unidentified Camera Type</td>
|
||||
</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>{{ cameraInfoFor(camera).baseName }}</td>
|
||||
<td>{{ camera.baseName }}</td>
|
||||
</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>{{ cameraInfoFor(camera).vendorId }}</td>
|
||||
<td>{{ camera.vendorId }}</td>
|
||||
</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>{{ cameraInfoFor(camera).productId }}</td>
|
||||
<td>{{ camera.productId }}</td>
|
||||
</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 style="word-break: break-all">{{ cameraInfoFor(camera).path }}</td>
|
||||
<td style="word-break: break-all">{{ camera.path }}</td>
|
||||
</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 style="word-break: break-all">{{ cameraInfoFor(camera).uniquePath }}</td>
|
||||
<td style="word-break: break-all">{{ camera.uniquePath }}</td>
|
||||
</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>{{ cameraInfoFor(camera).otherPaths }}</td>
|
||||
<td>{{ camera.otherPaths }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { PVCameraInfo } from "@/types/SettingTypes";
|
||||
import { cameraInfoFor } from "@/lib/PhotonUtils";
|
||||
import type { PVCameraInfo } from "@/types/SettingTypes";
|
||||
|
||||
function isEqual<T>(a: T, b: T): boolean {
|
||||
if (a === b) {
|
||||
@@ -16,16 +15,7 @@ function isEqual<T>(a: T, b: T): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
const { saved, current } = defineProps({
|
||||
saved: {
|
||||
type: PVCameraInfo,
|
||||
required: true
|
||||
},
|
||||
current: {
|
||||
type: PVCameraInfo,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const { saved, current } = defineProps<{ saved: PVCameraInfo; current: PVCameraInfo }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -38,79 +28,70 @@ const { saved, current } = defineProps({
|
||||
<th>Current</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="cameraInfoFor(saved).dev !== undefined && cameraInfoFor(saved).dev !== null"
|
||||
:class="cameraInfoFor(saved).dev !== cameraInfoFor(current).dev ? 'mismatch' : ''"
|
||||
v-if="'dev' in saved && 'dev' in current && saved.dev !== null"
|
||||
:class="saved.dev !== current.dev ? 'mismatch' : ''"
|
||||
>
|
||||
<td>Device Number:</td>
|
||||
<td>{{ cameraInfoFor(saved).dev }}</td>
|
||||
<td>{{ cameraInfoFor(current).dev }}</td>
|
||||
<td>{{ saved.dev }}</td>
|
||||
<td>{{ current.dev }}</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="cameraInfoFor(saved).name !== undefined && cameraInfoFor(saved).name !== null"
|
||||
:class="cameraInfoFor(saved).name !== cameraInfoFor(current).name ? 'mismatch' : ''"
|
||||
>
|
||||
<tr v-if="saved.name !== null" :class="saved.name !== current.name ? 'mismatch' : ''">
|
||||
<td>Name:</td>
|
||||
<td>{{ cameraInfoFor(saved).name }}</td>
|
||||
<td>{{ cameraInfoFor(current).name }}</td>
|
||||
<td>{{ saved.name }}</td>
|
||||
<td>{{ current.name }}</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="cameraInfoFor(saved).baseName !== undefined && cameraInfoFor(saved).baseName !== null"
|
||||
:class="cameraInfoFor(saved).baseName !== cameraInfoFor(current).baseName ? 'mismatch' : ''"
|
||||
v-if="'baseName' in saved && 'baseName' in current && saved.baseName !== null"
|
||||
:class="saved.baseName !== current.baseName ? 'mismatch' : ''"
|
||||
>
|
||||
<td>Base Name:</td>
|
||||
<td>{{ cameraInfoFor(saved).baseName }}</td>
|
||||
<td>{{ cameraInfoFor(current).baseName }}</td>
|
||||
<td>{{ saved.baseName }}</td>
|
||||
<td>{{ current.baseName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type:</td>
|
||||
<td v-if="saved.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
||||
<td v-else-if="saved.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
||||
<td v-else-if="saved.PVFileCameraInfo" class="mb-3">File Camera</td>
|
||||
<td v-if="saved.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||
<td v-else-if="saved.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||
<td v-else-if="saved.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||
<td v-else>Unidentified Camera Type</td>
|
||||
<td v-if="current.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
||||
<td v-else-if="current.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
||||
<td v-else-if="current.PVFileCameraInfo" class="mb-3">File Camera</td>
|
||||
<td v-if="current.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||
<td v-else-if="current.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||
<td v-else-if="current.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||
<td v-else>Unidentified Camera Type</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="cameraInfoFor(saved).vendorId !== undefined && cameraInfoFor(saved).vendorId !== null"
|
||||
:class="cameraInfoFor(saved).vendorId !== cameraInfoFor(current).vendorId ? 'mismatch' : ''"
|
||||
v-if="'vendorId' in saved && 'vendorId' in current && saved.vendorId !== null"
|
||||
:class="saved.vendorId !== current.vendorId ? 'mismatch' : ''"
|
||||
>
|
||||
<td>Vendor ID:</td>
|
||||
<td>{{ cameraInfoFor(saved).vendorId }}</td>
|
||||
<td>{{ cameraInfoFor(current).vendorId }}</td>
|
||||
<td>{{ saved.vendorId }}</td>
|
||||
<td>{{ current.vendorId }}</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="cameraInfoFor(saved).productId !== undefined && cameraInfoFor(saved).productId !== null"
|
||||
:class="cameraInfoFor(saved).productId !== cameraInfoFor(current).productId ? 'mismatch' : ''"
|
||||
v-if="'productId' in saved && 'productId' in current && saved.productId !== null"
|
||||
:class="saved.productId !== current.productId ? 'mismatch' : ''"
|
||||
>
|
||||
<td>Product ID:</td>
|
||||
<td>{{ cameraInfoFor(saved).productId }}</td>
|
||||
<td>{{ cameraInfoFor(current).productId }}</td>
|
||||
<td>{{ saved.productId }}</td>
|
||||
<td>{{ current.productId }}</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="cameraInfoFor(saved).path !== undefined && cameraInfoFor(saved).path !== null"
|
||||
:class="cameraInfoFor(saved).path !== cameraInfoFor(current).path ? 'mismatch' : ''"
|
||||
>
|
||||
<tr v-if="saved.path !== null" :class="saved.path !== current.path ? 'mismatch' : ''">
|
||||
<td>Path:</td>
|
||||
<td style="word-break: break-all">{{ cameraInfoFor(saved).path }}</td>
|
||||
<td style="word-break: break-all">{{ cameraInfoFor(current).path }}</td>
|
||||
<td style="word-break: break-all">{{ saved.path }}</td>
|
||||
<td style="word-break: break-all">{{ current.path }}</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="cameraInfoFor(saved).uniquePath !== undefined && cameraInfoFor(saved).uniquePath !== null"
|
||||
:class="cameraInfoFor(saved).uniquePath !== cameraInfoFor(current).uniquePath ? 'mismatch' : ''"
|
||||
>
|
||||
<tr v-if="saved.uniquePath !== null" :class="saved.uniquePath !== current.uniquePath ? 'mismatch' : ''">
|
||||
<td>Unique Path:</td>
|
||||
<td style="word-break: break-all">{{ cameraInfoFor(saved).uniquePath }}</td>
|
||||
<td style="word-break: break-all">{{ cameraInfoFor(current).uniquePath }}</td>
|
||||
<td style="word-break: break-all">{{ saved.uniquePath }}</td>
|
||||
<td style="word-break: break-all">{{ current.uniquePath }}</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="cameraInfoFor(saved).otherPaths !== undefined && cameraInfoFor(saved).otherPaths !== null"
|
||||
:class="isEqual(cameraInfoFor(saved).otherPaths, cameraInfoFor(current).otherPaths) ? '' : 'mismatch'"
|
||||
v-if="'otherPaths' in saved && 'otherPaths' in current && saved.otherPaths !== null"
|
||||
:class="isEqual(saved.otherPaths, current.otherPaths) ? '' : 'mismatch'"
|
||||
>
|
||||
<td>Other Paths:</td>
|
||||
<td>{{ cameraInfoFor(saved).otherPaths }}</td>
|
||||
<td>{{ cameraInfoFor(current).otherPaths }}</td>
|
||||
<td>{{ saved.otherPaths }}</td>
|
||||
<td>{{ current.otherPaths }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
@@ -163,7 +163,7 @@ const interactiveCols = computed(() =>
|
||||
/>
|
||||
<pv-switch
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.blockForFrames"
|
||||
:disabled="!useCameraSettingsStore().currentCameraSettings.matchedCameraInfo.PVUsbCameraInfo"
|
||||
:disabled="useCameraSettingsStore().currentCameraSettings.matchedCameraInfo.type !== 'PVUsbCameraInfo'"
|
||||
label="Low Latency Mode"
|
||||
: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."
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import type { PVCameraInfo, Resolution } from "@/types/SettingTypes";
|
||||
import type { Resolution } from "@/types/SettingTypes";
|
||||
import axios, { type AxiosRequestConfig } from "axios";
|
||||
|
||||
export const resolutionsAreEqual = (a: Resolution, b: Resolution) => {
|
||||
@@ -109,23 +109,3 @@ export const axiosPost = async (
|
||||
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,10 +116,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
||||
inputPort: d.inputStreamPort,
|
||||
outputPort: d.outputStreamPort
|
||||
},
|
||||
validVideoFormats: Object.entries(d.videoFormatList)
|
||||
.sort(([firstKey], [secondKey]) => parseInt(firstKey) - parseInt(secondKey))
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.map<VideoFormat>(([k, v], i) => ({
|
||||
validVideoFormats: d.videoFormatList.map((v, i) => ({
|
||||
resolution: {
|
||||
width: v.width,
|
||||
height: v.height
|
||||
|
||||
@@ -75,46 +75,29 @@ export type ConfigurableNetworkSettings = Omit<
|
||||
"canManage" | "networkInterfaceNames" | "networkingDisabled"
|
||||
>;
|
||||
|
||||
export interface PVCameraInfoBase {
|
||||
/*
|
||||
Huge hack. In Jackson, this is set based on the underlying type -- this
|
||||
then maps to one of the 3 subclasses here below. Not sure how to best deal with this.
|
||||
*/
|
||||
cameraTypename: "PVUsbCameraInfo" | "PVCSICameraInfo" | "PVFileCameraInfo";
|
||||
interface PVCameraInfoBase {
|
||||
type: "PVUsbCameraInfo" | "PVCSICameraInfo" | "PVFileCameraInfo";
|
||||
path: string;
|
||||
name: string;
|
||||
uniquePath: string;
|
||||
}
|
||||
|
||||
export interface PVUsbCameraInfo {
|
||||
export interface PVUsbCameraInfo extends PVCameraInfoBase {
|
||||
type: "PVUsbCameraInfo";
|
||||
dev: number;
|
||||
name: string;
|
||||
otherPaths: string[];
|
||||
path: string;
|
||||
vendorId: 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;
|
||||
path: string;
|
||||
|
||||
// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
|
||||
uniquePath: string;
|
||||
}
|
||||
export interface PVFileCameraInfo {
|
||||
path: string;
|
||||
name: string;
|
||||
|
||||
// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
|
||||
uniquePath: string;
|
||||
export interface PVFileCameraInfo extends PVCameraInfoBase {
|
||||
type: "PVFileCameraInfo";
|
||||
}
|
||||
|
||||
// This camera info will only ever hold one of its members - the others should be undefined.
|
||||
export class PVCameraInfo {
|
||||
PVUsbCameraInfo: PVUsbCameraInfo | undefined;
|
||||
PVCSICameraInfo: PVCSICameraInfo | undefined;
|
||||
PVFileCameraInfo: PVFileCameraInfo | undefined;
|
||||
}
|
||||
export type PVCameraInfo = PVUsbCameraInfo | PVCSICameraInfo | PVFileCameraInfo;
|
||||
|
||||
export interface VsmState {
|
||||
disabledConfigs: WebsocketCameraSettingsUpdate[];
|
||||
@@ -439,14 +422,11 @@ export const PlaceholderCameraSettings: UiCameraConfiguration = reactive({
|
||||
minWhiteBalanceTemp: 2000,
|
||||
maxWhiteBalanceTemp: 10000,
|
||||
matchedCameraInfo: {
|
||||
PVFileCameraInfo: {
|
||||
type: "PVFileCameraInfo",
|
||||
name: "Foobar",
|
||||
path: "/dev/foobar",
|
||||
uniquePath: "/dev/foobar2"
|
||||
},
|
||||
PVCSICameraInfo: undefined,
|
||||
PVUsbCameraInfo: undefined
|
||||
},
|
||||
fpsLimit: -1,
|
||||
isEnabled: true,
|
||||
isConnected: true,
|
||||
|
||||
@@ -30,9 +30,7 @@ export interface WebsocketNumberPair {
|
||||
second: number;
|
||||
}
|
||||
|
||||
export type WebsocketVideoFormat = Record<
|
||||
number,
|
||||
{
|
||||
export type WebsocketVideoFormat = {
|
||||
fps: number;
|
||||
height: number;
|
||||
width: number;
|
||||
@@ -43,8 +41,7 @@ export type WebsocketVideoFormat = Record<
|
||||
verticalFOV?: number;
|
||||
standardDeviation?: number;
|
||||
mean?: number;
|
||||
}
|
||||
>;
|
||||
}[];
|
||||
|
||||
// Companion to UICameraConfiguration in Java
|
||||
export interface WebsocketCameraSettingsUpdate {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||
import { computed, inject, ref } from "vue";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import { PlaceholderCameraSettings, PVCameraInfo } from "@/types/SettingTypes";
|
||||
import { axiosPost, getResolutionString, cameraInfoFor } from "@/lib/PhotonUtils";
|
||||
import { PlaceholderCameraSettings, type PVCameraInfo } from "@/types/SettingTypes";
|
||||
import { axiosPost, getResolutionString } from "@/lib/PhotonUtils";
|
||||
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
||||
import PvDeleteModal from "@/components/common/pv-delete-modal.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 => {
|
||||
if (!uniquePath) return false;
|
||||
return (
|
||||
useStateStore().vsmState.allConnectedCameras.find((it) => cameraInfoFor(it).uniquePath === uniquePath) !== undefined
|
||||
);
|
||||
return useStateStore().vsmState.allConnectedCameras.find((it) => it.uniquePath === uniquePath) !== undefined;
|
||||
};
|
||||
|
||||
const unmatchedCameras = computed(() => {
|
||||
const activeVmPaths = Object.values(useCameraSettingsStore().cameras).map(
|
||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath
|
||||
);
|
||||
const disabledVmPaths = useStateStore().vsmState.disabledConfigs.map(
|
||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath
|
||||
);
|
||||
const activeVmPaths = Object.values(useCameraSettingsStore().cameras).map((it) => it.matchedCameraInfo.uniquePath);
|
||||
const disabledVmPaths = useStateStore().vsmState.disabledConfigs.map((it) => it.matchedCameraInfo.uniquePath);
|
||||
|
||||
return useStateStore().vsmState.allConnectedCameras.filter(
|
||||
(it) =>
|
||||
!activeVmPaths.includes(cameraInfoFor(it).uniquePath) && !disabledVmPaths.includes(cameraInfoFor(it).uniquePath)
|
||||
(it) => !activeVmPaths.includes(it.uniquePath) && !disabledVmPaths.includes(it.uniquePath)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -85,8 +78,8 @@ const activeVisionModules = computed(() =>
|
||||
// Display connected cameras first
|
||||
.sort(
|
||||
(first, second) =>
|
||||
(cameraConnected(cameraInfoFor(second.matchedCameraInfo).uniquePath) ? 1 : 0) -
|
||||
(cameraConnected(cameraInfoFor(first.matchedCameraInfo).uniquePath) ? 1 : 0)
|
||||
(cameraConnected(second.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 => {
|
||||
if (!info) {
|
||||
return {
|
||||
PVFileCameraInfo: undefined,
|
||||
PVCSICameraInfo: undefined,
|
||||
PVUsbCameraInfo: undefined
|
||||
type: "PVFileCameraInfo",
|
||||
path: "",
|
||||
name: "",
|
||||
uniquePath: ""
|
||||
};
|
||||
}
|
||||
return (
|
||||
useStateStore().vsmState.allConnectedCameras.find(
|
||||
(it) => cameraInfoFor(it).uniquePath === cameraInfoFor(info).uniquePath
|
||||
) || {
|
||||
PVFileCameraInfo: undefined,
|
||||
PVCSICameraInfo: undefined,
|
||||
PVUsbCameraInfo: undefined
|
||||
useStateStore().vsmState.allConnectedCameras.find((it) => it.uniquePath === info.uniquePath) || {
|
||||
type: "PVFileCameraInfo",
|
||||
path: "",
|
||||
name: "",
|
||||
uniquePath: ""
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -135,12 +128,11 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
class="pr-0"
|
||||
>
|
||||
<v-card color="surface" class="rounded-12">
|
||||
<v-card-title>{{ cameraInfoFor(module.matchedCameraInfo).name }}</v-card-title>
|
||||
<v-card-subtitle v-if="!cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)"
|
||||
<v-card-title>{{ module.matchedCameraInfo.name }}</v-card-title>
|
||||
<v-card-subtitle v-if="!cameraConnected(module.matchedCameraInfo.uniquePath)"
|
||||
>Status: <span class="inactive-status">Disconnected</span></v-card-subtitle
|
||||
>
|
||||
<v-card-subtitle
|
||||
v-else-if="cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) && !module.mismatch"
|
||||
<v-card-subtitle v-else-if="cameraConnected(module.matchedCameraInfo.uniquePath) && !module.mismatch"
|
||||
>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>
|
||||
@@ -149,7 +141,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
<tbody>
|
||||
<tr
|
||||
v-if="
|
||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) &&
|
||||
cameraConnected(module.matchedCameraInfo.uniquePath) &&
|
||||
useStateStore().backendResults[module.uniqueName]
|
||||
"
|
||||
>
|
||||
@@ -191,7 +183,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
</tbody>
|
||||
</v-table>
|
||||
<div
|
||||
v-if="cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)"
|
||||
v-if="cameraConnected(module.matchedCameraInfo.uniquePath)"
|
||||
:id="`stream-container-${index}`"
|
||||
class="d-flex flex-column justify-center align-center mt-3"
|
||||
style="height: 250px"
|
||||
@@ -210,12 +202,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
color="buttonPassive"
|
||||
style="width: 100%"
|
||||
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||
@click="
|
||||
setCameraView(
|
||||
module.matchedCameraInfo,
|
||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)
|
||||
)
|
||||
"
|
||||
@click="setCameraView(module.matchedCameraInfo, cameraConnected(module.matchedCameraInfo.uniquePath))"
|
||||
>
|
||||
<span>Details</span>
|
||||
</v-btn>
|
||||
@@ -292,7 +279,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Connected</td>
|
||||
<td>{{ cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) }}</td>
|
||||
<td>{{ cameraConnected(module.matchedCameraInfo.uniquePath) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
@@ -304,12 +291,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
color="buttonPassive"
|
||||
style="width: 100%"
|
||||
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||
@click="
|
||||
setCameraView(
|
||||
module.matchedCameraInfo,
|
||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)
|
||||
)
|
||||
"
|
||||
@click="setCameraView(module.matchedCameraInfo, cameraConnected(module.matchedCameraInfo.uniquePath))"
|
||||
>
|
||||
<span>Details</span>
|
||||
</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-card class="pr-0 rounded-12" color="surface">
|
||||
<v-card-title>
|
||||
<span v-if="camera.PVUsbCameraInfo">USB Camera:</span>
|
||||
<span v-else-if="camera.PVCSICameraInfo">CSI Camera:</span>
|
||||
<span v-else-if="camera.PVFileCameraInfo">File Camera:</span>
|
||||
<span v-if="camera.type === 'PVUsbCameraInfo'">USB Camera:</span>
|
||||
<span v-else-if="camera.type === 'PVCSICameraInfo'">CSI Camera:</span>
|
||||
<span v-else-if="camera.type === 'PVFileCameraInfo'">File 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-subtitle>Status: Unassigned</v-card-subtitle>
|
||||
<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 class="pt-0">
|
||||
<v-row>
|
||||
@@ -413,7 +395,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
<v-dialog v-model="viewingDetails" max-width="800">
|
||||
<v-card v-if="viewingCamera[0] !== null" flat color="surface">
|
||||
<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-icon size="x-large">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
@@ -423,9 +405,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
</v-card-text>
|
||||
<v-card-text
|
||||
v-else-if="
|
||||
activeVisionModules.find(
|
||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath === cameraInfoFor(viewingCamera[0]).uniquePath
|
||||
)?.mismatch
|
||||
activeVisionModules.find((it) => it.matchedCameraInfo.uniquePath === viewingCamera[0]?.uniquePath)?.mismatch
|
||||
"
|
||||
>
|
||||
<v-alert
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -36,6 +34,7 @@ import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
||||
import org.photonvision.vision.processes.PipelineManager;
|
||||
import org.wpilib.vision.camera.UsbCameraInfo;
|
||||
|
||||
@Json
|
||||
public class CameraConfiguration {
|
||||
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...
|
||||
|
||||
// Ignore the pipes, as we serialize them to their own column to hack around
|
||||
// polymorphic lists
|
||||
@JsonIgnore public List<CVPipelineSettings> pipelineSettings = new ArrayList<>();
|
||||
public List<CVPipelineSettings> pipelineSettings = new ArrayList<>();
|
||||
|
||||
@JsonIgnore
|
||||
public DriverModePipelineSettings driveModeSettings = new DriverModePipelineSettings();
|
||||
|
||||
public CameraConfiguration(PVCameraInfo cameraInfo, String uniqueName, String nickname) {
|
||||
@@ -78,24 +74,22 @@ public class CameraConfiguration {
|
||||
logger.debug("Creating USB camera configuration for " + this.toShortString());
|
||||
}
|
||||
|
||||
// Shiny new constructor
|
||||
@JsonCreator
|
||||
// JSON Constructor (can't be marked with @Json.Creator due to public fields that aren't part of
|
||||
// the parameters)
|
||||
public CameraConfiguration(
|
||||
@JsonProperty("uniqueName") String uniqueName,
|
||||
@JsonProperty("matchedCameraInfo") PVCameraInfo matchedCameraInfo,
|
||||
@JsonProperty("nickname") String nickname,
|
||||
@JsonProperty("deactivated") boolean deactivated,
|
||||
@JsonProperty("cameraQuirks") QuirkyCamera cameraQuirks,
|
||||
@JsonProperty("FOV") double FOV,
|
||||
@JsonProperty("calibrations") List<CameraCalibrationCoefficients> calibrations,
|
||||
@JsonProperty("currentPipelineIndex") int currentPipelineIndex) {
|
||||
String uniqueName,
|
||||
PVCameraInfo matchedCameraInfo,
|
||||
String nickname,
|
||||
boolean deactivated,
|
||||
QuirkyCamera cameraQuirks,
|
||||
double FOV,
|
||||
int currentPipelineIndex) {
|
||||
this.uniqueName = uniqueName;
|
||||
this.matchedCameraInfo = matchedCameraInfo;
|
||||
this.nickname = nickname;
|
||||
this.deactivated = deactivated;
|
||||
this.cameraQuirks = cameraQuirks;
|
||||
this.FOV = FOV;
|
||||
this.calibrations = calibrations != null ? calibrations : new ArrayList<>();
|
||||
this.currentPipelineIndex = currentPipelineIndex;
|
||||
}
|
||||
|
||||
@@ -120,14 +114,14 @@ public class CameraConfiguration {
|
||||
PVCameraInfo matchedCameraInfo;
|
||||
|
||||
/** Legacy constructor for compat with 2024.3.1 */
|
||||
@JsonCreator
|
||||
@Json.Creator
|
||||
public LegacyCameraConfigStruct(
|
||||
@JsonProperty("baseName") String baseName,
|
||||
@JsonProperty("path") String path,
|
||||
@JsonProperty("otherPaths") String[] otherPaths,
|
||||
@JsonProperty("cameraType") CameraType cameraType,
|
||||
@JsonProperty("usbVID") int usbVID,
|
||||
@JsonProperty("usbPID") int usbPID) {
|
||||
String baseName,
|
||||
String path,
|
||||
String[] otherPaths,
|
||||
CameraType cameraType,
|
||||
int usbVID,
|
||||
int usbPID) {
|
||||
if (cameraType == CameraType.UsbCamera) {
|
||||
this.matchedCameraInfo =
|
||||
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
|
||||
* none exists yet. If we are replacing an existing calibration, the old one will be "released"
|
||||
* and the underlying data matrices will become invalid.
|
||||
* Replace a calibration in our list with the same resolution with a new one, or add it if none
|
||||
* exists yet. If we are replacing an existing calibration, the old one will be "released" and the
|
||||
* underlying data matrices will become invalid.
|
||||
*
|
||||
* @param calibration The calibration to add.
|
||||
*/
|
||||
public void addCalibration(CameraCalibrationCoefficients calibration) {
|
||||
logger.info("adding calibration " + calibration.unrotatedImageSize);
|
||||
logger.info("adding calibration " + calibration.resolution);
|
||||
calibrations.stream()
|
||||
.filter(it -> it.unrotatedImageSize.equals(calibration.unrotatedImageSize))
|
||||
.filter(it -> it.resolution.equals(calibration.resolution))
|
||||
.findAny()
|
||||
.ifPresent(
|
||||
(it) -> {
|
||||
@@ -194,12 +188,12 @@ public class CameraConfiguration {
|
||||
* Remove a calibration from our list. If found, the calibration will be "released". If not found,
|
||||
* no-op.
|
||||
*
|
||||
* @param unrotatedImageSize The resolution to remove.
|
||||
* @param resolution The resolution to remove.
|
||||
*/
|
||||
public void removeCalibration(Size unrotatedImageSize) {
|
||||
logger.info("deleting calibration " + unrotatedImageSize);
|
||||
public void removeCalibration(Size resolution) {
|
||||
logger.info("deleting calibration " + resolution);
|
||||
calibrations.stream()
|
||||
.filter(it -> it.unrotatedImageSize.equals(unrotatedImageSize))
|
||||
.filter(it -> it.resolution.equals(resolution))
|
||||
.findAny()
|
||||
.ifPresent(
|
||||
(it) -> {
|
||||
@@ -215,7 +209,6 @@ public class CameraConfiguration {
|
||||
*
|
||||
* <p>This represents our best guess at an immutable path to detect a camera at.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getDevicePath() {
|
||||
return matchedCameraInfo.uniquePath();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import io.avaje.json.JsonException;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
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.Logger;
|
||||
import org.photonvision.common.util.file.FileUtils;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
import org.photonvision.vision.processes.VisionSource;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
||||
@@ -233,14 +235,15 @@ public class ConfigManager {
|
||||
Path.of(getModelsDirectory().toString(), "photonvision-object-detection-models.json")
|
||||
.toFile();
|
||||
try {
|
||||
JacksonUtils.serialize(
|
||||
tempProperties.toPath(), this.getConfig().neuralNetworkPropertyManager());
|
||||
Jsonb.instance()
|
||||
.type(NeuralNetworkModelsSettings.class)
|
||||
.toJson(this.getConfig().getNeuralNetworkProperties(), new FileWriter(tempProperties));
|
||||
ZipUtil.pack(getModelsDirectory(), out);
|
||||
// Now delete the tempProperties
|
||||
if (tempProperties.exists()) {
|
||||
Files.delete(tempProperties.toPath());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch (IOException | IllegalStateException | JsonException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -17,47 +17,49 @@
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.hardware.statusLED.StatusLEDType;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Json
|
||||
public class HardwareConfig {
|
||||
public final String deviceName;
|
||||
public String deviceName;
|
||||
|
||||
// LED control
|
||||
public final ArrayList<Integer> ledPins;
|
||||
public final boolean ledsCanDim;
|
||||
public final ArrayList<Integer> ledBrightnessRange;
|
||||
public final int ledPWMFrequency;
|
||||
public final StatusLEDType statusLEDType;
|
||||
public List<Integer> ledPins;
|
||||
public boolean ledsCanDim;
|
||||
public List<Integer> ledBrightnessRange;
|
||||
public int ledPWMFrequency;
|
||||
public StatusLEDType statusLEDType;
|
||||
|
||||
@JsonAlias("statusRGBPins")
|
||||
public final ArrayList<Integer> statusLEDPins;
|
||||
// MIGRATION: 2026
|
||||
@Json.Alias("statusRGBPins")
|
||||
public List<Integer> statusLEDPins;
|
||||
|
||||
@JsonAlias("statusRGBActiveHigh")
|
||||
public final boolean statusLEDActiveHigh;
|
||||
// MIGRATION: 2026
|
||||
@Json.Alias("statusRGBActiveHigh")
|
||||
public boolean statusLEDActiveHigh;
|
||||
|
||||
// Custom GPIO
|
||||
public final String getGPIOCommand;
|
||||
public final String setGPIOCommand;
|
||||
public final String setPWMCommand;
|
||||
public final String setPWMFrequencyCommand;
|
||||
public final String releaseGPIOCommand;
|
||||
public String getGPIOCommand;
|
||||
public String setGPIOCommand;
|
||||
public String setPWMCommand;
|
||||
public String setPWMFrequencyCommand;
|
||||
public String releaseGPIOCommand;
|
||||
|
||||
// Device stuff
|
||||
public final String restartHardwareCommand;
|
||||
public final double vendorFOV; // -1 for unmanaged
|
||||
public String restartHardwareCommand;
|
||||
public double vendorFOV; // -1 for unmanaged
|
||||
|
||||
public HardwareConfig(
|
||||
String deviceName,
|
||||
ArrayList<Integer> ledPins,
|
||||
List<Integer> ledPins,
|
||||
boolean ledsCanDim,
|
||||
ArrayList<Integer> ledBrightnessRange,
|
||||
List<Integer> ledBrightnessRange,
|
||||
int ledPwmFrequency,
|
||||
StatusLEDType statusLEDType,
|
||||
ArrayList<Integer> statusLEDPins,
|
||||
List<Integer> statusLEDPins,
|
||||
boolean statusLEDActiveHigh,
|
||||
String getGPIOCommand,
|
||||
String setGPIOCommand,
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
|
||||
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.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
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.Logger;
|
||||
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.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||
import org.wpilib.vision.apriltag.AprilTagFields;
|
||||
@@ -126,14 +126,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
AprilTagFieldLayout atfl = null;
|
||||
|
||||
if (hardwareConfigFile.exists()) {
|
||||
try {
|
||||
hardwareConfig =
|
||||
JacksonUtils.deserialize(hardwareConfigFile.toPath(), HardwareConfig.class);
|
||||
try (var stream = new FileInputStream(hardwareConfigFile)) {
|
||||
hardwareConfig = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
|
||||
if (hardwareConfig == null) {
|
||||
logger.error("Could not deserialize hardware config! Loading defaults");
|
||||
hardwareConfig = new HardwareConfig();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | IllegalStateException | JsonException e) {
|
||||
logger.error("Could not deserialize hardware config! Loading defaults");
|
||||
hardwareConfig = new HardwareConfig();
|
||||
}
|
||||
@@ -143,14 +142,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
}
|
||||
|
||||
if (hardwareSettingsFile.exists()) {
|
||||
try {
|
||||
hardwareSettings =
|
||||
JacksonUtils.deserialize(hardwareSettingsFile.toPath(), HardwareSettings.class);
|
||||
try (var stream = new FileInputStream(hardwareSettingsFile)) {
|
||||
hardwareSettings = Jsonb.instance().type(HardwareSettings.class).fromJson(stream);
|
||||
if (hardwareSettings == null) {
|
||||
logger.error("Could not deserialize hardware settings! Loading defaults");
|
||||
hardwareSettings = new HardwareSettings();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | IllegalStateException | JsonException e) {
|
||||
logger.error("Could not deserialize hardware settings! Loading defaults");
|
||||
hardwareSettings = new HardwareSettings();
|
||||
}
|
||||
@@ -160,13 +158,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
}
|
||||
|
||||
if (networkConfigFile.exists()) {
|
||||
try {
|
||||
networkConfig = JacksonUtils.deserialize(networkConfigFile.toPath(), NetworkConfig.class);
|
||||
try (var stream = new FileInputStream(networkConfigFile)) {
|
||||
networkConfig = Jsonb.instance().type(NetworkConfig.class).fromJson(stream);
|
||||
if (networkConfig == null) {
|
||||
logger.error("Could not deserialize network config! Loading defaults");
|
||||
networkConfig = new NetworkConfig();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | IllegalStateException | JsonException e) {
|
||||
logger.error("Could not deserialize network config! Loading defaults");
|
||||
networkConfig = new NetworkConfig();
|
||||
}
|
||||
@@ -184,13 +182,12 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
}
|
||||
|
||||
if (apriltagFieldLayoutFile.exists()) {
|
||||
try {
|
||||
atfl =
|
||||
JacksonUtils.deserialize(apriltagFieldLayoutFile.toPath(), AprilTagFieldLayout.class);
|
||||
try (var stream = new FileInputStream(apriltagFieldLayoutFile)) {
|
||||
atfl = Jsonb.instance().type(AprilTagFieldLayout.class).fromJson(stream);
|
||||
if (atfl == 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);
|
||||
atfl = null; // not required, nice to be explicit
|
||||
}
|
||||
@@ -227,14 +224,14 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
// Delete old configs
|
||||
FileUtils.deleteDirectory(camerasFolder.toPath());
|
||||
|
||||
try {
|
||||
JacksonUtils.serialize(networkConfigFile.toPath(), config.getNetworkConfig());
|
||||
} catch (IOException e) {
|
||||
try (var stream = new FileOutputStream(networkConfigFile)) {
|
||||
Jsonb.instance().type(NetworkConfig.class).toJson(config.getNetworkConfig(), stream);
|
||||
} catch (IOException | IllegalStateException | JsonException e) {
|
||||
logger.error("Could not save network config!", e);
|
||||
}
|
||||
try {
|
||||
JacksonUtils.serialize(hardwareSettingsFile.toPath(), config.getHardwareSettings());
|
||||
} catch (IOException e) {
|
||||
try (var stream = new FileOutputStream(hardwareSettingsFile)) {
|
||||
Jsonb.instance().type(HardwareSettings.class).toJson(config.getHardwareSettings(), stream);
|
||||
} catch (IOException | IllegalStateException | JsonException e) {
|
||||
logger.error("Could not save hardware config!", e);
|
||||
}
|
||||
|
||||
@@ -249,33 +246,11 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
subdir.toFile().mkdirs();
|
||||
}
|
||||
|
||||
try {
|
||||
JacksonUtils.serialize(Path.of(subdir.toString(), "config.json"), camConfig);
|
||||
} catch (IOException e) {
|
||||
try (var stream = new FileOutputStream(Path.of(subdir.toString(), "config.json").toFile())) {
|
||||
Jsonb.instance().type(CameraConfiguration.class).toJson(camConfig, stream);
|
||||
} catch (IOException | IllegalStateException | JsonException 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!");
|
||||
return false; // TODO, deal with this. Do I need to?
|
||||
@@ -289,11 +264,9 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
for (var subdir : subdirectories) {
|
||||
var cameraConfigPath = Path.of(subdir.toString(), "config.json");
|
||||
CameraConfiguration loadedConfig = null;
|
||||
try {
|
||||
loadedConfig =
|
||||
JacksonUtils.deserialize(
|
||||
cameraConfigPath.toAbsolutePath(), CameraConfiguration.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
try (var stream = new FileInputStream(cameraConfigPath.toFile())) {
|
||||
loadedConfig = Jsonb.instance().type(CameraConfiguration.class).fromJson(stream);
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
logger.error("Camera config deserialization failed!", e);
|
||||
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?
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -17,16 +17,17 @@
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.avaje.jsonb.Json;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.networking.NetworkMode;
|
||||
|
||||
@Json
|
||||
public class NetworkConfig {
|
||||
// Can be an integer team number, or an IP address
|
||||
// MIGRATION: 2023
|
||||
@Json.Alias("teamNumber")
|
||||
public String ntServerAddress = "0";
|
||||
|
||||
public NetworkMode connectionType = NetworkMode.DHCP;
|
||||
public String staticIp = "";
|
||||
public String hostname = "photonvision";
|
||||
@@ -34,8 +35,8 @@ public class NetworkConfig {
|
||||
public boolean shouldManage;
|
||||
public boolean shouldPublishProto = false;
|
||||
|
||||
@JsonIgnore public static final String NM_IFACE_STRING = "${interface}";
|
||||
@JsonIgnore public static final String NM_IP_STRING = "${ipaddr}";
|
||||
public static final String NM_IFACE_STRING = "${interface}";
|
||||
public static final String NM_IP_STRING = "${ipaddr}";
|
||||
|
||||
public String networkManagerIface = "";
|
||||
// TODO: remove these strings if no longer needed
|
||||
@@ -50,19 +51,17 @@ public class NetworkConfig {
|
||||
setShouldManage(deviceCanManageNetwork());
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public NetworkConfig(
|
||||
@JsonProperty("ntServerAddress") @JsonAlias({"ntServerAddress", "teamNumber"})
|
||||
String ntServerAddress,
|
||||
@JsonProperty("connectionType") NetworkMode connectionType,
|
||||
@JsonProperty("staticIp") String staticIp,
|
||||
@JsonProperty("hostname") String hostname,
|
||||
@JsonProperty("runNTServer") boolean runNTServer,
|
||||
@JsonProperty("shouldManage") boolean shouldManage,
|
||||
@JsonProperty("shouldPublishProto") boolean shouldPublishProto,
|
||||
@JsonProperty("networkManagerIface") String networkManagerIface,
|
||||
@JsonProperty("setStaticCommand") String setStaticCommand,
|
||||
@JsonProperty("setDHCPcommand") String setDHCPcommand) {
|
||||
NetworkMode connectionType,
|
||||
String staticIp,
|
||||
String hostname,
|
||||
boolean runNTServer,
|
||||
boolean shouldManage,
|
||||
boolean shouldPublishProto,
|
||||
String networkManagerIface,
|
||||
String setStaticCommand,
|
||||
String setDHCPcommand) {
|
||||
this.ntServerAddress = ntServerAddress;
|
||||
this.connectionType = connectionType;
|
||||
this.staticIp = staticIp;
|
||||
@@ -89,12 +88,10 @@ public class NetworkConfig {
|
||||
config.setDHCPcommand);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getPhysicalInterfaceName() {
|
||||
return this.networkManagerIface;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getEscapedInterfaceName() {
|
||||
return "\"" + networkManagerIface + "\"";
|
||||
}
|
||||
@@ -103,7 +100,6 @@ public class NetworkConfig {
|
||||
this.shouldManage = shouldManage && this.deviceCanManageNetwork();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
protected boolean deviceCanManageNetwork() {
|
||||
return Platform.isLinux();
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ public class NeuralNetworkModelManager {
|
||||
}
|
||||
|
||||
ModelProperties properties =
|
||||
ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().getModel(path);
|
||||
ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().getModel(path);
|
||||
|
||||
if (properties == null) {
|
||||
logger.warn(
|
||||
@@ -332,7 +332,7 @@ public class NeuralNetworkModelManager {
|
||||
// NeuralNetworkModelsSettings
|
||||
ConfigManager.getInstance()
|
||||
.getConfig()
|
||||
.neuralNetworkPropertyManager()
|
||||
.getNeuralNetworkProperties()
|
||||
.addModelProperties(properties);
|
||||
} catch (IllegalArgumentException | IOException e) {
|
||||
logger.error("Failed to translate legacy model filename to properties: " + path, e);
|
||||
@@ -486,7 +486,7 @@ public class NeuralNetworkModelManager {
|
||||
.getConfig()
|
||||
.setNeuralNetworkProperties(
|
||||
supportedProperties.sum(
|
||||
ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager()));
|
||||
ConfigManager.getInstance().getConfig().getNeuralNetworkProperties()));
|
||||
}
|
||||
|
||||
public boolean clearModels() {
|
||||
@@ -511,7 +511,7 @@ public class NeuralNetworkModelManager {
|
||||
}
|
||||
|
||||
// Delete model info
|
||||
return ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().clear();
|
||||
return ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().clear();
|
||||
}
|
||||
|
||||
public File exportSingleModel(String modelPath) {
|
||||
@@ -525,7 +525,7 @@ public class NeuralNetworkModelManager {
|
||||
ModelProperties properties =
|
||||
ConfigManager.getInstance()
|
||||
.getConfig()
|
||||
.neuralNetworkPropertyManager()
|
||||
.getNeuralNetworkProperties()
|
||||
.getModel(Path.of(modelPath));
|
||||
|
||||
String fileName = "";
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.avaje.jsonb.Json;
|
||||
import io.avaje.jsonb.JsonType;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import io.avaje.jsonb.Types;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -27,27 +28,29 @@ import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
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.Version;
|
||||
|
||||
@Json
|
||||
public class NeuralNetworkModelsSettings {
|
||||
/*
|
||||
* 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)
|
||||
*/
|
||||
@Json
|
||||
public record ModelProperties(
|
||||
@JsonProperty("modelPath") Path modelPath,
|
||||
@JsonProperty("nickname") String nickname,
|
||||
@JsonProperty("labels") List<String> labels,
|
||||
@JsonProperty("resolutionWidth") int resolutionWidth,
|
||||
@JsonProperty("resolutionHeight") int resolutionHeight,
|
||||
@JsonProperty("family") Family family,
|
||||
@JsonProperty("version") Version version) {
|
||||
@JsonCreator
|
||||
public ModelProperties {}
|
||||
|
||||
Path modelPath,
|
||||
String nickname,
|
||||
List<String> labels,
|
||||
int resolutionWidth,
|
||||
int resolutionHeight,
|
||||
Family family,
|
||||
Version version) {
|
||||
ModelProperties(ModelProperties other) {
|
||||
this(
|
||||
other.modelPath,
|
||||
@@ -59,13 +62,6 @@ public class NeuralNetworkModelsSettings {
|
||||
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 ===========
|
||||
|
||||
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 model, and should not change
|
||||
@JsonProperty("modelPathToProperties")
|
||||
@Json.Ignore
|
||||
private HashMap<Path, ModelProperties> modelPathToProperties =
|
||||
new HashMap<Path, ModelProperties>();
|
||||
|
||||
/**
|
||||
* 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() {}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
public String toString() {
|
||||
@@ -239,7 +268,7 @@ public class NeuralNetworkModelsSettings {
|
||||
*
|
||||
* @return A list of all models
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Json.Property("models")
|
||||
public ModelProperties[] getModels() {
|
||||
return modelPathToProperties.values().toArray(new ModelProperties[0]);
|
||||
}
|
||||
|
||||
@@ -17,19 +17,25 @@
|
||||
|
||||
package org.photonvision.common.configuration;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.photonvision.vision.processes.VisionSource;
|
||||
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||
|
||||
@Json
|
||||
public class PhotonConfiguration {
|
||||
private final HardwareConfig hardwareConfig;
|
||||
private final HardwareSettings hardwareSettings;
|
||||
private NetworkConfig networkConfig;
|
||||
private AprilTagFieldLayout atfl;
|
||||
|
||||
@Json.Property("atfl")
|
||||
private AprilTagFieldLayout aprilTagFieldLayout;
|
||||
|
||||
private NeuralNetworkModelsSettings neuralNetworkProperties;
|
||||
private HashMap<String, CameraConfiguration> cameraConfigurations;
|
||||
private Map<String, CameraConfiguration> cameraConfigurations;
|
||||
|
||||
public PhotonConfiguration(
|
||||
HardwareConfig hardwareConfig,
|
||||
@@ -46,19 +52,20 @@ public class PhotonConfiguration {
|
||||
new HashMap<>());
|
||||
}
|
||||
|
||||
@Json.Creator
|
||||
public PhotonConfiguration(
|
||||
HardwareConfig hardwareConfig,
|
||||
HardwareSettings hardwareSettings,
|
||||
NetworkConfig networkConfig,
|
||||
AprilTagFieldLayout atfl,
|
||||
NeuralNetworkModelsSettings neuralNetworkProperties,
|
||||
HashMap<String, CameraConfiguration> cameraConfigurations) {
|
||||
Map<String, CameraConfiguration> cameraConfigurations) {
|
||||
this.hardwareConfig = hardwareConfig;
|
||||
this.hardwareSettings = hardwareSettings;
|
||||
this.networkConfig = networkConfig;
|
||||
this.neuralNetworkProperties = neuralNetworkProperties;
|
||||
this.cameraConfigurations = cameraConfigurations;
|
||||
this.atfl = atfl;
|
||||
this.aprilTagFieldLayout = atfl;
|
||||
}
|
||||
|
||||
public PhotonConfiguration() {
|
||||
@@ -83,15 +90,15 @@ public class PhotonConfiguration {
|
||||
}
|
||||
|
||||
public AprilTagFieldLayout getApriltagFieldLayout() {
|
||||
return atfl;
|
||||
return aprilTagFieldLayout;
|
||||
}
|
||||
|
||||
public NeuralNetworkModelsSettings neuralNetworkPropertyManager() {
|
||||
public NeuralNetworkModelsSettings getNeuralNetworkProperties() {
|
||||
return neuralNetworkProperties;
|
||||
}
|
||||
|
||||
public void setApriltagFieldLayout(AprilTagFieldLayout atfl) {
|
||||
this.atfl = atfl;
|
||||
this.aprilTagFieldLayout = atfl;
|
||||
}
|
||||
|
||||
public void setNetworkConfig(NetworkConfig networkConfig) {
|
||||
@@ -102,7 +109,7 @@ public class PhotonConfiguration {
|
||||
this.neuralNetworkProperties = neuralNetworkProperties;
|
||||
}
|
||||
|
||||
public HashMap<String, CameraConfiguration> getCameraConfigurations() {
|
||||
public Map<String, CameraConfiguration> getCameraConfigurations() {
|
||||
return cameraConfigurations;
|
||||
}
|
||||
|
||||
@@ -148,8 +155,8 @@ public class PhotonConfiguration {
|
||||
+ hardwareSettings
|
||||
+ "\n networkConfig="
|
||||
+ networkConfig
|
||||
+ "\n atfl="
|
||||
+ atfl
|
||||
+ "\n aprilTagFieldLayout="
|
||||
+ aprilTagFieldLayout
|
||||
+ "\n neuralNetworkProperties="
|
||||
+ neuralNetworkProperties
|
||||
+ "\n cameraConfigurations={"
|
||||
|
||||
@@ -17,24 +17,27 @@
|
||||
|
||||
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.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import org.photonvision.common.configuration.CameraConfiguration.LegacyCameraConfigStruct;
|
||||
import org.photonvision.common.configuration.DatabaseSchema.Columns;
|
||||
import org.photonvision.common.configuration.DatabaseSchema.Tables;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
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.DriverModePipelineSettings;
|
||||
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||
@@ -260,16 +263,16 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
T configObj;
|
||||
if (!configString.isBlank()) {
|
||||
try {
|
||||
configObj = JacksonUtils.deserialize(configString, ref);
|
||||
configObj = Jsonb.instance().type(ref).fromJson(configString);
|
||||
logger.info("Loaded " + ref.getSimpleName() + " from database");
|
||||
return configObj;
|
||||
} catch (IOException e) {
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
logger.error("Could not deserialize " + ref.getSimpleName() + " from database!", e);
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
configObj = factory.get();
|
||||
logger.info("Loaded default " + ref.getSimpleName());
|
||||
@@ -390,30 +393,16 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
|
||||
var config = c.getValue();
|
||||
statement.setString(1, c.getKey());
|
||||
statement.setString(2, JacksonUtils.serializeToString(config));
|
||||
statement.setString(3, JacksonUtils.serializeToString(config.driveModeSettings));
|
||||
statement.setString(2, Jsonb.instance().type(CameraConfiguration.class).toJson(config));
|
||||
|
||||
// Serializing a list of abstract classes sucks. Instead, make it into an array
|
||||
// of strings, which we can later unpack back into individual settings
|
||||
List<String> settings =
|
||||
config.pipelineSettings.stream()
|
||||
.map(
|
||||
it -> {
|
||||
try {
|
||||
return JacksonUtils.serializeToString(it);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
statement.setString(4, JacksonUtils.serializeToString(settings));
|
||||
// MIGRATION: 2026
|
||||
// We used to serialize pipelines separately, but don't anymore
|
||||
statement.setString(3, "null");
|
||||
statement.setString(4, "[]");
|
||||
|
||||
statement.executeUpdate();
|
||||
}
|
||||
|
||||
} catch (SQLException | IOException e) {
|
||||
} catch (SQLException | IllegalStateException | JsonException e) {
|
||||
logger.error("Err saving cameras", e);
|
||||
try {
|
||||
conn.rollback();
|
||||
@@ -469,7 +458,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
addFile(
|
||||
statement1,
|
||||
GlobalKeys.HARDWARE_SETTINGS,
|
||||
JacksonUtils.serializeToString(config.getHardwareSettings()));
|
||||
Jsonb.instance().type(HardwareSettings.class).toJson(config.getHardwareSettings()));
|
||||
statement1.executeUpdate();
|
||||
}
|
||||
|
||||
@@ -478,7 +467,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
addFile(
|
||||
statement2,
|
||||
GlobalKeys.NETWORK_CONFIG,
|
||||
JacksonUtils.serializeToString(config.getNetworkConfig()));
|
||||
Jsonb.instance().type(NetworkConfig.class).toJson(config.getNetworkConfig()));
|
||||
statement2.executeUpdate();
|
||||
statement2.close();
|
||||
}
|
||||
@@ -488,7 +477,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
addFile(
|
||||
statement3,
|
||||
GlobalKeys.HARDWARE_CONFIG,
|
||||
JacksonUtils.serializeToString(config.getHardwareConfig()));
|
||||
Jsonb.instance().type(HardwareConfig.class).toJson(config.getHardwareConfig()));
|
||||
statement3.executeUpdate();
|
||||
statement3.close();
|
||||
}
|
||||
@@ -498,12 +487,14 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
addFile(
|
||||
statement3,
|
||||
GlobalKeys.NEURAL_NETWORK_PROPERTIES,
|
||||
JacksonUtils.serializeToString(config.neuralNetworkPropertyManager()));
|
||||
Jsonb.instance()
|
||||
.type(NeuralNetworkModelsSettings.class)
|
||||
.toJson(config.getNeuralNetworkProperties()));
|
||||
statement3.executeUpdate();
|
||||
statement3.close();
|
||||
}
|
||||
|
||||
} catch (SQLException | IOException e) {
|
||||
} catch (SQLException | IllegalStateException | JsonException e) {
|
||||
logger.error("Err saving global", e);
|
||||
try {
|
||||
conn.rollback();
|
||||
@@ -594,6 +585,12 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn) {
|
||||
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
|
||||
PreparedStatement query = null;
|
||||
try {
|
||||
@@ -614,57 +611,82 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
while (result.next()) {
|
||||
String uniqueName = "";
|
||||
try {
|
||||
List<String> dummyList = new ArrayList<>();
|
||||
JsonType<List<String>> strListJsonb = Jsonb.instance().type(Types.listOf(String.class));
|
||||
|
||||
uniqueName = result.getString(Columns.CAM_UNIQUE_NAME);
|
||||
|
||||
// A horrifying hack to keep backward compat with otherpaths
|
||||
// We -really- need to delete this -stupid- otherpaths column. I hate it.
|
||||
var configStr = result.getString(Columns.CAM_CONFIG_JSON);
|
||||
CameraConfiguration config =
|
||||
JacksonUtils.deserialize(configStr, CameraConfiguration.class);
|
||||
// MIGRATION: 2024
|
||||
var configJson = result.getString(Columns.CAM_CONFIG_JSON);
|
||||
|
||||
// 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) {
|
||||
logger.info("Legacy CameraConfiguration detected - upgrading");
|
||||
|
||||
// manually create the matchedCameraInfo ourselves. Need to upgrade:
|
||||
// baseName, path, otherPaths, cameraType, usbvid/pid -> matchedCameraInfo
|
||||
config.matchedCameraInfo =
|
||||
JacksonUtils.deserialize(configStr, LegacyCameraConfigStruct.class)
|
||||
Jsonb.instance()
|
||||
.type(LegacyCameraConfigStruct.class)
|
||||
.fromJson(configJson)
|
||||
.matchedCameraInfo;
|
||||
|
||||
// Except that otherPaths used to be its own column. so hack that in here as well
|
||||
var otherPaths =
|
||||
JacksonUtils.deserialize(
|
||||
result.getString(Columns.CAM_OTHERPATHS_JSON), String[].class);
|
||||
Jsonb.instance()
|
||||
.type(String[].class)
|
||||
.fromJson(result.getString(Columns.CAM_OTHERPATHS_JSON));
|
||||
if (config.matchedCameraInfo instanceof UsbCameraInfo usbInfo) {
|
||||
usbInfo.otherPaths = otherPaths;
|
||||
}
|
||||
}
|
||||
|
||||
var driverMode =
|
||||
JacksonUtils.deserialize(
|
||||
result.getString(Columns.CAM_DRIVERMODE_JSON), DriverModePipelineSettings.class);
|
||||
List<?> pipelineSettings =
|
||||
JacksonUtils.deserialize(
|
||||
result.getString(Columns.CAM_PIPELINE_JSONS), dummyList.getClass());
|
||||
// MIGRATION: 2026
|
||||
List<String> legacyPipelineSettings =
|
||||
strListJsonb.fromJson(result.getString(Columns.CAM_PIPELINE_JSONS));
|
||||
|
||||
for (var pipelineJson : legacyPipelineSettings) {
|
||||
logger.info("Importing pipeline JSON into camera settings");
|
||||
if (pipelineJson.startsWith("[")) {
|
||||
logger.info("Legacy type-wrapper CVPipelineSettings being migrated");
|
||||
pipelineJson = CVPipelineSettings.remapSettingsJson(pipelineJson);
|
||||
}
|
||||
|
||||
List<CVPipelineSettings> loadedSettings = new ArrayList<>();
|
||||
for (var setting : pipelineSettings) {
|
||||
if (setting instanceof String str) {
|
||||
try {
|
||||
loadedSettings.add(JacksonUtils.deserialize(str, CVPipelineSettings.class));
|
||||
} catch (IOException e) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// MIGRATION: 2026
|
||||
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);
|
||||
}
|
||||
|
||||
config.pipelineSettings = loadedSettings;
|
||||
config.driveModeSettings = driverMode;
|
||||
loadedConfigurations.put(uniqueName, config);
|
||||
} catch (IOException e) {
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
logger.error(
|
||||
"Could not deserialize camera configuration " + uniqueName + " from database!", e);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public abstract class DataChangeSubscriber {
|
||||
this(DataChangeSource.AllSources, wantedDestinations);
|
||||
}
|
||||
|
||||
public abstract void onDataChangeEvent(DataChangeEvent<?> event);
|
||||
public abstract <T> void onDataChangeEvent(DataChangeEvent<T> event);
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
|
||||
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.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.photonvision.PhotonVersion;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
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.networking.NetworkUtils;
|
||||
import org.photonvision.common.util.TimedTaskManager;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
import org.wpilib.driverstation.Alert;
|
||||
import org.wpilib.driverstation.Alert.Level;
|
||||
import org.wpilib.networktables.LogMessage;
|
||||
@@ -199,7 +200,7 @@ public class NetworkTablesManager {
|
||||
var atfl_json = event.valueData.value.getString();
|
||||
try {
|
||||
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().requestSave();
|
||||
DataChangeService.getInstance()
|
||||
@@ -207,7 +208,7 @@ public class NetworkTablesManager {
|
||||
new OutgoingUIEvent<>(
|
||||
"fullsettings",
|
||||
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
||||
} catch (IOException e) {
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
logger.error("Error deserializing atfl!");
|
||||
logger.error(atfl_json);
|
||||
}
|
||||
@@ -270,7 +271,7 @@ public class NetworkTablesManager {
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, CameraConfiguration> cameraConfigs =
|
||||
Map<String, CameraConfiguration> cameraConfigs =
|
||||
ConfigManager.getInstance().getConfig().getCameraConfigurations();
|
||||
String[] cameraNames =
|
||||
cameraConfigs.entrySet().stream()
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
|
||||
package org.photonvision.common.dataflow.websocket;
|
||||
|
||||
import java.util.HashMap;
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
import org.photonvision.vision.calibration.UICameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.camera.PVCameraInfo;
|
||||
import org.photonvision.vision.camera.QuirkyCamera;
|
||||
|
||||
@Json
|
||||
public class UICameraConfiguration {
|
||||
// Path to the camera device. On Linux, this is a special file in /dev/v4l/by-id
|
||||
// or /dev/videoN.
|
||||
@@ -37,10 +39,10 @@ public class UICameraConfiguration {
|
||||
public String uniqueName;
|
||||
|
||||
public double fov;
|
||||
public HashMap<String, Object> currentPipelineSettings;
|
||||
public Map<String, Object> currentPipelineSettings;
|
||||
public int currentPipelineIndex;
|
||||
public List<String> pipelineNicknames;
|
||||
public HashMap<Integer, HashMap<String, Object>> videoFormatList;
|
||||
public List<Map<String, Object>> videoFormatList;
|
||||
public int outputStreamPort;
|
||||
public int inputStreamPort;
|
||||
public List<UICameraCalibrationCoefficients> calibrations;
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
|
||||
package org.photonvision.common.dataflow.websocket;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.configuration.NeuralNetworkModelsSettings;
|
||||
|
||||
@Json
|
||||
public class UIGeneralSettings {
|
||||
public UIGeneralSettings(
|
||||
String version,
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
package org.photonvision.common.dataflow.websocket;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
|
||||
@Json
|
||||
public class UILightingConfig {
|
||||
public UILightingConfig(int brightness, boolean supported) {
|
||||
this.brightness = brightness;
|
||||
|
||||
@@ -17,11 +17,18 @@
|
||||
|
||||
package org.photonvision.common.dataflow.websocket;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.configuration.NetworkConfig;
|
||||
import org.photonvision.common.networking.NetworkUtils.NMDeviceInfo;
|
||||
|
||||
@Json
|
||||
public class UINetConfig extends NetworkConfig {
|
||||
// Constructor for JSON to allow all properties to be set directly
|
||||
public UINetConfig() {
|
||||
this.canManage = this.deviceCanManageNetwork();
|
||||
}
|
||||
|
||||
public UINetConfig(
|
||||
NetworkConfig config, List<NMDeviceInfo> networkInterfaceNames, boolean networkingDisabled) {
|
||||
super(config);
|
||||
@@ -32,5 +39,7 @@ public class UINetConfig extends NetworkConfig {
|
||||
|
||||
public List<NMDeviceInfo> networkInterfaceNames;
|
||||
public boolean networkingDisabled;
|
||||
|
||||
@Json.Ignore(serialize = true)
|
||||
public boolean canManage;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.photonvision.common.dataflow.websocket;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.List;
|
||||
import org.photonvision.PhotonVersion;
|
||||
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.VisionSourceManager;
|
||||
|
||||
@Json
|
||||
public class UIPhotonConfiguration {
|
||||
public List<UICameraConfiguration> cameraSettings;
|
||||
public UIProgramSettings settings;
|
||||
@@ -59,7 +61,7 @@ public class UIPhotonConfiguration {
|
||||
// TODO add support for other types of GPU accel
|
||||
LoadJNI.hasLoaded(JNITypes.LIBCAMERA) ? "Zerocopy Libcamera Working" : "",
|
||||
LoadJNI.hasLoaded(JNITypes.MRCAL),
|
||||
c.neuralNetworkPropertyManager().getModels(),
|
||||
c.getNeuralNetworkProperties().getModels(),
|
||||
NeuralNetworkModelManager.getInstance().getSupportedBackends(),
|
||||
c.getHardwareConfig().deviceName.isEmpty()
|
||||
? Platform.getHardwareModel()
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
|
||||
package org.photonvision.common.dataflow.websocket;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||
|
||||
@Json
|
||||
public class UIProgramSettings {
|
||||
public UIProgramSettings(
|
||||
UINetConfig networkSettings,
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
|
||||
package org.photonvision.common.hardware;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import io.avaje.jsonb.Json;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
@@ -35,26 +37,22 @@ import org.photonvision.common.logging.Logger;
|
||||
public class OsImageData {
|
||||
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();
|
||||
|
||||
@Json(naming = Json.Naming.LowerUnderscore)
|
||||
public static record ImageMetadata(
|
||||
String buildDate, String commitSha, String commitTag, String imageName, String imageSource) {}
|
||||
|
||||
private static Optional<ImageMetadata> getImageMetadata() {
|
||||
if (!imageMetadataFile.toFile().exists()) {
|
||||
if (!imageMetadataFile.exists()) {
|
||||
logger.warn("Photon cannot locate OS image metadata at " + imageMetadataFile.toString());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
String content = Files.readString(imageMetadataFile).strip();
|
||||
|
||||
ObjectMapper mapper =
|
||||
new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||
|
||||
ImageMetadata md = mapper.readValue(content, ImageMetadata.class);
|
||||
try (InputStream stream = new FileInputStream(imageMetadataFile)) {
|
||||
ImageMetadata md = Jsonb.instance().type(ImageMetadata.class).fromJson(stream);
|
||||
|
||||
if (md.buildDate() == null
|
||||
&& md.commitSha() == null
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import org.photonvision.common.hardware.metrics.proto.DeviceMetricsProto;
|
||||
|
||||
@Json
|
||||
public record DeviceMetrics(
|
||||
double cpuTemp,
|
||||
double cpuUtil,
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
|
||||
package org.photonvision.common.networking;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import io.avaje.jsonb.Json;
|
||||
|
||||
@Json
|
||||
public enum NetworkMode {
|
||||
DHCP,
|
||||
STATIC;
|
||||
|
||||
@JsonValue
|
||||
@Json.Value
|
||||
public int toValue() {
|
||||
return ordinal();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.photonvision.common.networking;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
@@ -62,9 +63,10 @@ public class NetworkUtils {
|
||||
* @param devName The underlying device name, used by dhclient
|
||||
* @param nmType The NetworkManager device type
|
||||
*/
|
||||
@Json
|
||||
public static record NMDeviceInfo(String connName, String devName, NMType nmType) {
|
||||
public NMDeviceInfo(String c, String d, String type) {
|
||||
this(c, d, NMType.typeForString(type));
|
||||
public NMDeviceInfo(String connName, String devName, String nmType) {
|
||||
this(connName, devName, NMType.typeForString(nmType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
|
||||
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.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
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 CameraCalibrationCoefficients getCoeffs(String filename, boolean testMode) {
|
||||
try {
|
||||
return new ObjectMapper()
|
||||
.readValue(
|
||||
(Path.of(getCalibrationPath(testMode).toString(), filename).toFile()),
|
||||
CameraCalibrationCoefficients.class);
|
||||
} catch (IOException e) {
|
||||
try (var stream =
|
||||
new FileInputStream(Path.of(getCalibrationPath(testMode).toString(), filename).toFile())) {
|
||||
return Jsonb.instance().type(CameraCalibrationCoefficients.class).fromJson(stream);
|
||||
} catch (IOException | IllegalStateException | JsonException e) {
|
||||
e.printStackTrace();
|
||||
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;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import org.opencv.core.Point;
|
||||
|
||||
@Json
|
||||
public class DoubleCouple extends NumberCouple<Double> {
|
||||
public DoubleCouple() {
|
||||
super(0.0, 0.0);
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
package org.photonvision.common.util.numbers;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
|
||||
@Json
|
||||
public class IntegerCouple extends NumberCouple<Integer> {
|
||||
public IntegerCouple() {
|
||||
super(0, 0);
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
package org.photonvision.common.util.numbers;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public abstract class NumberCouple<T extends Number> {
|
||||
protected T first;
|
||||
protected T second;
|
||||
@@ -56,7 +54,6 @@ public abstract class NumberCouple<T extends Number> {
|
||||
&& couple.second.equals(second);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isEmpty() {
|
||||
return first.intValue() == 0 && second.intValue() == 0;
|
||||
}
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
|
||||
package org.photonvision.vision.calibration;
|
||||
|
||||
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 io.avaje.jsonb.Json;
|
||||
import java.awt.Color;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
@@ -36,45 +33,36 @@ import org.opencv.imgproc.Imgproc;
|
||||
import org.photonvision.common.util.ColorHelper;
|
||||
import org.wpilib.math.geometry.Pose3d;
|
||||
|
||||
@Json
|
||||
// Ignore the previous calibration data that was stored in the json file.
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class BoardObservation implements Cloneable {
|
||||
// Expected feature 3d location in the camera frame
|
||||
@JsonProperty("locationInObjectSpace")
|
||||
public List<Point3> locationInObjectSpace;
|
||||
|
||||
// Observed location in pixel space
|
||||
@JsonProperty("locationInImageSpace")
|
||||
public List<Point> locationInImageSpace;
|
||||
|
||||
// (measured location in pixels) - (expected from FK)
|
||||
@JsonProperty("reprojectionErrors")
|
||||
public List<Point> reprojectionErrors;
|
||||
|
||||
// Solver optimized board poses
|
||||
@JsonProperty("optimisedCameraToObject")
|
||||
public Pose3d optimisedCameraToObject;
|
||||
|
||||
// If we should use this observation when re-calculating camera calibration
|
||||
@JsonProperty("cornersUsed")
|
||||
public boolean[] cornersUsed;
|
||||
|
||||
@JsonProperty("snapshotName")
|
||||
public String snapshotName;
|
||||
|
||||
@JsonProperty("snapshotDataLocation")
|
||||
@Nullable
|
||||
public Path snapshotDataLocation;
|
||||
@Nullable public Path snapshotDataLocation;
|
||||
|
||||
@JsonCreator
|
||||
public BoardObservation(
|
||||
@JsonProperty("locationInObjectSpace") List<Point3> locationInObjectSpace,
|
||||
@JsonProperty("locationInImageSpace") List<Point> locationInImageSpace,
|
||||
@JsonProperty("reprojectionErrors") List<Point> reprojectionErrors,
|
||||
@JsonProperty("optimisedCameraToObject") Pose3d optimisedCameraToObject,
|
||||
@JsonProperty("cornersUsed") boolean[] cornersUsed,
|
||||
@JsonProperty("snapshotName") String snapshotName,
|
||||
@JsonProperty("snapshotDataLocation") Path snapshotDataLocation) {
|
||||
List<Point3> locationInObjectSpace,
|
||||
List<Point> locationInImageSpace,
|
||||
List<Point> reprojectionErrors,
|
||||
Pose3d optimisedCameraToObject,
|
||||
boolean[] cornersUsed,
|
||||
String snapshotName,
|
||||
Path snapshotDataLocation) {
|
||||
this.locationInObjectSpace = locationInObjectSpace;
|
||||
this.locationInImageSpace = locationInImageSpace;
|
||||
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
|
||||
* 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
|
||||
* releasing the Mat.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public Mat annotateImage() {
|
||||
var image = loadImage();
|
||||
|
||||
@@ -179,7 +165,6 @@ public final class BoardObservation implements Cloneable {
|
||||
*
|
||||
* @return Mean reprojection error in pixels.
|
||||
*/
|
||||
@JsonIgnore
|
||||
double meanReprojectionError() {
|
||||
return reprojectionErrors.stream()
|
||||
.filter(pt -> cornersUsed[reprojectionErrors.indexOf(pt)])
|
||||
|
||||
@@ -17,11 +17,7 @@
|
||||
|
||||
package org.photonvision.vision.calibration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
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 io.avaje.jsonb.Json;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
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.Releasable;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Json
|
||||
public class CameraCalibrationCoefficients implements Releasable {
|
||||
@JsonProperty("resolution")
|
||||
public final Size unrotatedImageSize;
|
||||
/** The unrotated resolution of the calibration */
|
||||
public final Size resolution;
|
||||
|
||||
@JsonProperty("cameraIntrinsics")
|
||||
public final JsonMatOfDouble cameraIntrinsics;
|
||||
|
||||
@JsonProperty("distCoeffs")
|
||||
@JsonAlias({"distCoeffs", "distCoeffs"})
|
||||
public final JsonMatOfDouble distCoeffs;
|
||||
|
||||
@JsonProperty("observations")
|
||||
public final List<BoardObservation> observations;
|
||||
|
||||
@JsonProperty("calobjectWarp")
|
||||
public final double[] calobjectWarp;
|
||||
|
||||
@JsonProperty("calobjectSize")
|
||||
public final Size calobjectSize;
|
||||
|
||||
@JsonProperty("calobjectSpacing")
|
||||
public final double calobjectSpacing;
|
||||
|
||||
@JsonProperty("lensmodel")
|
||||
public final CameraLensModel lensmodel;
|
||||
|
||||
/**
|
||||
@@ -75,17 +63,16 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
* width/height
|
||||
* @param calobjectSpacing Spacing between adjacent squares, in meters
|
||||
*/
|
||||
@JsonCreator
|
||||
public CameraCalibrationCoefficients(
|
||||
@JsonProperty("resolution") Size resolution,
|
||||
@JsonProperty("cameraIntrinsics") JsonMatOfDouble cameraIntrinsics,
|
||||
@JsonProperty("distCoeffs") JsonMatOfDouble distCoeffs,
|
||||
@JsonProperty("calobjectWarp") double[] calobjectWarp,
|
||||
@JsonProperty("observations") List<BoardObservation> observations,
|
||||
@JsonProperty("calobjectSize") Size calobjectSize,
|
||||
@JsonProperty("calobjectSpacing") double calobjectSpacing,
|
||||
@JsonProperty("lensmodel") CameraLensModel lensmodel) {
|
||||
this.unrotatedImageSize = resolution;
|
||||
Size resolution,
|
||||
JsonMatOfDouble cameraIntrinsics,
|
||||
JsonMatOfDouble distCoeffs,
|
||||
double[] calobjectWarp,
|
||||
List<BoardObservation> observations,
|
||||
Size calobjectSize,
|
||||
double calobjectSpacing,
|
||||
CameraLensModel lensmodel) {
|
||||
this.resolution = resolution;
|
||||
this.cameraIntrinsics = cameraIntrinsics;
|
||||
this.distCoeffs = distCoeffs;
|
||||
this.calobjectWarp = calobjectWarp;
|
||||
@@ -129,7 +116,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
rotatedIntrinsics.put(1, 1, fx);
|
||||
|
||||
// CX
|
||||
rotatedIntrinsics.put(0, 2, unrotatedImageSize.height - cy);
|
||||
rotatedIntrinsics.put(0, 2, resolution.height - cy);
|
||||
// CY
|
||||
rotatedIntrinsics.put(1, 2, cx);
|
||||
|
||||
@@ -139,14 +126,14 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
rotatedDistCoeffs.put(0, 3, -p1);
|
||||
|
||||
// The rotated image size is the same as the unrotated image size, but the width and height
|
||||
// are flipped
|
||||
rotatedImageSize = new Size(unrotatedImageSize.height, unrotatedImageSize.width);
|
||||
// are swapped
|
||||
rotatedImageSize = new Size(resolution.height, resolution.width);
|
||||
break;
|
||||
case DEG_180_CCW:
|
||||
// CX
|
||||
rotatedIntrinsics.put(0, 2, unrotatedImageSize.width - cx);
|
||||
rotatedIntrinsics.put(0, 2, resolution.width - cx);
|
||||
// CY
|
||||
rotatedIntrinsics.put(1, 2, unrotatedImageSize.height - cy);
|
||||
rotatedIntrinsics.put(1, 2, resolution.height - cy);
|
||||
|
||||
// P1
|
||||
rotatedDistCoeffs.put(0, 2, -p1);
|
||||
@@ -154,7 +141,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
rotatedDistCoeffs.put(0, 3, -p2);
|
||||
|
||||
// The rotated image size is the same as the unrotated image size
|
||||
rotatedImageSize = unrotatedImageSize;
|
||||
rotatedImageSize = resolution;
|
||||
break;
|
||||
case DEG_90_CCW:
|
||||
// FX
|
||||
@@ -165,7 +152,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
// CX
|
||||
rotatedIntrinsics.put(0, 2, cy);
|
||||
// CY
|
||||
rotatedIntrinsics.put(1, 2, unrotatedImageSize.width - cx);
|
||||
rotatedIntrinsics.put(1, 2, resolution.width - cx);
|
||||
|
||||
// P1
|
||||
rotatedDistCoeffs.put(0, 2, -p2);
|
||||
@@ -173,8 +160,8 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
rotatedDistCoeffs.put(0, 3, p1);
|
||||
|
||||
// The rotated image size is the same as the unrotated image size, but the width and height
|
||||
// are flipped
|
||||
rotatedImageSize = new Size(unrotatedImageSize.height, unrotatedImageSize.width);
|
||||
// are swapped
|
||||
rotatedImageSize = new Size(resolution.height, resolution.width);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -196,27 +183,22 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
lensmodel);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Mat getCameraIntrinsicsMat() {
|
||||
return cameraIntrinsics.getAsMatOfDouble();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public MatOfDouble getDistCoeffsMat() {
|
||||
return distCoeffs.getAsMatOfDouble();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public double[] getIntrinsicsArr() {
|
||||
return cameraIntrinsics.data;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public double[] getDistCoeffsArr() {
|
||||
return distCoeffs.data;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<BoardObservation> getObservations() {
|
||||
return observations;
|
||||
}
|
||||
@@ -230,7 +212,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CameraCalibrationCoefficients [resolution="
|
||||
+ unrotatedImageSize
|
||||
+ resolution
|
||||
+ ", cameraIntrinsics="
|
||||
+ cameraIntrinsics
|
||||
+ ", distCoeffs="
|
||||
@@ -244,7 +226,7 @@ public class CameraCalibrationCoefficients implements Releasable {
|
||||
|
||||
public UICameraCalibrationCoefficients cloneWithoutObservations() {
|
||||
return new UICameraCalibrationCoefficients(
|
||||
unrotatedImageSize,
|
||||
resolution,
|
||||
cameraIntrinsics,
|
||||
distCoeffs,
|
||||
calobjectWarp,
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
|
||||
package org.photonvision.vision.calibration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.Arrays;
|
||||
import org.ejml.simple.SimpleMatrix;
|
||||
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.util.Num;
|
||||
|
||||
@Json
|
||||
/** JSON-serializable image. Data is stored as a raw JSON array. */
|
||||
public class JsonMatOfDouble implements Releasable {
|
||||
public final int rows;
|
||||
@@ -36,28 +36,23 @@ public class JsonMatOfDouble implements Releasable {
|
||||
public final double[] data;
|
||||
|
||||
// Cached matrices to avoid object recreation
|
||||
@JsonIgnore private Mat wrappedMat = null;
|
||||
@JsonIgnore private Matrix wpilibMat = null;
|
||||
@Json.Ignore private Mat wrappedMat = null;
|
||||
@Json.Ignore private Matrix wpilibMat = null;
|
||||
|
||||
@JsonIgnore private MatOfDouble wrappedMatOfDouble;
|
||||
private boolean released = false;
|
||||
@Json.Ignore private MatOfDouble wrappedMatOfDouble;
|
||||
@Json.Ignore private boolean released = false;
|
||||
|
||||
public JsonMatOfDouble(int rows, int cols, double[] data) {
|
||||
this(rows, cols, CvType.CV_64FC1, data);
|
||||
}
|
||||
|
||||
public JsonMatOfDouble(
|
||||
@JsonProperty("rows") int rows,
|
||||
@JsonProperty("cols") int cols,
|
||||
@JsonProperty("type") int type,
|
||||
@JsonProperty("data") double[] data) {
|
||||
public JsonMatOfDouble(int rows, int cols, int type, double[] data) {
|
||||
this.rows = rows;
|
||||
this.cols = cols;
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
private static double[] getDataFromMat(Mat mat) {
|
||||
double[] data = new double[(int) mat.total()];
|
||||
mat.get(0, 0, data);
|
||||
@@ -75,7 +70,6 @@ public class JsonMatOfDouble implements Releasable {
|
||||
return new JsonMatOfDouble(mat.rows(), mat.cols(), getDataFromMat(mat));
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
private Mat getAsMat() {
|
||||
if (this.type != CvType.CV_64FC1) return null;
|
||||
|
||||
@@ -91,7 +85,6 @@ public class JsonMatOfDouble implements Releasable {
|
||||
return this.wrappedMat;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public MatOfDouble getAsMatOfDouble() {
|
||||
if (this.released) {
|
||||
throw new RuntimeException("This calibration object was already released");
|
||||
@@ -105,7 +98,6 @@ public class JsonMatOfDouble implements Releasable {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@JsonIgnore
|
||||
public <R extends Num, C extends Num> Matrix<R, C> getAsWpilibMat() {
|
||||
if (wpilibMat == null) {
|
||||
wpilibMat = new Matrix<R, C>(new SimpleMatrix(rows, cols, true, data));
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
|
||||
package org.photonvision.vision.calibration;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import org.opencv.core.Size;
|
||||
|
||||
@Json
|
||||
public class UICameraCalibrationCoefficients extends CameraCalibrationCoefficients {
|
||||
public int numSnapshots;
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
package org.photonvision.vision.camera;
|
||||
|
||||
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.vision.frame.FrameProvider;
|
||||
import org.photonvision.vision.frame.FrameStaticProperties;
|
||||
@@ -100,7 +101,7 @@ public class FileVisionSource extends VisionSource {
|
||||
public static class FileSourceSettables extends VisionSourceSettables {
|
||||
private final VideoMode videoMode;
|
||||
|
||||
private final HashMap<Integer, VideoMode> videoModes = new HashMap<>();
|
||||
private final ArrayList<VideoMode> videoModes = new ArrayList<>();
|
||||
|
||||
FileSourceSettables(
|
||||
CameraConfiguration cameraConfiguration, FrameStaticProperties frameStaticProperties) {
|
||||
@@ -112,7 +113,7 @@ public class FileVisionSource extends VisionSource {
|
||||
frameStaticProperties.imageWidth,
|
||||
frameStaticProperties.imageHeight,
|
||||
30);
|
||||
videoModes.put(0, videoMode);
|
||||
videoModes.add(videoMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,7 +138,7 @@ public class FileVisionSource extends VisionSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, VideoMode> getAllVideoModes() {
|
||||
public List<VideoMode> getAllVideoModes() {
|
||||
return videoModes;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,33 +17,31 @@
|
||||
|
||||
package org.photonvision.vision.camera;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.avaje.jsonb.Json;
|
||||
import io.avaje.jsonb.JsonType;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import io.avaje.jsonb.Types;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import org.wpilib.vision.camera.UsbCameraInfo;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = PVCameraInfo.PVUsbCameraInfo.class),
|
||||
@JsonSubTypes.Type(value = PVCameraInfo.PVCSICameraInfo.class),
|
||||
@JsonSubTypes.Type(value = PVCameraInfo.PVFileCameraInfo.class)
|
||||
})
|
||||
@Json(typeProperty = "type")
|
||||
@Json.SubType(type = PVCameraInfo.PVUsbCameraInfo.class)
|
||||
@Json.SubType(type = PVCameraInfo.PVCSICameraInfo.class)
|
||||
@Json.SubType(type = PVCameraInfo.PVFileCameraInfo.class)
|
||||
public sealed interface PVCameraInfo {
|
||||
/**
|
||||
* @return The path of the camera.
|
||||
*/
|
||||
@Json.Property("path")
|
||||
String path();
|
||||
|
||||
/**
|
||||
* @return The base name of the camera aka the name as just ascii.
|
||||
*/
|
||||
@Json.Property("name")
|
||||
String name();
|
||||
|
||||
/**
|
||||
@@ -64,7 +62,7 @@ public sealed interface PVCameraInfo {
|
||||
*
|
||||
* @return The unique path of the camera
|
||||
*/
|
||||
@JsonGetter(value = "uniquePath")
|
||||
@Json.Property("uniquePath")
|
||||
String uniquePath();
|
||||
|
||||
String[] otherPaths();
|
||||
@@ -82,16 +80,9 @@ public sealed interface PVCameraInfo {
|
||||
return this.equals((Object) other);
|
||||
}
|
||||
|
||||
@JsonTypeName("PVUsbCameraInfo")
|
||||
public static final class PVUsbCameraInfo extends UsbCameraInfo implements PVCameraInfo {
|
||||
@JsonCreator
|
||||
public PVUsbCameraInfo(
|
||||
@JsonProperty("dev") int dev,
|
||||
@JsonProperty("path") String path,
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("otherPaths") String[] otherPaths,
|
||||
@JsonProperty("vendorId") int vendorId,
|
||||
@JsonProperty("productId") int productId) {
|
||||
int dev, String path, String name, String[] otherPaths, int vendorId, int 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 final String path;
|
||||
public final String baseName;
|
||||
|
||||
@JsonCreator
|
||||
public PVCSICameraInfo(
|
||||
@JsonProperty("path") String path, @JsonProperty("baseName") String baseName) {
|
||||
public PVCSICameraInfo(String path, String baseName) {
|
||||
this.path = path;
|
||||
this.baseName = baseName;
|
||||
}
|
||||
@@ -233,13 +221,11 @@ public sealed interface PVCameraInfo {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("PVFileCameraInfo")
|
||||
public static final class PVFileCameraInfo implements PVCameraInfo {
|
||||
public final String path;
|
||||
public final String name;
|
||||
|
||||
@JsonCreator
|
||||
public PVFileCameraInfo(@JsonProperty("path") String path, @JsonProperty("name") String name) {
|
||||
public PVFileCameraInfo(String path, String name) {
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
}
|
||||
@@ -300,4 +286,25 @@ public sealed interface PVCameraInfo {
|
||||
public static PVCameraInfo fromFileInfo(String path, String 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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Json
|
||||
public class QuirkyCamera {
|
||||
private static final List<QuirkyCamera> quirkyCameras =
|
||||
List.of(
|
||||
@@ -98,19 +98,14 @@ public class QuirkyCamera {
|
||||
CameraQuirk.Gain,
|
||||
CameraQuirk.AwbRedBlueGain); // PiCam (using libcamera GPU Driver on raspberry pi)
|
||||
|
||||
@JsonProperty("baseName")
|
||||
public final String baseName;
|
||||
|
||||
@JsonProperty("usbVid")
|
||||
public final int usbVid;
|
||||
|
||||
@JsonProperty("usbPid")
|
||||
public final int usbPid;
|
||||
|
||||
@JsonProperty("displayName")
|
||||
public final String displayName;
|
||||
|
||||
@JsonProperty("quirks")
|
||||
public final Map<CameraQuirk, Boolean> quirks;
|
||||
|
||||
/**
|
||||
@@ -165,13 +160,12 @@ public class QuirkyCamera {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public QuirkyCamera(
|
||||
@JsonProperty("baseName") String baseName,
|
||||
@JsonProperty("usbVid") int usbVid,
|
||||
@JsonProperty("usbPid") int usbPid,
|
||||
@JsonProperty("displayName") String displayName,
|
||||
@JsonProperty("quirks") Map<CameraQuirk, Boolean> quirks) {
|
||||
String baseName,
|
||||
int usbVid,
|
||||
int usbPid,
|
||||
String displayName,
|
||||
Map<CameraQuirk, Boolean> quirks) {
|
||||
this.baseName = baseName;
|
||||
this.usbPid = usbPid;
|
||||
this.usbVid = usbVid;
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.photonvision.vision.camera.USBCameras;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -286,7 +285,6 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
|
||||
}
|
||||
|
||||
private void cacheVideoModes() {
|
||||
videoModes = new HashMap<>();
|
||||
List<VideoMode> videoModesList = new ArrayList<>();
|
||||
try {
|
||||
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
|
||||
// closer to the bottom.
|
||||
Collections.reverse(sortedList);
|
||||
|
||||
for (int i = 0; i < sortedList.size(); i++) {
|
||||
videoModes.put(i, sortedList.get(i));
|
||||
}
|
||||
videoModes = sortedList;
|
||||
|
||||
// If after all that we still have no video modes, not much we can do besides
|
||||
// throw up our hands
|
||||
@@ -329,11 +324,11 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, VideoMode> getAllVideoModes() {
|
||||
public List<VideoMode> getAllVideoModes() {
|
||||
if (!cameraPropertiesCached) {
|
||||
// Device hasn't connected at least once, best I can do is given up
|
||||
logger.warn("Device hasn't connected, cannot enumerate video modes");
|
||||
return new HashMap<>();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return videoModes;
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
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.util.math.MathUtils;
|
||||
import org.photonvision.raspi.LibCameraJNI;
|
||||
@@ -61,7 +62,7 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
|
||||
public LibcameraGpuSettables(CameraConfiguration configuration) {
|
||||
super(configuration);
|
||||
|
||||
videoModes = new HashMap<>();
|
||||
videoModes = new ArrayList<>();
|
||||
|
||||
LibCameraJNI.SensorModel tempSensorModel;
|
||||
try {
|
||||
@@ -74,18 +75,18 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
|
||||
|
||||
if (sensorModel == LibCameraJNI.SensorModel.IMX219) {
|
||||
// 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.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 30, 30, .39));
|
||||
videoModes.put(2, 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, 320, 240, 120, 120, .39));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 30, 30, .39));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 65, 90, .39));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 30, 30, .39));
|
||||
// TODO: fix 1280x720 in the native code and re-add it
|
||||
videoModes.put(4, new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, .53));
|
||||
videoModes.put(5, 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, 1920, 1080, 15, 20, .53));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 2, 2464 / 2, 15, 20, 1));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 4, 2464 / 4, 15, 20, 1));
|
||||
} else if (sensorModel == LibCameraJNI.SensorModel.OV9281) {
|
||||
// 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.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 120, 120, 1));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 400, 120, 240, 1));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 120, 120, 1));
|
||||
|
||||
} else {
|
||||
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
|
||||
videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 90, 90, 1));
|
||||
videoModes.put(1, 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, 320, 240, 90, 90, 1));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 85, 90, 1));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 960, 720, 45, 49, 0.74));
|
||||
// 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.put(4, 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, 2592 / 2, 1944 / 2, 20, 20, 1));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 720, 30, 45, 0.91));
|
||||
videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, 0.72));
|
||||
}
|
||||
|
||||
// TODO need to add more video modes for new sensors here
|
||||
@@ -253,7 +254,7 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, VideoMode> getAllVideoModes() {
|
||||
public List<VideoMode> getAllVideoModes() {
|
||||
return videoModes;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.Objects;
|
||||
import org.opencv.core.Point;
|
||||
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||
@@ -91,21 +91,18 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
|
||||
public int cornerDetectionSideCount = 4;
|
||||
public double cornerDetectionAccuracyPercentage = 10;
|
||||
|
||||
// MIGRATION: 2025
|
||||
/**
|
||||
* Handles backward compatibility for the deprecated outputShowMultipleTargets property. When
|
||||
* outputShowMultipleTargets is encountered during deserialization, it sets outputMaximumTargets
|
||||
* appropriately. If outputShowMultipleTargets is false, outputMaximumTargets is set to 1.
|
||||
*/
|
||||
@JsonAnySetter
|
||||
public void handleUnknownProperty(String name, Object value) {
|
||||
// Handle the old showMultipleTargets property for backward compatibility
|
||||
if ("outputShowMultipleTargets".equals(name) && value instanceof Boolean showMultipleTargets) {
|
||||
@Json.Property("outputShowMultipleTargets")
|
||||
public void importShowMultipleTargets(boolean showMultipleTargets) {
|
||||
if (!showMultipleTargets) {
|
||||
// If showMultipleTargets is false, limit to 1 target
|
||||
outputMaximumTargets = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import org.photonvision.vision.apriltag.AprilTagFamily;
|
||||
import org.photonvision.vision.target.TargetModel;
|
||||
|
||||
@JsonTypeName("AprilTagPipelineSettings")
|
||||
public class AprilTagPipelineSettings extends AdvancedPipelineSettings {
|
||||
public AprilTagFamily tagFamily = AprilTagFamily.kTag36h11;
|
||||
public int decimate = 1;
|
||||
|
||||
@@ -17,12 +17,10 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import org.photonvision.common.util.numbers.IntegerCouple;
|
||||
import org.photonvision.vision.apriltag.AprilTagFamily;
|
||||
import org.photonvision.vision.target.TargetModel;
|
||||
|
||||
@JsonTypeName("ArucoPipelineSettings")
|
||||
public class ArucoPipelineSettings extends AdvancedPipelineSettings {
|
||||
public AprilTagFamily tagFamily = AprilTagFamily.kTag36h11;
|
||||
|
||||
|
||||
@@ -17,23 +17,25 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import io.avaje.jsonb.Json;
|
||||
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 org.photonvision.vision.frame.FrameDivisor;
|
||||
import org.photonvision.vision.opencv.ImageRotationMode;
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.WRAPPER_ARRAY,
|
||||
property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = ColoredShapePipelineSettings.class),
|
||||
@JsonSubTypes.Type(value = ReflectivePipelineSettings.class),
|
||||
@JsonSubTypes.Type(value = DriverModePipelineSettings.class),
|
||||
@JsonSubTypes.Type(value = AprilTagPipelineSettings.class),
|
||||
@JsonSubTypes.Type(value = ArucoPipelineSettings.class),
|
||||
@JsonSubTypes.Type(value = ObjectDetectionPipelineSettings.class)
|
||||
@Json(typeProperty = "type")
|
||||
@Json.SubTypes({
|
||||
@Json.SubType(type = ColoredShapePipelineSettings.class),
|
||||
@Json.SubType(type = ReflectivePipelineSettings.class),
|
||||
@Json.SubType(type = DriverModePipelineSettings.class),
|
||||
@Json.SubType(type = AprilTagPipelineSettings.class),
|
||||
@Json.SubType(type = ArucoPipelineSettings.class),
|
||||
@Json.SubType(type = ObjectDetectionPipelineSettings.class)
|
||||
})
|
||||
public class CVPipelineSettings implements Cloneable {
|
||||
public int pipelineIndex = 0;
|
||||
@@ -153,4 +155,22 @@ public class CVPipelineSettings implements Cloneable {
|
||||
+ 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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import java.util.Objects;
|
||||
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||
import org.photonvision.common.util.numbers.IntegerCouple;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.opencv.ContourShape;
|
||||
|
||||
@JsonTypeName("ColoredShapePipelineSettings")
|
||||
public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
|
||||
public ContourShape contourShape = ContourShape.Triangle;
|
||||
public DoubleCouple contourPerimeter = new DoubleCouple(0, Double.MAX_VALUE);
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
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.vision.processes.PipelineManager;
|
||||
|
||||
@JsonTypeName("DriverModePipelineSettings")
|
||||
@Json
|
||||
public class DriverModePipelineSettings extends CVPipelineSettings {
|
||||
public DoubleCouple offsetPoint = new DoubleCouple();
|
||||
public boolean crosshair = true;
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import org.photonvision.vision.processes.PipelineManager;
|
||||
|
||||
@JsonTypeName("FocusPipelineSettings")
|
||||
public class FocusPipelineSettings extends CVPipelineSettings {
|
||||
public FocusPipelineSettings() {
|
||||
super();
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
|
||||
@JsonTypeName("ReflectivePipelineSettings")
|
||||
public class ReflectivePipelineSettings extends AdvancedPipelineSettings {
|
||||
public double contourFilterRangeX = 2;
|
||||
public double contourFilterRangeY = 2;
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import org.opencv.objdetect.Objdetect;
|
||||
|
||||
@Json
|
||||
public class UICalibrationData {
|
||||
public int videoModeIndex;
|
||||
public int count;
|
||||
@@ -53,11 +55,18 @@ public class UICalibrationData {
|
||||
this.tagFamily = tagFamily;
|
||||
}
|
||||
|
||||
@Json
|
||||
public enum BoardType {
|
||||
CHESSBOARD,
|
||||
CHARUCOBOARD,
|
||||
CHARUCOBOARD;
|
||||
|
||||
@Json.Value
|
||||
int toValue() {
|
||||
return ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
@Json
|
||||
public enum TagFamily {
|
||||
Dict_4X4_1000(Objdetect.DICT_4X4_1000),
|
||||
Dict_5X5_1000(Objdetect.DICT_5X5_1000),
|
||||
@@ -75,6 +84,11 @@ public class UICalibrationData {
|
||||
private TagFamily(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Json.Value
|
||||
int toValue() {
|
||||
return ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import org.opencv.core.Size;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
@@ -591,23 +592,23 @@ public class VisionModule {
|
||||
ret.fpsLimit = this.fpsLimit;
|
||||
|
||||
// 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();
|
||||
|
||||
for (var k : videoModes.entrySet()) {
|
||||
for (var videoMode : videoModes) {
|
||||
var internalMap = new HashMap<String, Object>();
|
||||
|
||||
internalMap.put("width", k.getValue().width);
|
||||
internalMap.put("height", k.getValue().height);
|
||||
internalMap.put("fps", k.getValue().fps);
|
||||
internalMap.put("width", videoMode.width);
|
||||
internalMap.put("height", videoMode.height);
|
||||
internalMap.put("fps", videoMode.fps);
|
||||
internalMap.put(
|
||||
"pixelFormat",
|
||||
((k.getValue() instanceof LibcameraGpuSource.FPSRatedVideoMode)
|
||||
((videoMode instanceof LibcameraGpuSource.FPSRatedVideoMode)
|
||||
? "kPicam"
|
||||
: k.getValue().pixelFormat.toString())
|
||||
: videoMode.pixelFormat.toString())
|
||||
.substring(1)); // Remove the k prefix
|
||||
|
||||
temp.put(k.getKey(), internalMap);
|
||||
temp.add(internalMap);
|
||||
}
|
||||
|
||||
if (videoModes.size() == 0) {
|
||||
@@ -728,7 +729,7 @@ public class VisionModule {
|
||||
|
||||
public void addCalibrationToConfig(CameraCalibrationCoefficients newCalibration) {
|
||||
if (newCalibration != null) {
|
||||
logger.info("Got new calibration for " + newCalibration.unrotatedImageSize);
|
||||
logger.info("Got new calibration for " + newCalibration.resolution);
|
||||
visionSource.getSettables().addCalibration(newCalibration);
|
||||
} else {
|
||||
logger.error("Got null calibration?");
|
||||
@@ -737,9 +738,9 @@ public class VisionModule {
|
||||
saveAndBroadcastAll();
|
||||
}
|
||||
|
||||
public void removeCalibrationFromConfig(Size unrotatedImageSize) {
|
||||
if (unrotatedImageSize != null) {
|
||||
visionSource.getSettables().removeCalibration(unrotatedImageSize);
|
||||
public void removeCalibrationFromConfig(Size resolution) {
|
||||
if (resolution != null) {
|
||||
visionSource.getSettables().removeCalibration(resolution);
|
||||
} else {
|
||||
logger.error("Got null size?");
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.photonvision.vision.processes;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
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.logging.LogGroup;
|
||||
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.IntegerCouple;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
@@ -57,10 +56,10 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
}
|
||||
|
||||
@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
|
||||
// cameras)
|
||||
if (event instanceof IncomingWebSocketEvent wsEvent
|
||||
if (event instanceof IncomingWebSocketEvent<T> wsEvent
|
||||
&& wsEvent.cameraUniqueName != null
|
||||
&& wsEvent.cameraUniqueName.equals(parentModule.uniqueName())) {
|
||||
logger.trace("Got PSC event - propName: " + wsEvent.propertyName);
|
||||
@@ -68,7 +67,7 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
try {
|
||||
getSettingChanges()
|
||||
.add(
|
||||
new VisionModuleChange(
|
||||
new VisionModuleChange<T>(
|
||||
wsEvent.propertyName,
|
||||
wsEvent.data,
|
||||
parentModule.pipelineManager.getCurrentPipeline().getSettings(),
|
||||
@@ -92,6 +91,10 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
var newPropValue = change.getNewPropValue();
|
||||
var currentSettings = change.getCurrentSettings();
|
||||
var originContext = change.getOriginContext();
|
||||
if (newPropValue instanceof Long) {
|
||||
newPropValue = ((Long) newPropValue).intValue();
|
||||
}
|
||||
|
||||
switch (propName) {
|
||||
case "pipelineName" -> newPipelineNickname((String) newPropValue);
|
||||
case "newPipelineInfo" -> newPipelineInfo((Pair<String, PipelineType>) newPropValue);
|
||||
@@ -199,7 +202,7 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
|
||||
public void startCalibration(Map<String, Object> data) {
|
||||
try {
|
||||
var deserialized = JacksonUtils.deserialize(data, UICalibrationData.class);
|
||||
var deserialized = Jsonb.instance().type(UICalibrationData.class).fromObject(data);
|
||||
parentModule.startCalibration(deserialized);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
} catch (Exception e) {
|
||||
@@ -295,8 +298,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
}
|
||||
} else if (propField.getType() == ModelProperties.class
|
||||
&& newPropValue instanceof LinkedHashMap) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ModelProperties modelProps = mapper.convertValue(newPropValue, ModelProperties.class);
|
||||
ModelProperties modelProps =
|
||||
Jsonb.instance().type(ModelProperties.class).fromObject(newPropValue);
|
||||
propField.set(currentSettings, modelProps);
|
||||
} else {
|
||||
propField.set(currentSettings, newPropValue);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.photonvision.vision.processes;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -72,7 +73,7 @@ public class VisionSourceManager {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
// Jackson does use these members even if your IDE claims otherwise
|
||||
@Json
|
||||
public static class VisionSourceManagerState {
|
||||
public List<UICameraConfiguration> disabledConfigs;
|
||||
public List<PVCameraInfo> allConnectedCameras;
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
package org.photonvision.vision.processes;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.opencv.core.Size;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
@@ -38,7 +39,7 @@ public abstract class VisionSourceSettables {
|
||||
}
|
||||
|
||||
protected FrameStaticProperties frameStaticProperties = null;
|
||||
protected HashMap<Integer, VideoMode> videoModes = new HashMap<>();
|
||||
protected List<VideoMode> videoModes = new ArrayList<>();
|
||||
|
||||
public CameraConfiguration getConfiguration() {
|
||||
return configuration;
|
||||
@@ -99,12 +100,11 @@ public abstract class VisionSourceSettables {
|
||||
|
||||
protected abstract void setVideoModeInternal(VideoMode videoMode);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setVideoModeIndex(int index) {
|
||||
setVideoMode(videoModes.get(index));
|
||||
}
|
||||
|
||||
public abstract HashMap<Integer, VideoMode> getAllVideoModes();
|
||||
public abstract List<VideoMode> getAllVideoModes();
|
||||
|
||||
public double getFOV() {
|
||||
return configuration.FOV;
|
||||
@@ -121,8 +121,8 @@ public abstract class VisionSourceSettables {
|
||||
calculateFrameStaticProps();
|
||||
}
|
||||
|
||||
public void removeCalibration(Size unrotatedImageSize) {
|
||||
configuration.removeCalibration(unrotatedImageSize);
|
||||
public void removeCalibration(Size resolution) {
|
||||
configuration.removeCalibration(resolution);
|
||||
calculateFrameStaticProps();
|
||||
}
|
||||
|
||||
@@ -135,8 +135,8 @@ public abstract class VisionSourceSettables {
|
||||
configuration.calibrations.stream()
|
||||
.filter(
|
||||
it ->
|
||||
it.unrotatedImageSize.width == videoMode.width
|
||||
&& it.unrotatedImageSize.height == videoMode.height)
|
||||
it.resolution.width == videoMode.width
|
||||
&& it.resolution.height == videoMode.height)
|
||||
.findFirst()
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
|
||||
package org.photonvision.vision.target;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.avaje.jsonb.Json;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.
|
||||
*/
|
||||
@Json
|
||||
public enum TargetModel implements Releasable {
|
||||
k2016HighGoal(
|
||||
List.of(
|
||||
@@ -129,7 +127,8 @@ public enum TargetModel implements Releasable {
|
||||
-Units.inchesToMeters(16.25) / 2)),
|
||||
0),
|
||||
// 2023 AprilTag, with 6 inch marker width (inner black square).
|
||||
@JsonAlias({"k6in_16h5"})
|
||||
// MIGRATION: 2023
|
||||
@Json.Alias({"k6in_16h5"})
|
||||
kAprilTag6in_16h5(
|
||||
// Corners of the tag's inner black square (excluding white border)
|
||||
List.of(
|
||||
@@ -139,7 +138,8 @@ public enum TargetModel implements Releasable {
|
||||
new Point3(Units.inchesToMeters(3), -Units.inchesToMeters(3), 0)),
|
||||
Units.inchesToMeters(3 * 2)),
|
||||
// 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(
|
||||
// Corners of the tag's inner black square (excluding white border)
|
||||
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)),
|
||||
Units.inchesToMeters(6.5));
|
||||
|
||||
@JsonIgnore private MatOfPoint3f realWorldTargetCoordinates;
|
||||
@JsonIgnore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
|
||||
@JsonIgnore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
|
||||
@Json.Ignore private final MatOfPoint3f realWorldTargetCoordinates;
|
||||
@Json.Ignore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
|
||||
@Json.Ignore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
|
||||
|
||||
@JsonProperty("realWorldCoordinatesArray")
|
||||
private List<Point3> realWorldCoordinatesArray;
|
||||
|
||||
@JsonProperty("boxHeight")
|
||||
private double boxHeight;
|
||||
|
||||
TargetModel() {}
|
||||
|
||||
TargetModel(MatOfPoint3f realWorldTargetCoordinates, double boxHeight) {
|
||||
this.realWorldTargetCoordinates = realWorldTargetCoordinates;
|
||||
this.realWorldCoordinatesArray = realWorldTargetCoordinates.toList();
|
||||
@@ -176,11 +171,8 @@ public enum TargetModel implements Releasable {
|
||||
this.visualizationBoxTop.fromList(topList);
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
TargetModel(
|
||||
@JsonProperty(value = "realWorldCoordinatesArray") List<Point3> points,
|
||||
@JsonProperty(value = "boxHeight") double boxHeight) {
|
||||
this(listToMat(points), boxHeight);
|
||||
TargetModel(List<Point3> realWorldCoordinatesArray, double boxHeight) {
|
||||
this(listToMat(realWorldCoordinatesArray), boxHeight);
|
||||
}
|
||||
|
||||
public List<Point3> getRealWorldCoordinatesArray() {
|
||||
@@ -227,6 +219,12 @@ public enum TargetModel implements Releasable {
|
||||
// return new TargetModel(corners, 0);
|
||||
// }
|
||||
|
||||
@Json.Value
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void 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.assertTrue;
|
||||
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
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.LogLevel;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
import org.photonvision.vision.camera.PVCameraInfo;
|
||||
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
|
||||
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
||||
@@ -135,60 +134,31 @@ public class ConfigTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJacksonHandlesOldVersions() throws IOException {
|
||||
var str =
|
||||
public void testJsonbHandlesOldVersions() throws IOException {
|
||||
var json =
|
||||
"{\"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
|
||||
public void testJacksonAddUSBVIDPID() throws IOException {
|
||||
var str =
|
||||
public void testJsonbAddUSBVIDPID() throws IOException {
|
||||
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\":[]}";
|
||||
File tempFile = File.createTempFile("test", ".json");
|
||||
tempFile.deleteOnExit();
|
||||
var writer = new FileWriter(tempFile);
|
||||
writer.write(str);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
|
||||
try {
|
||||
CameraConfiguration result =
|
||||
JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
|
||||
String ser = JacksonUtils.serializeToString(result);
|
||||
CameraConfiguration result = Jsonb.instance().type(CameraConfiguration.class).fromJson(json);
|
||||
String ser = Jsonb.instance().toJson(result);
|
||||
System.out.println(ser);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
tempFile.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJacksonHandlesOldTargetEnum() throws IOException {
|
||||
var str = "[ \"AprilTagPipelineSettings\", {\n \"targetModel\" : \"k6in_16h5\"\n} ]\n";
|
||||
public void testJsonbHandlesOldTargetEnum() throws IOException {
|
||||
var json = "[ \"AprilTagPipelineSettings\", {\n \"targetModel\" : \"k6in_16h5\"\n} ]\n";
|
||||
|
||||
File tempFile = File.createTempFile("test", ".json");
|
||||
tempFile.deleteOnExit();
|
||||
var writer = new FileWriter(tempFile);
|
||||
writer.write(str);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
json = CVPipelineSettings.remapSettingsJson(json);
|
||||
|
||||
AprilTagPipelineSettings settings =
|
||||
(AprilTagPipelineSettings)
|
||||
JacksonUtils.deserialize(tempFile.toPath(), CVPipelineSettings.class);
|
||||
(AprilTagPipelineSettings) Jsonb.instance().type(CVPipelineSettings.class).fromJson(json);
|
||||
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.assertEquals;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.jsonb.JsonType;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -30,26 +33,30 @@ import org.photonvision.common.util.TestUtils;
|
||||
public class NetworkConfigTest {
|
||||
@Test
|
||||
public void testSerialization() throws IOException {
|
||||
var mapper = new ObjectMapper();
|
||||
var path = Path.of("netTest.json");
|
||||
mapper.writeValue(path.toFile(), new NetworkConfig());
|
||||
assertDoesNotThrow(() -> mapper.readValue(path.toFile(), NetworkConfig.class));
|
||||
JsonType<NetworkConfig> jsonb = Jsonb.instance().type(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();
|
||||
}
|
||||
|
||||
@Test
|
||||
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));
|
||||
configMgr.load();
|
||||
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));
|
||||
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.assertEquals;
|
||||
|
||||
import io.avaje.jsonb.JsonType;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedList;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
|
||||
import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
|
||||
public class NeuralNetworkPropertyManagerTest {
|
||||
@Test
|
||||
@@ -42,10 +43,12 @@ public class NeuralNetworkPropertyManagerTest {
|
||||
640,
|
||||
Family.RKNN,
|
||||
Version.YOLOV8));
|
||||
String result = assertDoesNotThrow(() -> JacksonUtils.serializeToString(nnpm));
|
||||
var deserializedNnpm =
|
||||
assertDoesNotThrow(
|
||||
() -> JacksonUtils.deserialize(result, NeuralNetworkModelsSettings.class));
|
||||
JsonType<NeuralNetworkModelsSettings> jsonb =
|
||||
Jsonb.instance().type(NeuralNetworkModelsSettings.class);
|
||||
String result = assertDoesNotThrow(() -> jsonb.toJson(nnpm));
|
||||
System.out.println(result);
|
||||
var deserializedNnpm = assertDoesNotThrow(() -> jsonb.fromJson(result));
|
||||
assertEquals(nnpm.getModels().length, deserializedNnpm.getModels().length);
|
||||
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.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.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
@@ -32,8 +33,8 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.photonvision.common.LoadJNI;
|
||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.vision.camera.CameraQuirk;
|
||||
import org.photonvision.vision.camera.PVCameraInfo;
|
||||
import org.photonvision.vision.pipeline.AdvancedPipelineSettings;
|
||||
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
|
||||
@@ -92,32 +93,6 @@ public class SQLConfigTest {
|
||||
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) {
|
||||
// Make sure we got 8 cameras
|
||||
assertEquals(8, config.getCameraConfigurations().size());
|
||||
@@ -134,7 +109,7 @@ public class SQLConfigTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadNewNNMM() throws JsonProcessingException, IOException {
|
||||
public void testLoadNewNNMM() throws JsonDataException, IOException {
|
||||
var folder = tmpDir.resolve("2025.3.1-old-nnmm");
|
||||
FileUtils.copyDirectory(
|
||||
TestUtils.getConfigDirectoriesPath(false).resolve("2025.3.1-old-nnmm").toFile(),
|
||||
@@ -165,7 +140,7 @@ public class SQLConfigTest {
|
||||
common2025p3p1Assertions(reloadedProvider.getConfig());
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -210,4 +185,31 @@ public class SQLConfigTest {
|
||||
|
||||
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 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 org.junit.jupiter.api.Test;
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
@@ -33,10 +34,9 @@ import org.photonvision.common.util.TestUtils;
|
||||
public class HardwareConfigTest {
|
||||
@Test
|
||||
public void loadJson() {
|
||||
try {
|
||||
System.out.println("Loading Hardware configs...");
|
||||
var config =
|
||||
new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class);
|
||||
try (var stream = new FileInputStream(TestUtils.getHardwareConfigJson())) {
|
||||
var config = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
|
||||
assertEquals(config.deviceName, "PhotonVision");
|
||||
// Ensure defaults are not null
|
||||
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.spi.NativeDeviceFactoryInterface;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -68,8 +69,9 @@ public class HardwareTest {
|
||||
@BeforeEach
|
||||
void setup() throws IOException {
|
||||
System.out.println("Loading Hardware configs...");
|
||||
hardwareConfig =
|
||||
new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class);
|
||||
try (var stream = new FileInputStream(TestUtils.getHardwareConfigJson())) {
|
||||
hardwareConfig = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
|
||||
}
|
||||
deviceFactory = HardwareManager.configureCustomGPIO(hardwareConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -128,10 +128,7 @@ public class CalibrationRotationPipeTest {
|
||||
|
||||
FrameStaticProperties frameProps =
|
||||
new FrameStaticProperties(
|
||||
(int) coeffs.unrotatedImageSize.width,
|
||||
(int) coeffs.unrotatedImageSize.height,
|
||||
66,
|
||||
coeffs);
|
||||
(int) coeffs.resolution.width, (int) coeffs.resolution.height, 66, coeffs);
|
||||
|
||||
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};
|
||||
assertArrayEquals(rotatedCamMat, coeffs2.cameraIntrinsics.data);
|
||||
// AND the image size should be the same
|
||||
assertEquals(res, coeffs2.unrotatedImageSize);
|
||||
assertEquals(res, coeffs2.resolution);
|
||||
|
||||
// WHEN the camera calibration is rotated 180 degrees
|
||||
var coeffs3 = coeffs2.rotateCoefficients(rot);
|
||||
@@ -218,7 +215,7 @@ public class CalibrationRotationPipeTest {
|
||||
// THEN the camera matrix will be the same as the original
|
||||
assertArrayEquals(intrinsics, coeffs3.cameraIntrinsics.data);
|
||||
// AND the image size should be the same
|
||||
assertEquals(res, coeffs2.unrotatedImageSize);
|
||||
assertEquals(res, coeffs2.resolution);
|
||||
}
|
||||
|
||||
@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.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -115,9 +115,9 @@ public class VisionModuleManagerTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, VideoMode> getAllVideoModes() {
|
||||
var ret = new HashMap<Integer, VideoMode>();
|
||||
ret.put(0, getCurrentVideoMode());
|
||||
public List<VideoMode> getAllVideoModes() {
|
||||
var ret = new ArrayList<VideoMode>();
|
||||
ret.add(getCurrentVideoMode());
|
||||
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.assertTrue;
|
||||
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -31,8 +32,8 @@ import org.junit.jupiter.api.Test;
|
||||
import org.photonvision.common.LoadJNI;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
import org.photonvision.common.configuration.PhotonConfiguration;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
import org.photonvision.vision.camera.PVCameraInfo;
|
||||
import org.wpilib.vision.camera.UsbCameraInfo;
|
||||
|
||||
@@ -94,17 +95,18 @@ public class VisionSourceManagerTest {
|
||||
7,
|
||||
8));
|
||||
|
||||
var str = JacksonUtils.serializeToString(usb);
|
||||
var str = Jsonb.instance().type(PVCameraInfo.class).toJson(usb);
|
||||
System.out.println(str);
|
||||
System.out.println(JacksonUtils.deserialize(str, PVCameraInfo.class));
|
||||
System.out.println(Jsonb.instance().type(PVCameraInfo.class).fromJson(str));
|
||||
}
|
||||
{
|
||||
var csi =
|
||||
PVCameraInfo.fromCSICameraInfo(
|
||||
"/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(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);
|
||||
|
||||
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
|
||||
assertEquals(1, vsm.getVsmState().allConnectedCameras.size());
|
||||
@@ -268,7 +273,10 @@ public class VisionSourceManagerTest {
|
||||
|
||||
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
|
||||
assertEquals(3, vsm.getVsmState().allConnectedCameras.size());
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
|
||||
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.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -66,6 +69,26 @@ import org.wpilib.math.util.Pair;
|
||||
* network latency and the latency reported will always be perfect.
|
||||
*/
|
||||
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();
|
||||
// calibration
|
||||
private int resWidth;
|
||||
@@ -114,48 +137,27 @@ public class SimCameraProperties {
|
||||
* calibrated resolution.
|
||||
*/
|
||||
public SimCameraProperties(Path path, int width, int height) throws IOException {
|
||||
var mapper = new ObjectMapper();
|
||||
var json = mapper.readTree(path.toFile());
|
||||
json = json.get("calibrations");
|
||||
SimCameraData data;
|
||||
try (var stream = new FileInputStream(path.toFile())) {
|
||||
data = Jsonb.instance().type(SimCameraData.class).fromJson(stream);
|
||||
} catch (JsonIoException e) {
|
||||
throw new IOException("Invalid calibration JSON", e);
|
||||
}
|
||||
boolean success = false;
|
||||
try {
|
||||
for (int i = 0; i < json.size() && !success; i++) {
|
||||
for (var calib : data.calibrations) {
|
||||
// check if this calibration entry is our desired resolution
|
||||
var calib = json.get(i);
|
||||
int jsonWidth = calib.get("resolution").get("width").asInt();
|
||||
int jsonHeight = calib.get("resolution").get("height").asInt();
|
||||
if (jsonWidth != width || jsonHeight != height) continue;
|
||||
if (calib.resolution.width != width || calib.resolution.height != height) continue;
|
||||
// get the relevant calibration values
|
||||
var jsonIntrinsicsNode = calib.get("cameraIntrinsics").get("data");
|
||||
double[] jsonIntrinsics = new double[jsonIntrinsicsNode.size()];
|
||||
for (int j = 0; j < jsonIntrinsicsNode.size(); j++) {
|
||||
jsonIntrinsics[j] = jsonIntrinsicsNode.get(j).asDouble();
|
||||
}
|
||||
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();
|
||||
double avgViewError = Arrays.stream(calib.perViewErrors).average().orElse(0);
|
||||
// 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);
|
||||
calib.resolution.width,
|
||||
calib.resolution.height,
|
||||
MatBuilder.fill(Nat.N3(), Nat.N3(), calib.cameraIntrinsics.data),
|
||||
MatBuilder.fill(Nat.N8(), Nat.N1(), calib.distCoeffs.data));
|
||||
setCalibError(avgViewError, calib.standardDeviation);
|
||||
success = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Invalid calibration JSON");
|
||||
}
|
||||
if (!success) throw new IOException("Requested resolution not found in calibration");
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ shadowJar {
|
||||
configurations = [
|
||||
project.configurations.runtimeClasspath
|
||||
]
|
||||
mergeServiceFiles()
|
||||
}
|
||||
|
||||
node {
|
||||
|
||||
@@ -235,6 +235,8 @@ public class Main {
|
||||
Logger.setLevel(LogGroup.General, logLevel);
|
||||
logger.info("Logging initialized in debug mode.");
|
||||
|
||||
System.setProperty("jsonb.disableAdapterSpi", "true");
|
||||
|
||||
logger.info(
|
||||
"Starting PhotonVision version "
|
||||
+ PhotonVersion.versionString
|
||||
|
||||
@@ -17,22 +17,21 @@
|
||||
|
||||
package org.photonvision.server;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.json.JsonException;
|
||||
import io.avaje.jsonb.Json;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import io.avaje.jsonb.jackson.JacksonAdapter;
|
||||
import io.javalin.websocket.WsBinaryMessageContext;
|
||||
import io.javalin.websocket.WsCloseContext;
|
||||
import io.javalin.websocket.WsConnectContext;
|
||||
import io.javalin.websocket.WsContext;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.msgpack.jackson.dataformat.MessagePackFactory;
|
||||
import org.photonvision.common.dataflow.DataChangeDestination;
|
||||
import org.photonvision.common.dataflow.DataChangeService;
|
||||
@@ -47,7 +46,9 @@ import org.wpilib.math.util.Pair;
|
||||
public class DataSocketHandler {
|
||||
private final Logger logger = new Logger(DataSocketHandler.class, LogGroup.WebServer);
|
||||
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();
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
@@ -91,20 +92,16 @@ public class DataSocketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Json
|
||||
static record WSMessage(
|
||||
@Nullable String cameraUniqueName, @Json.Unmapped Map<String, Object> properties) {}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public void onBinaryMessage(WsBinaryMessageContext context) {
|
||||
try {
|
||||
Map<String, Object> deserializedData =
|
||||
objectMapper.readValue(context.data(), new TypeReference<>() {});
|
||||
var message = msgpackJsonb.type(WSMessage.class).fromJson(context.data());
|
||||
|
||||
// Special case the current camera index
|
||||
String cameraUniqueName = "";
|
||||
if (deserializedData.get("cameraUniqueName") instanceof String camUniqueNameValue) {
|
||||
cameraUniqueName = camUniqueNameValue;
|
||||
deserializedData.remove("cameraUniqueName");
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : deserializedData.entrySet()) {
|
||||
for (Map.Entry<String, Object> entry : message.properties.entrySet()) {
|
||||
try {
|
||||
var entryKey = entry.getKey();
|
||||
var entryValue = entry.getValue();
|
||||
@@ -131,7 +128,7 @@ public class DataSocketHandler {
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"isDriverMode",
|
||||
(Boolean) entryValue,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_CHANGECAMERANAME ->
|
||||
dcService.publishEvent(
|
||||
@@ -139,7 +136,7 @@ public class DataSocketHandler {
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"cameraNickname",
|
||||
(String) entryValue,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_CHANGEPIPELINENAME ->
|
||||
dcService.publishEvent(
|
||||
@@ -147,38 +144,39 @@ public class DataSocketHandler {
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"pipelineName",
|
||||
(String) entryValue,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_ADDNEWPIPELINE -> {
|
||||
// HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
|
||||
// var type = (PipelineType) data.get("pipelineType");
|
||||
// var name = (String) data.get("pipelineName");
|
||||
var arr = (ArrayList<Object>) entryValue;
|
||||
var arr = (List<Object>) entryValue;
|
||||
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(
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"newPipelineInfo",
|
||||
Pair.of(name, type),
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
}
|
||||
case SMT_CHANGEBRIGHTNESS ->
|
||||
HardwareManager.getInstance()
|
||||
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
|
||||
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(
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"duplicatePipeline",
|
||||
pipeIndex,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
}
|
||||
case SMT_DELETECURRENTPIPELINE ->
|
||||
@@ -187,27 +185,29 @@ public class DataSocketHandler {
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"deleteCurrPipeline",
|
||||
0,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_ROBOTOFFSETPOINT ->
|
||||
dcService.publishEvent(
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"robotOffsetPoint",
|
||||
(Integer) entryValue,
|
||||
cameraUniqueName,
|
||||
((Long) entryValue).intValue(),
|
||||
message.cameraUniqueName,
|
||||
null));
|
||||
case SMT_CURRENTCAMERA ->
|
||||
dcService.publishEvent(
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_OTHER, "changeUICamera", (Integer) entryValue));
|
||||
DataChangeDestination.DCD_OTHER,
|
||||
"changeUICamera",
|
||||
((Long) entryValue).intValue()));
|
||||
case SMT_CURRENTPIPELINE ->
|
||||
dcService.publishEvent(
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"changePipeline",
|
||||
(Integer) entryValue,
|
||||
cameraUniqueName,
|
||||
((Long) entryValue).intValue(),
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_STARTPNPCALIBRATION ->
|
||||
dcService.publishEvent(
|
||||
@@ -215,7 +215,7 @@ public class DataSocketHandler {
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"startCalibration",
|
||||
(Map) entryValue,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_SAVEINPUTSNAPSHOT ->
|
||||
dcService.publishEvent(
|
||||
@@ -223,7 +223,7 @@ public class DataSocketHandler {
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"saveInputSnapshot",
|
||||
0,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_SAVEOUTPUTSNAPSHOT ->
|
||||
dcService.publishEvent(
|
||||
@@ -231,7 +231,7 @@ public class DataSocketHandler {
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"saveOutputSnapshot",
|
||||
0,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_TAKECALIBRATIONSNAPSHOT ->
|
||||
dcService.publishEvent(
|
||||
@@ -239,10 +239,10 @@ public class DataSocketHandler {
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"takeCalSnapshot",
|
||||
0,
|
||||
cameraUniqueName,
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
case SMT_PIPELINESETTINGCHANGE -> {
|
||||
HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
|
||||
Map<String, Object> data = (Map) entryValue;
|
||||
|
||||
if (data.size() >= 2) {
|
||||
var cameraIndex2 = (String) data.get("cameraUniqueName");
|
||||
@@ -267,28 +267,27 @@ public class DataSocketHandler {
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"changePipelineType",
|
||||
(Integer) entryValue,
|
||||
cameraUniqueName,
|
||||
((Long) entryValue).intValue(),
|
||||
message.cameraUniqueName,
|
||||
context));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse message!", e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (IllegalStateException | JsonException 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()) {
|
||||
user.send(b);
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastMessage(Object message, WsContext userToSkip)
|
||||
throws JsonProcessingException {
|
||||
ByteBuffer b = ByteBuffer.wrap(objectMapper.writeValueAsBytes(message));
|
||||
public void broadcastMessage(Object message, WsContext userToSkip) throws JsonException {
|
||||
ByteBuffer b = ByteBuffer.wrap(msgpackJsonb.toJsonBytes(message));
|
||||
|
||||
if (userToSkip == null) {
|
||||
for (WsContext user : users) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum DataSocketMessageType {
|
||||
SMT_DRIVERMODE("driverMode"),
|
||||
SMT_CHANGECAMERANAME("changeCameraName"),
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
|
||||
package org.photonvision.server;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.json.JsonException;
|
||||
import io.avaje.jsonb.Json;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.http.UploadedFile;
|
||||
import java.io.*;
|
||||
@@ -53,7 +54,6 @@ import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.networking.NetworkManager;
|
||||
import org.photonvision.common.util.ShellExec;
|
||||
import org.photonvision.common.util.TimedTaskManager;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
import org.photonvision.common.util.file.ProgramDirectoryUtilities;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
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 ObjectMapper kObjectMapper = new ObjectMapper();
|
||||
|
||||
private static boolean testMode = false;
|
||||
|
||||
public static void onStatusRequest(Context ctx) {
|
||||
@@ -84,7 +82,8 @@ public class RequestHandler {
|
||||
testMode = isTestMode;
|
||||
}
|
||||
|
||||
private record CommonCameraUniqueName(String cameraUniqueName) {}
|
||||
@Json
|
||||
record CommonCameraUniqueName(String cameraUniqueName) {}
|
||||
|
||||
public static void onSettingsImportRequest(Context ctx) {
|
||||
var file = ctx.uploadedFile("data");
|
||||
@@ -372,12 +371,12 @@ public class RequestHandler {
|
||||
public static void onGeneralSettingsRequest(Context ctx) {
|
||||
NetworkConfig config;
|
||||
try {
|
||||
config = kObjectMapper.readValue(ctx.bodyInputStream(), NetworkConfig.class);
|
||||
config = Jsonb.instance().type(NetworkConfig.class).fromJson(ctx.bodyInputStream());
|
||||
|
||||
ctx.status(200);
|
||||
ctx.result("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
|
||||
config = new NetworkConfig();
|
||||
|
||||
@@ -394,13 +393,14 @@ public class RequestHandler {
|
||||
NetworkTablesManager.getInstance().setConfig(config);
|
||||
}
|
||||
|
||||
private record CameraSettingsRequest(
|
||||
@Json
|
||||
record CameraSettingsRequest(
|
||||
double fov, HashMap<CameraQuirk, Boolean> quirksToChange, String cameraUniqueName) {}
|
||||
|
||||
public static void onCameraSettingsRequest(Context ctx) {
|
||||
try {
|
||||
CameraSettingsRequest request =
|
||||
kObjectMapper.readValue(ctx.body(), CameraSettingsRequest.class);
|
||||
Jsonb.instance().type(CameraSettingsRequest.class).fromJson(ctx.body());
|
||||
// Extract the settings from the request
|
||||
double fov = request.fov;
|
||||
HashMap<CameraQuirk, Boolean> quirksToChange = request.quirksToChange;
|
||||
@@ -489,7 +489,7 @@ public class RequestHandler {
|
||||
|
||||
try {
|
||||
CommonCameraUniqueName request =
|
||||
kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
|
||||
Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
|
||||
|
||||
var calData =
|
||||
VisionSourceManager.getInstance()
|
||||
@@ -509,7 +509,7 @@ public class RequestHandler {
|
||||
ctx.result("Camera calibration successfully completed!");
|
||||
ctx.status(200);
|
||||
logger.info("Camera calibration successfully completed!");
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
ctx.status(400);
|
||||
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.");
|
||||
@@ -523,13 +523,14 @@ public class RequestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private record DataCalibrationImportRequest(
|
||||
@Json
|
||||
record DataCalibrationImportRequest(
|
||||
String cameraUniqueName, CameraCalibrationCoefficients calibration) {}
|
||||
|
||||
public static void onDataCalibrationImportRequest(Context ctx) {
|
||||
try {
|
||||
try (var stream = ctx.req().getInputStream()) {
|
||||
DataCalibrationImportRequest request =
|
||||
kObjectMapper.readValue(ctx.req().getInputStream(), DataCalibrationImportRequest.class);
|
||||
Jsonb.instance().type(DataCalibrationImportRequest.class).fromJson(stream);
|
||||
|
||||
var uploadCalibrationEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
@@ -543,7 +544,7 @@ public class RequestHandler {
|
||||
ctx.status(200);
|
||||
ctx.result("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.result("The provided calibration data was malformed");
|
||||
logger.error("The provided calibration data was malformed", e);
|
||||
@@ -704,11 +705,10 @@ public class RequestHandler {
|
||||
}
|
||||
ConfigManager.getInstance()
|
||||
.getConfig()
|
||||
.neuralNetworkPropertyManager()
|
||||
.getNeuralNetworkProperties()
|
||||
.addModelProperties(modelProperties);
|
||||
|
||||
logger.debug(
|
||||
ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().toString());
|
||||
logger.debug(ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().toString());
|
||||
|
||||
NeuralNetworkModelManager.getInstance().discoverModels();
|
||||
|
||||
@@ -860,14 +860,15 @@ public class RequestHandler {
|
||||
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
||||
}
|
||||
|
||||
private record DeleteObjectDetectionModelRequest(Path modelPath) {}
|
||||
@Json
|
||||
record DeleteObjectDetectionModelRequest(Path modelPath) {}
|
||||
|
||||
public static void onDeleteObjectDetectionModelRequest(Context ctx) {
|
||||
logger.info("Deleting object detection model");
|
||||
|
||||
try {
|
||||
DeleteObjectDetectionModelRequest request =
|
||||
JacksonUtils.deserialize(ctx.body(), DeleteObjectDetectionModelRequest.class);
|
||||
Jsonb.instance().type(DeleteObjectDetectionModelRequest.class).fromJson(ctx.body());
|
||||
|
||||
if (request.modelPath == null) {
|
||||
ctx.status(400);
|
||||
@@ -892,7 +893,7 @@ public class RequestHandler {
|
||||
|
||||
if (!ConfigManager.getInstance()
|
||||
.getConfig()
|
||||
.neuralNetworkPropertyManager()
|
||||
.getNeuralNetworkProperties()
|
||||
.removeModel(request.modelPath)) {
|
||||
ctx.status(400);
|
||||
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) {
|
||||
try {
|
||||
RenameObjectDetectionModelRequest request =
|
||||
JacksonUtils.deserialize(ctx.body(), RenameObjectDetectionModelRequest.class);
|
||||
Jsonb.instance().type(RenameObjectDetectionModelRequest.class).fromJson(ctx.body());
|
||||
|
||||
if (request.modelPath == null) {
|
||||
ctx.status(400);
|
||||
@@ -947,7 +949,7 @@ public class RequestHandler {
|
||||
|
||||
if (!ConfigManager.getInstance()
|
||||
.getConfig()
|
||||
.neuralNetworkPropertyManager()
|
||||
.getNeuralNetworkProperties()
|
||||
.renameModel(request.modelPath, request.newName)) {
|
||||
ctx.status(400);
|
||||
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);
|
||||
}
|
||||
|
||||
private record CameraNicknameChangeRequest(String name, String cameraUniqueName) {}
|
||||
@Json
|
||||
record CameraNicknameChangeRequest(String name, String cameraUniqueName) {}
|
||||
|
||||
public static void onCameraNicknameChangeRequest(Context ctx) {
|
||||
try {
|
||||
CameraNicknameChangeRequest request =
|
||||
kObjectMapper.readValue(ctx.body(), CameraNicknameChangeRequest.class);
|
||||
Jsonb.instance().type(CameraNicknameChangeRequest.class).fromJson(ctx.body());
|
||||
|
||||
VisionSourceManager.getInstance()
|
||||
.vmm
|
||||
@@ -1008,7 +1011,7 @@ public class RequestHandler {
|
||||
ctx.status(200);
|
||||
ctx.result("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");
|
||||
logger.error("Failed to process camera nickname change request", e);
|
||||
} catch (Exception e) {
|
||||
@@ -1038,8 +1041,8 @@ public class RequestHandler {
|
||||
module.getStateAsCameraConfig().calibrations.stream()
|
||||
.filter(
|
||||
it ->
|
||||
Math.abs(it.unrotatedImageSize.width - width) < 1e-4
|
||||
&& Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
|
||||
Math.abs(it.resolution.width - width) < 1e-4
|
||||
&& Math.abs(it.resolution.height - height) < 1e-4)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
@@ -1052,12 +1055,13 @@ public class RequestHandler {
|
||||
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) {
|
||||
try {
|
||||
CalibrationRemoveRequest request =
|
||||
kObjectMapper.readValue(ctx.body(), CalibrationRemoveRequest.class);
|
||||
Jsonb.instance().type(CalibrationRemoveRequest.class).fromJson(ctx.body());
|
||||
|
||||
logger.info(
|
||||
"Attempting to remove calibration for camera: "
|
||||
@@ -1083,7 +1087,7 @@ public class RequestHandler {
|
||||
+ request.width
|
||||
+ "x"
|
||||
+ request.height);
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
ctx.status(400).result("Invalid JSON format");
|
||||
logger.error("Failed to process calibration removed request", e);
|
||||
} catch (Exception e) {
|
||||
@@ -1107,8 +1111,8 @@ public class RequestHandler {
|
||||
.stream()
|
||||
.filter(
|
||||
it ->
|
||||
Math.abs(it.unrotatedImageSize.width - width) < 1e-4
|
||||
&& Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
|
||||
Math.abs(it.resolution.width - width) < 1e-4
|
||||
&& Math.abs(it.resolution.height - height) < 1e-4)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
@@ -1146,8 +1150,8 @@ public class RequestHandler {
|
||||
cc.calibrations.stream()
|
||||
.filter(
|
||||
it ->
|
||||
Math.abs(it.unrotatedImageSize.width - width) < 1e-4
|
||||
&& Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
|
||||
Math.abs(it.resolution.width - width) < 1e-4
|
||||
&& Math.abs(it.resolution.height - height) < 1e-4)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
@@ -1319,7 +1323,7 @@ public class RequestHandler {
|
||||
public static void onNukeOneCamera(Context ctx) {
|
||||
try {
|
||||
CommonCameraUniqueName request =
|
||||
kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
|
||||
Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
|
||||
|
||||
logger.warn("Deleting camera name " + request.cameraUniqueName);
|
||||
|
||||
@@ -1334,7 +1338,7 @@ public class RequestHandler {
|
||||
VisionSourceManager.getInstance().deleteVisionSource(request.cameraUniqueName);
|
||||
|
||||
ctx.status(200);
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | IllegalStateException | JsonException e) {
|
||||
logger.error("Failed to delete camera", e);
|
||||
ctx.status(500);
|
||||
ctx.result("Failed to delete camera");
|
||||
@@ -1346,7 +1350,7 @@ public class RequestHandler {
|
||||
logger.info(ctx.queryString());
|
||||
try {
|
||||
CommonCameraUniqueName request =
|
||||
kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
|
||||
Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
|
||||
|
||||
if (VisionSourceManager.getInstance()
|
||||
.reactivateDisabledCameraConfig(request.cameraUniqueName)) {
|
||||
@@ -1354,7 +1358,7 @@ public class RequestHandler {
|
||||
} else {
|
||||
ctx.status(403);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
ctx.status(401);
|
||||
logger.error("Failed to process activate matched camera request", e);
|
||||
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) {
|
||||
logger.info(ctx.queryString());
|
||||
|
||||
try {
|
||||
AssignUnmatchedCamera request =
|
||||
kObjectMapper.readValue(ctx.body(), AssignUnmatchedCamera.class);
|
||||
Jsonb.instance().type(AssignUnmatchedCamera.class).fromJson(ctx.body());
|
||||
|
||||
if (request.cameraInfo == null) {
|
||||
ctx.status(400);
|
||||
@@ -1385,7 +1390,7 @@ public class RequestHandler {
|
||||
}
|
||||
|
||||
ctx.result("Successfully assigned camera: " + request.cameraInfo);
|
||||
} catch (IOException e) {
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
ctx.status(401);
|
||||
logger.error("Failed to process assign unmatched camera request", e);
|
||||
ctx.result("Failed to process assign unmatched camera request");
|
||||
@@ -1397,14 +1402,14 @@ public class RequestHandler {
|
||||
logger.info(ctx.queryString());
|
||||
try {
|
||||
CommonCameraUniqueName request =
|
||||
kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
|
||||
Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
|
||||
|
||||
if (VisionSourceManager.getInstance().deactivateVisionSource(request.cameraUniqueName)) {
|
||||
ctx.status(200);
|
||||
} else {
|
||||
ctx.status(403);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
ctx.status(401);
|
||||
logger.error("Failed to process unassign camera request", e);
|
||||
ctx.result("Failed to process unassign camera request");
|
||||
|
||||
@@ -41,7 +41,7 @@ public class Server {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChangeEvent(DataChangeEvent<?> event) {
|
||||
public <T> void onDataChangeEvent(DataChangeEvent<T> event) {
|
||||
if (event.propertyName.equals("restartServer")) {
|
||||
Server.restart();
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
|
||||
package org.photonvision.server;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import io.javalin.http.Context;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
import org.photonvision.common.configuration.NeuralNetworkModelManager;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
|
||||
public class TestRequestHandler {
|
||||
// Treat all 2XX calls as "INFO"
|
||||
@@ -39,12 +40,13 @@ public class TestRequestHandler {
|
||||
ConfigManager.getInstance().load();
|
||||
}
|
||||
|
||||
private record PlatformOverrideRequest(Platform platform) {}
|
||||
@Json
|
||||
record PlatformOverrideRequest(Platform platform) {}
|
||||
|
||||
public static void handlePlatformOverrideRequest(Context ctx) {
|
||||
try {
|
||||
PlatformOverrideRequest request =
|
||||
JacksonUtils.deserialize(ctx.body(), PlatformOverrideRequest.class);
|
||||
Jsonb.instance().type(PlatformOverrideRequest.class).fromJson(ctx.body());
|
||||
Platform platform = request.platform();
|
||||
logger.info("Overriding platform to: " + platform);
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ public class UIInboundSubscriber extends DataChangeSubscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChangeEvent(DataChangeEvent<?> event) {
|
||||
if (event instanceof IncomingWebSocketEvent incomingWSEvent) {
|
||||
public <T> void onDataChangeEvent(DataChangeEvent<T> event) {
|
||||
if (event instanceof IncomingWebSocketEvent<T> incomingWSEvent) {
|
||||
if (incomingWSEvent.propertyName.equals("userConnected")
|
||||
|| incomingWSEvent.propertyName.equals("sendFullSettings")) {
|
||||
// Send full settings
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.photonvision.server;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import io.avaje.json.JsonDataException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import org.photonvision.common.dataflow.DataChangeDestination;
|
||||
@@ -43,15 +43,15 @@ class UIOutboundSubscriber extends DataChangeSubscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChangeEvent(DataChangeEvent event) {
|
||||
if (event instanceof OutgoingUIEvent thisEvent) {
|
||||
public <T> void onDataChangeEvent(DataChangeEvent<T> event) {
|
||||
if (event instanceof OutgoingUIEvent<T> thisEvent) {
|
||||
try {
|
||||
if (event.data instanceof HashMap data) {
|
||||
socketHandler.broadcastMessage(data, thisEvent.originContext);
|
||||
} else {
|
||||
socketHandler.broadcastMessage(event.data, thisEvent.originContext);
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (JsonDataException e) {
|
||||
logger.error("Failed to process outgoing message!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
package org.photonvision.common.networktables;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.json.JsonException;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.photonvision.common.dataflow.structures.Packet;
|
||||
@@ -34,12 +34,11 @@ public class PacketPublisher<T> implements AutoCloseable {
|
||||
this.publisher = publisher;
|
||||
this.photonStruct = photonStruct;
|
||||
|
||||
var mapper = new ObjectMapper();
|
||||
try {
|
||||
this.publisher
|
||||
.getTopic()
|
||||
.setProperty("message_uuid", mapper.writeValueAsString(photonStruct.getInterfaceUUID()));
|
||||
} catch (JsonProcessingException e) {
|
||||
.setProperty("message_uuid", Jsonb.instance().toJson(photonStruct.getInterfaceUUID()));
|
||||
} catch (IllegalStateException | JsonException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
|
||||
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.File;
|
||||
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.,
|
||||
* x86-64, arm64).
|
||||
*/
|
||||
@Json
|
||||
public record ArchInfo(Map<String, String> fileHashes) {}
|
||||
|
||||
/**
|
||||
* Platform-specific information containing architectures for a specific OS platform (e.g., linux,
|
||||
* windows).
|
||||
*/
|
||||
@Json
|
||||
public record PlatformInfo(Map<String, ArchInfo> architectures) {}
|
||||
|
||||
/** Overall resource information to be serialized */
|
||||
@Json
|
||||
public record ResourceInformation(
|
||||
// Combined MD5 hash of all native resource files
|
||||
String hash,
|
||||
@@ -167,10 +172,10 @@ public final class CombinedRuntimeLoader {
|
||||
*/
|
||||
public static <T> List<String> extractLibraries(Class<T> clazz, String resourceName)
|
||||
throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonType<ResourceInformation> jsonb = Jsonb.instance().type(ResourceInformation.class);
|
||||
ResourceInformation resourceInfo;
|
||||
try (var stream = clazz.getResourceAsStream(resourceName)) {
|
||||
resourceInfo = mapper.readValue(stream, ResourceInformation.class);
|
||||
resourceInfo = jsonb.fromJson(stream);
|
||||
}
|
||||
|
||||
var platformPath = Paths.get(getPlatformPath());
|
||||
|
||||
@@ -18,8 +18,8 @@ dependencies {
|
||||
|
||||
implementation "io.javalin:javalin:$javalinVersion"
|
||||
|
||||
implementation 'org.msgpack:msgpack-core:0.9.0'
|
||||
implementation 'org.msgpack:jackson-dataformat-msgpack:0.9.0'
|
||||
implementation "org.msgpack:msgpack-core:$msgpackVersion"
|
||||
implementation "org.msgpack:jackson-dataformat-msgpack:$msgpackVersion"
|
||||
|
||||
implementation "org.wpilib.wpiutil:wpiutil-java:$wpilibVersion"
|
||||
implementation "org.wpilib.datalog:datalog-java:$wpilibVersion"
|
||||
@@ -34,9 +34,11 @@ dependencies {
|
||||
implementation "org.wpilib.wpiunits:wpiunits-java:$wpilibVersion"
|
||||
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-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: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion;
|
||||
@@ -53,6 +55,7 @@ dependencies {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
systemProperty("java.awt.headless", !project.hasProperty("enableTestUi"))
|
||||
systemProperty 'jsonb.disableAdapterSpi', 'true'
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
exceptionFormat = "full"
|
||||
|
||||
@@ -120,6 +120,7 @@ publishing {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'
|
||||
systemProperty 'jsonb.disableAdapterSpi', 'true'
|
||||
testLogging {
|
||||
events "failed"
|
||||
exceptionFormat = "full"
|
||||
@@ -149,9 +150,10 @@ dependencies {
|
||||
implementation "org.wpilib.wpiunits:wpiunits-java:$wpilibVersion"
|
||||
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-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: "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