Skip to content

Commit 62112cd

Browse files
authored
Reduce initial connection bandwidth (#1200)
Reduces bandwidth requirements by being much lazier about how much calibration data is sent to the UI.
1 parent c7508fe commit 62112cd

File tree

15 files changed

+275
-88
lines changed

15 files changed

+275
-88
lines changed

photon-client/src/components/cameras/CameraCalibrationCard.vue

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,8 @@ const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
2525
2626
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
2727
if (calib !== undefined) {
28-
// Is this the right formula for RMS error? who knows! not me!
29-
const perViewSumSquareReprojectionError = calib.observations.flatMap((it) =>
30-
it.reprojectionErrors.flatMap((it2) => [it2.x, it2.y])
31-
);
3228
// For each error, square it, sum the squares, and divide by total points N
33-
format.mean = Math.sqrt(
34-
perViewSumSquareReprojectionError.map((it) => Math.pow(it, 2)).reduce((a, b) => a + b, 0) /
35-
perViewSumSquareReprojectionError.length
36-
);
29+
format.mean = calib.meanErrors.reduce((a, b) => a + b) / calib.meanErrors.length;
3730
3831
format.horizontalFOV =
3932
2 * Math.atan2(format.resolution.width / 2, calib.cameraIntrinsics.data[0]) * (180 / Math.PI);

photon-client/src/components/cameras/CameraCalibrationInfoCard.vue

Lines changed: 36 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,19 @@
11
<script setup lang="ts">
2-
import type { BoardObservation, CameraCalibrationResult, VideoFormat } from "@/types/SettingTypes";
2+
import type { CameraCalibrationResult, VideoFormat } from "@/types/SettingTypes";
33
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
44
import { useStateStore } from "@/stores/StateStore";
5-
import { ref } from "vue";
6-
import loadingImage from "@/assets/images/loading.svg";
5+
import { computed, inject, ref } from "vue";
76
import { getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
87
98
const props = defineProps<{
109
videoFormat: VideoFormat;
1110
}>();
1211
13-
const getMeanFromView = (o: BoardObservation) => {
14-
// Is this the right formula for RMS error? who knows! not me!
15-
const perViewSumSquareReprojectionError = o.reprojectionErrors.flatMap((it2) => [it2.x, it2.y]);
16-
17-
// For each error, square it, sum the squares, and divide by total points N
18-
return Math.sqrt(
19-
perViewSumSquareReprojectionError.map((it) => Math.pow(it, 2)).reduce((a, b) => a + b, 0) /
20-
perViewSumSquareReprojectionError.length
21-
);
12+
const exportCalibration = ref();
13+
const openExportCalibrationPrompt = () => {
14+
exportCalibration.value.click();
2215
};
2316
24-
// Import and export functions
25-
const downloadCalibration = () => {
26-
const calibData = useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution);
27-
if (calibData === undefined) {
28-
useStateStore().showSnackbarMessage({
29-
color: "error",
30-
message:
31-
"Calibration data isn't available for the requested resolution, please calibrate the requested resolution first"
32-
});
33-
return;
34-
}
35-
36-
const camUniqueName = useCameraSettingsStore().currentCameraSettings.uniqueName;
37-
const filename = `photon_calibration_${camUniqueName}_${calibData.resolution.width}x${calibData.resolution.height}.json`;
38-
const fileData = JSON.stringify(calibData);
39-
40-
const element = document.createElement("a");
41-
element.style.display = "none";
42-
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(fileData));
43-
element.setAttribute("download", filename);
44-
45-
document.body.appendChild(element);
46-
element.click();
47-
document.body.removeChild(element);
48-
};
4917
const importCalibrationFromPhotonJson = ref();
5018
const openUploadPhotonCalibJsonPrompt = () => {
5119
importCalibrationFromPhotonJson.value.click();
@@ -97,19 +65,28 @@ const importCalibration = async () => {
9765
};
9866
9967
interface ObservationDetails {
100-
snapshotSrc: any;
10168
mean: number;
10269
index: number;
10370
}
71+
72+
const currentCalibrationCoeffs = computed<CameraCalibrationResult | undefined>(() =>
73+
useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution)
74+
);
75+
10476
const getObservationDetails = (): ObservationDetails[] | undefined => {
105-
return useCameraSettingsStore()
106-
.getCalibrationCoeffs(props.videoFormat.resolution)
107-
?.observations.map((o, i) => ({
108-
index: i,
109-
mean: parseFloat(getMeanFromView(o).toFixed(2)),
110-
snapshotSrc: o.includeObservationInCalibration ? "data:image/png;base64," + o.snapshotData.data : loadingImage
111-
}));
77+
const coefficients = currentCalibrationCoeffs.value;
78+
79+
return coefficients?.meanErrors.map((m, i) => ({
80+
index: i,
81+
mean: parseFloat(m.toFixed(2))
82+
}));
11283
};
84+
85+
const exportCalibrationURL = computed<string>(() =>
86+
useCameraSettingsStore().getCalJSONUrl(inject("backendHost") as string, props.videoFormat.resolution)
87+
);
88+
const calibrationImageURL = (index: number) =>
89+
useCameraSettingsStore().getCalImageUrl(inject<string>("backendHost") as string, props.videoFormat.resolution, index);
11390
</script>
11491

11592
<template>
@@ -140,19 +117,22 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
140117
<v-btn
141118
color="secondary"
142119
class="mt-4"
143-
:disabled="useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution) === undefined"
120+
:disabled="!currentCalibrationCoeffs"
144121
style="width: 100%"
145-
@click="downloadCalibration"
122+
@click="openExportCalibrationPrompt"
146123
>
147124
<v-icon left>mdi-export</v-icon>
148125
<span>Export</span>
149126
</v-btn>
127+
<a
128+
ref="exportCalibration"
129+
style="color: black; text-decoration: none; display: none"
130+
:href="exportCalibrationURL"
131+
target="_blank"
132+
/>
150133
</v-col>
151134
</v-row>
152-
<v-row
153-
v-if="useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution) !== undefined"
154-
class="pt-2"
155-
>
135+
<v-row v-if="!currentCalibrationCoeffs" class="pt-2">
156136
<v-card-subtitle>Calibration Details</v-card-subtitle>
157137
<v-simple-table dense style="width: 100%" class="pl-2 pr-2">
158138
<template #default>
@@ -231,7 +211,9 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
231211
</tr>
232212
<tr>
233213
<td>Horizontal FOV</td>
234-
<td>{{ videoFormat.horizontalFOV !== undefined ? videoFormat.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
214+
<td>
215+
{{ videoFormat.horizontalFOV !== undefined ? videoFormat.horizontalFOV.toFixed(2) + "°" : "-" }}
216+
</td>
235217
</tr>
236218
<tr>
237219
<td>Vertical FOV</td>
@@ -242,11 +224,7 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
242224
<td>{{ videoFormat.diagonalFOV !== undefined ? videoFormat.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
243225
</tr>
244226
<!-- Board warp, only shown for mrcal-calibrated cameras -->
245-
<tr
246-
v-if="
247-
useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution)?.calobjectWarp?.length === 2
248-
"
249-
>
227+
<tr v-if="currentCalibrationCoeffs?.calobjectWarp?.length === 2">
250228
<td>Board warp, X/Y</td>
251229
<td>
252230
{{
@@ -278,7 +256,7 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
278256
<template #expanded-item="{ headers, item }">
279257
<td :colspan="headers.length">
280258
<div style="display: flex; justify-content: center; width: 100%">
281-
<img :src="item.snapshotSrc" alt="observation image" class="snapshot-preview pt-2 pb-2" />
259+
<img :src="calibrationImageURL(item.index)" alt="observation image" class="snapshot-preview pt-2 pb-2" />
282260
</div>
283261
</td>
284262
</template>

photon-client/src/stores/settings/CameraSettingsStore.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,23 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
416416
cameraIndex: number = useStateStore().currentCameraIndex
417417
): CameraCalibrationResult | undefined {
418418
return this.cameras[cameraIndex].completeCalibrations.find((v) => resolutionsAreEqual(v.resolution, resolution));
419+
},
420+
getCalImageUrl(host: string, resolution: Resolution, idx: number, cameraIdx = useStateStore().currentCameraIndex) {
421+
const url = new URL(`http://${host}/api/utils/getCalSnapshot`);
422+
url.searchParams.set("width", Math.round(resolution.width).toFixed(0));
423+
url.searchParams.set("height", Math.round(resolution.height).toFixed(0));
424+
url.searchParams.set("snapshotIdx", Math.round(idx).toFixed(0));
425+
url.searchParams.set("cameraIdx", Math.round(cameraIdx).toFixed(0));
426+
427+
return url.href;
428+
},
429+
getCalJSONUrl(host: string, resolution: Resolution, cameraIdx = useStateStore().currentCameraIndex) {
430+
const url = new URL(`http://${host}/api/utils/getCalibrationJSON`);
431+
url.searchParams.set("width", Math.round(resolution.width).toFixed(0));
432+
url.searchParams.set("height", Math.round(resolution.height).toFixed(0));
433+
url.searchParams.set("cameraIdx", Math.round(cameraIdx).toFixed(0));
434+
435+
return url.href;
419436
}
420437
}
421438
});

photon-client/src/types/SettingTypes.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ export interface CameraCalibrationResult {
138138
distCoeffs: JsonMatOfDouble;
139139
observations: BoardObservation[];
140140
calobjectWarp?: number[];
141+
// We might have to omit observations for bandwith, so backend will send us this
142+
numSnapshots: number;
143+
meanErrors: number[];
141144
}
142145

143146
export enum ValidQuirks {
@@ -255,7 +258,9 @@ export const PlaceholderCameraSettings: CameraSettings = {
255258
snapshotName: "img0.png",
256259
snapshotData: { rows: 480, cols: 640, type: CvType.CV_8U, data: "" }
257260
}
258-
]
261+
],
262+
numSnapshots: 1,
263+
meanErrors: [123.45]
259264
}
260265
],
261266
pipelineNicknames: ["Placeholder Pipeline"],

photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import org.photonvision.jni.RknnDetectorJNI;
3232
import org.photonvision.mrcal.MrCalJNILoader;
3333
import org.photonvision.raspi.LibCameraJNILoader;
34-
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
34+
import org.photonvision.vision.calibration.UICameraCalibrationCoefficients;
3535
import org.photonvision.vision.camera.QuirkyCamera;
3636
import org.photonvision.vision.processes.VisionModule;
3737
import org.photonvision.vision.processes.VisionModuleManager;
@@ -126,13 +126,6 @@ public Map<String, Object> toHashMap() {
126126

127127
settingsSubmap.put("networkSettings", netConfigMap);
128128

129-
map.put(
130-
"cameraSettings",
131-
VisionModuleManager.getInstance().getModules().stream()
132-
.map(VisionModule::toUICameraConfig)
133-
.map(SerializationUtils::objectToHashMap)
134-
.collect(Collectors.toList()));
135-
136129
var lightingConfig = new UILightingConfig();
137130
lightingConfig.brightness = hardwareSettings.ledBrightnessPercentage;
138131
lightingConfig.supported = !hardwareConfig.ledPins.isEmpty();
@@ -181,7 +174,7 @@ public static class UICameraConfiguration {
181174
public HashMap<Integer, HashMap<String, Object>> videoFormatList;
182175
public int outputStreamPort;
183176
public int inputStreamPort;
184-
public List<CameraCalibrationCoefficients> calibrations;
177+
public List<UICameraCalibrationCoefficients> calibrations;
185178
public boolean isFovConfigurable = true;
186179
public QuirkyCamera cameraQuirks;
187180
public boolean isCSICamera;

photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIDataPublisher.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.photonvision.common.logging.Logger;
2727
import org.photonvision.common.util.SerializationUtils;
2828
import org.photonvision.vision.pipeline.result.CVPipelineResult;
29+
import org.photonvision.vision.pipeline.result.CalibrationPipelineResult;
2930

3031
public class UIDataPublisher implements CVPipelineResultConsumer {
3132
private static final Logger logger = new Logger(UIDataPublisher.class, LogGroup.VisionModule);
@@ -41,16 +42,22 @@ public UIDataPublisher(int index) {
4142
public void accept(CVPipelineResult result) {
4243
long now = System.currentTimeMillis();
4344

44-
// only update the UI at 15hz
45+
// only update the UI at 10hz
4546
if (lastUIResultUpdateTime + 1000.0 / 10.0 > now) return;
4647

4748
var dataMap = new HashMap<String, Object>();
4849
dataMap.put("fps", result.fps);
4950
dataMap.put("latency", result.getLatencyMillis());
5051
var uiTargets = new ArrayList<HashMap<String, Object>>(result.targets.size());
51-
for (var t : result.targets) {
52-
uiTargets.add(t.toHashMap());
52+
53+
// We don't actually need to send targets during calibration and it can take up a lot (up to
54+
// 1.2Mbps for 60 snapshots) of target results with no pitch/yaw/etc set
55+
if (!(result instanceof CalibrationPipelineResult)) {
56+
for (var t : result.targets) {
57+
uiTargets.add(t.toHashMap());
58+
}
5359
}
60+
5461
dataMap.put("targets", uiTargets);
5562
dataMap.put("classNames", result.objectDetectionClassNames);
5663

photon-core/src/main/java/org/photonvision/vision/calibration/BoardObservation.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.opencv.core.Point;
2525
import org.opencv.core.Point3;
2626

27-
public final class BoardObservation {
27+
public final class BoardObservation implements Cloneable {
2828
// Expected feature 3d location in the camera frame
2929
@JsonProperty("locationInObjectSpace")
3030
public List<Point3> locationInObjectSpace;
@@ -68,4 +68,33 @@ public BoardObservation(
6868
this.snapshotName = snapshotName;
6969
this.snapshotData = snapshotData;
7070
}
71+
72+
@Override
73+
public String toString() {
74+
return "BoardObservation [locationInObjectSpace="
75+
+ locationInObjectSpace
76+
+ ", locationInImageSpace="
77+
+ locationInImageSpace
78+
+ ", reprojectionErrors="
79+
+ reprojectionErrors
80+
+ ", optimisedCameraToObject="
81+
+ optimisedCameraToObject
82+
+ ", includeObservationInCalibration="
83+
+ includeObservationInCalibration
84+
+ ", snapshotName="
85+
+ snapshotName
86+
+ ", snapshotData="
87+
+ snapshotData
88+
+ "]";
89+
}
90+
91+
@Override
92+
public BoardObservation clone() {
93+
try {
94+
return (BoardObservation) super.clone();
95+
} catch (CloneNotSupportedException e) {
96+
System.err.println("Guhhh clone buh");
97+
return null;
98+
}
99+
}
71100
}

photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ public String toString() {
191191
+ cameraIntrinsics
192192
+ ", distCoeffs="
193193
+ distCoeffs
194-
+ ", observations="
195-
+ observations
194+
+ ", observationslen="
195+
+ observations.size()
196196
+ ", calobjectWarp="
197197
+ Arrays.toString(calobjectWarp)
198198
+ ", intrinsicsArr="
@@ -201,4 +201,16 @@ public String toString() {
201201
+ Arrays.toString(distCoeffsArr)
202202
+ "]";
203203
}
204+
205+
public UICameraCalibrationCoefficients cloneWithoutObservations() {
206+
return new UICameraCalibrationCoefficients(
207+
resolution,
208+
cameraIntrinsics,
209+
distCoeffs,
210+
calobjectWarp,
211+
observations,
212+
calobjectSize,
213+
calobjectSpacing,
214+
lensmodel);
215+
}
204216
}

photon-core/src/main/java/org/photonvision/vision/calibration/JsonImageMat.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,17 @@ public Mat getAsMat() {
7676
public void release() {
7777
if (wrappedMat != null) wrappedMat.release();
7878
}
79+
80+
@Override
81+
public String toString() {
82+
return "JsonImageMat [rows="
83+
+ rows
84+
+ ", cols="
85+
+ cols
86+
+ ", type="
87+
+ type
88+
+ ", datalen="
89+
+ data.length()
90+
+ "]";
91+
}
7992
}

photon-core/src/main/java/org/photonvision/vision/calibration/JsonMatOfDouble.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class JsonMatOfDouble implements Releasable {
4040
@JsonIgnore private Mat wrappedMat = null;
4141
@JsonIgnore private Matrix wpilibMat = null;
4242

43-
private MatOfDouble wrappedMatOfDouble;
43+
@JsonIgnore private MatOfDouble wrappedMatOfDouble;
4444

4545
public JsonMatOfDouble(int rows, int cols, double[] data) {
4646
this(rows, cols, CvType.CV_64FC1, data);

0 commit comments

Comments
 (0)