Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save calibration data and show preliminary GUI #1078

Merged
merged 57 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
ecae647
Serialize all calibration data
mcm001 Dec 28, 2023
db73bbb
Run lint
mcm001 Dec 28, 2023
e27f776
typing nit
srimanachanta Dec 30, 2023
20db1df
fix code
srimanachanta Dec 30, 2023
824522d
move these tables around some
srimanachanta Dec 30, 2023
1ea00ca
Add cool formatting
srimanachanta Dec 30, 2023
6940840
add request to get snapshots by resolution and camera
srimanachanta Dec 30, 2023
74953c6
re-enable all resolutions
srimanachanta Dec 30, 2023
69a1d0d
add wip so i can change computers (SQUASH ME AND KILL ME AHHHH)
srimanachanta Dec 30, 2023
cab44c9
Get everything working but viewing snapshots
srimanachanta Dec 31, 2023
4c167f1
Update RequestHandler.java
srimanachanta Dec 31, 2023
fa94233
Update CameraCalibrationInfoCard.vue
mcm001 Dec 31, 2023
f7268dd
Update CameraCalibrationInfoCard.vue
mcm001 Dec 31, 2023
686b91b
add observation viewer
srimanachanta Dec 31, 2023
9d553b5
Merge branch '2023-12-27_save_cal_data' of https://github.com/mcm001/…
srimanachanta Dec 31, 2023
75a580b
round
srimanachanta Dec 31, 2023
e5333cf
fix illiegal import
srimanachanta Dec 31, 2023
da48930
Swap to PNG and serialize insolution
mcm001 Dec 31, 2023
a26736a
Merge branch '2023-12-27_save_cal_data' of https://github.com/mcm001/…
mcm001 Dec 31, 2023
689747e
move import/export buttons TO THE TOP
srimanachanta Dec 31, 2023
11005e9
Merge branch 'master' into 2023-12-27_save_cal_data
srimanachanta Dec 31, 2023
682b057
Update WebsocketDataTypes.ts
srimanachanta Dec 31, 2023
ddc683f
Merge branch '2023-12-27_save_cal_data' of https://github.com/mcm001/…
mcm001 Dec 31, 2023
08c89db
Add snapshotname to observation
mcm001 Dec 31, 2023
10f2024
Refactor to serialize snapshot image itself
mcm001 Jan 1, 2024
96d20b8
Run lint
mcm001 Jan 1, 2024
6c3972f
Use new base64 image data in info card
mcm001 Jan 1, 2024
d27b9d2
Update SettingTypes.ts
mcm001 Jan 1, 2024
b8a0cb8
Create calibration json -> mrcal converter script
mcm001 Jan 1, 2024
62a9176
Update calibrationUtils.py
mcm001 Jan 1, 2024
1b8318d
Fix calibrate NPEs in teest
mcm001 Jan 1, 2024
1308999
Run lint
mcm001 Jan 1, 2024
3922440
Always run cornersubpix
mcm001 Jan 1, 2024
6d31b1d
Update CameraCalibrationInfoCard.vue
mcm001 Jan 1, 2024
53514a2
Update OpenCVHelp.java
mcm001 Jan 1, 2024
f7438b4
Update OpenCVHelp.java
mcm001 Jan 1, 2024
c346987
Replace test mode camera JSONs
mcm001 Jan 1, 2024
4b67c9b
Run wpiformat
mcm001 Jan 1, 2024
4ea4224
Revert intrinsics but keep other data
mcm001 Jan 1, 2024
ab1a8da
Merge branch 'master' into 2023-12-27_save_cal_data
srimanachanta Jan 1, 2024
feb891a
Remove misc comments
mcm001 Jan 1, 2024
3e36920
Rename JsonMat->JsonImageMat and add calobject_warp
mcm001 Jan 1, 2024
299fff1
Merge branch 'master' into 2023-12-27_save_cal_data
mcm001 Jan 1, 2024
af33f64
Update Server.java
mcm001 Jan 2, 2024
55ec833
Rename cameraExtrinsics to distCoeffs
mcm001 Jan 2, 2024
f3ed7c6
Merge branch 'master' into pr/1078
srimanachanta Jan 2, 2024
0e6ff5c
fix typing issues
srimanachanta Jan 2, 2024
96b8f7c
use util methods
srimanachanta Jan 2, 2024
7c28075
Formatting fixes
srimanachanta Jan 2, 2024
c679ebf
fix styling
srimanachanta Jan 2, 2024
7c274b1
move to devTools
srimanachanta Jan 3, 2024
cb6a5c1
remove unneeded or unused imports
srimanachanta Jan 3, 2024
44d34aa
Remove fixed-right css
srimanachanta Jan 3, 2024
c971d8a
Merge branch 'master' into 2023-12-27_save_cal_data
srimanachanta Jan 3, 2024
998602a
Create util method
srimanachanta Jan 3, 2024
1ad2a84
Merge branch '2023-12-27_save_cal_data' of https://github.com/mcm001/…
srimanachanta Jan 3, 2024
3506d8f
Remove extra legacy calibration things
mcm001 Jan 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,5 @@ photonlib-cpp-examples/*/networktables.json.bck
photonlib-java-examples/*/networktables.json.bck
*.sqlite
photon-server/src/main/resources/web/index.html

venv
247 changes: 142 additions & 105 deletions photon-client/src/components/cameras/CameraCalibrationCard.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { CalibrationBoardTypes, type Resolution, type VideoFormat } from "@/types/SettingTypes";
import {
CalibrationBoardTypes,
type CameraCalibrationResult,
type Resolution,
type VideoFormat
} from "@/types/SettingTypes";
import JsPDF from "jspdf";
import { font as PromptRegular } from "@/assets/fonts/PromptRegular";
import MonoLogo from "@/assets/images/logoMono.png";
Expand All @@ -11,15 +16,18 @@ import PvSwitch from "@/components/common/pv-switch.vue";
import PvSelect from "@/components/common/pv-select.vue";
import PvNumberInput from "@/components/common/pv-number-input.vue";
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
import CameraCalibrationInfoCard from "@/components/cameras/CameraCalibrationInfoCard.vue";

type JSONFileUploadEvent = Event & { target: HTMLInputElement | null };

const settingsValid = ref(true);

const getCalibrationCoeffs = (resolution: Resolution) => {
const getCalibrationCoeffs = (resolution: Resolution): CameraCalibrationResult | undefined => {
return useCameraSettingsStore().currentCameraSettings.completeCalibrations.find(
(cal) => cal.resolution.width === resolution.width && cal.resolution.height === resolution.height
);
};
const getUniqueVideoResolutions = (): VideoFormat[] => {
const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
const uniqueResolutions: VideoFormat[] = [];
useCameraSettingsStore().currentCameraSettings.validVideoFormats.forEach((format, index) => {
if (
Expand All @@ -31,18 +39,28 @@ const getUniqueVideoResolutions = (): VideoFormat[] => {

const calib = getCalibrationCoeffs(format.resolution);
if (calib !== undefined) {
format.standardDeviation = calib.standardDeviation;
format.mean = calib.perViewErrors.reduce((a, b) => a + b) / calib.perViewErrors.length;
format.horizontalFOV = 2 * Math.atan2(format.resolution.width / 2, calib.intrinsics[0]) * (180 / Math.PI);
format.verticalFOV = 2 * Math.atan2(format.resolution.height / 2, calib.intrinsics[4]) * (180 / Math.PI);
// Is this the right formula for RMS error? who knows! not me!
const perViewSumSquareReprojectionError = calib.observations.flatMap((it) =>
it.reprojectionErrors.flatMap((it2) => [it2.x, it2.y])
);
// For each error, square it, sum the squares, and divide by total points N
format.mean = Math.sqrt(
perViewSumSquareReprojectionError.map((it) => Math.pow(it, 2)).reduce((a, b) => a + b, 0) /
perViewSumSquareReprojectionError.length
);
srimanachanta marked this conversation as resolved.
Show resolved Hide resolved

format.horizontalFOV =
2 * Math.atan2(format.resolution.width / 2, calib.cameraIntrinsics.data[0]) * (180 / Math.PI);
format.verticalFOV =
2 * Math.atan2(format.resolution.height / 2, calib.cameraIntrinsics.data[4]) * (180 / Math.PI);
format.diagonalFOV =
2 *
Math.atan2(
Math.sqrt(
format.resolution.width ** 2 +
(format.resolution.height / (calib.intrinsics[4] / calib.intrinsics[0])) ** 2
(format.resolution.height / (calib.cameraIntrinsics.data[4] / calib.cameraIntrinsics.data[0])) ** 2
) / 2,
calib.intrinsics[0]
calib.cameraIntrinsics.data[0]
) *
(180 / Math.PI);
}
Expand All @@ -55,7 +73,7 @@ const getUniqueVideoResolutions = (): VideoFormat[] => {
return uniqueResolutions;
};
const getUniqueVideoResolutionStrings = () =>
getUniqueVideoResolutions().map<{ name: string; value: number }>((f) => ({
getUniqueVideoFormatsByResolution().map<{ name: string; value: number }>((f) => ({
name: `${f.resolution.width} X ${f.resolution.height}`,
// Index won't ever be undefined
value: f.index || 0
Expand Down Expand Up @@ -150,7 +168,7 @@ const importCalibrationFromCalibDB = ref();
const openCalibUploadPrompt = () => {
importCalibrationFromCalibDB.value.click();
};
const readImportedCalibration = (payload: Event) => {
const readImportedCalibrationFromCalibDB = (payload: JSONFileUploadEvent) => {
if (payload.target == null || !payload.target?.files) return;
const files: FileList = payload.target.files as FileList;

Expand Down Expand Up @@ -191,6 +209,9 @@ const startCalibration = () => {
useCameraSettingsStore().currentCameraSettings.currentPipelineIndex = WebsocketPipelineType.Calib3d;
isCalibrating.value = true;
calibCanceled.value = false;

const camView: HTMLElement | null = document.getElementById("camera-settings-camera-view-card");
if (camView !== null) camView.classList.add("fixed-right");
};
const showCalibEndDialog = ref(false);
const calibCanceled = ref(false);
Expand All @@ -213,6 +234,16 @@ const endCalibration = () => {
.finally(() => {
isCalibrating.value = false;
});

const camView: HTMLElement | null = document.getElementById("camera-settings-camera-view-card");
if (camView !== null) camView.classList.remove("fixed-right");
};

let showCalDialog = ref(false);
let selectedVideoFormat = ref<VideoFormat | undefined>(undefined);
const setSelectedVideoFormat = (format: VideoFormat) => {
selectedVideoFormat.value = format;
showCalDialog.value = true;
};
</script>

Expand All @@ -221,100 +252,101 @@ const endCalibration = () => {
<v-card class="mb-3 pr-6 pb-3" color="primary" dark>
<v-card-title>Camera Calibration</v-card-title>
<div class="ml-5">
<v-row>
<v-col cols="12" md="6">
<v-form ref="form" v-model="settingsValid">
<pv-select
v-model="useStateStore().calibrationData.videoFormatIndex"
label="Resolution"
:select-cols="7"
:disabled="isCalibrating"
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
:items="getUniqueVideoResolutionStrings()"
/>
<pv-select
v-show="isCalibrating"
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
label="Decimation"
tooltip="Resolution to which camera frames are downscaled for detection. Calibration still uses full-res"
:items="calibrationDivisors"
:select-cols="7"
@input="
(v) => useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: v }, false)
"
/>
<pv-select
v-model="boardType"
label="Board Type"
tooltip="Calibration board pattern to use"
:select-cols="7"
:items="['Chessboard', 'Dotboard']"
:disabled="isCalibrating"
/>
<pv-number-input
v-model="squareSizeIn"
label="Pattern Spacing (in)"
tooltip="Spacing between pattern features in inches"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="5"
/>
<pv-number-input
v-model="patternWidth"
label="Board Width (in)"
tooltip="Width of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
:label-cols="5"
/>
<pv-number-input
v-model="patternHeight"
label="Board Height (in)"
tooltip="Height of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
:label-cols="5"
/>
</v-form>
</v-col>
<v-col cols="12" md="6">
<v-row align="start" class="pb-4 pt-2">
<v-simple-table fixed-header height="100%" dense>
<thead>
<tr>
<th>Resolution</th>
<th>Mean Error</th>
<th>Standard Deviation</th>
<th>Horizontal FOV</th>
<th>Vertical FOV</th>
<th>Diagonal FOV</th>
</tr>
</thead>
<tbody>
<tr v-for="(value, index) in getUniqueVideoResolutions()" :key="index">
<td>{{ value.resolution.width }} X {{ value.resolution.height }}</td>
<td>{{ value.mean !== undefined ? value.mean.toFixed(2) + "px" : "-" }}</td>
<td>
{{ value.standardDeviation !== undefined ? value.standardDeviation.toFixed(2) + "px" : "-" }}
</td>
<td>{{ value.horizontalFOV !== undefined ? value.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.diagonalFOV !== undefined ? value.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
</tr>
</tbody>
</v-simple-table>
</v-row>
<v-row justify="center">
<v-chip
v-show="isCalibrating"
label
:color="useStateStore().calibrationData.hasEnoughImages ? 'secondary' : 'gray'"
<v-row v-show="!isCalibrating" class="pb-12">
<v-card-subtitle class="pb-0 mb-0 pl-3">Complete Calibrations</v-card-subtitle>
<v-simple-table fixed-header height="100%" dense class="mt-2">
<thead>
<tr>
<th>Resolution</th>
<th>Mean Error</th>
<th>Horizontal FOV</th>
<th>Vertical FOV</th>
<th>Diagonal FOV</th>
</tr>
</thead>
<tbody>
<tr
v-for="(value, index) in getUniqueVideoFormatsByResolution()"
:key="index"
title="Click to get calibration specific information"
@click="setSelectedVideoFormat(value)"
>
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
{{ useStateStore().calibrationData.minimumImageCount }}
</v-chip>
</v-row>
</v-col>
<td>{{ value.resolution.width }} X {{ value.resolution.height }}</td>
<td>
{{ value.mean !== undefined ? (isNaN(value.mean) ? "NaN" : value.mean.toFixed(2) + "px") : "-" }}
</td>
<td>{{ value.horizontalFOV !== undefined ? value.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.diagonalFOV !== undefined ? value.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
</tr>
</tbody>
</v-simple-table>
</v-row>
<v-divider />
<v-row style="display: flex; flex-direction: column" class="mt-4">
<v-card-subtitle v-show="!isCalibrating" class="pl-3 pa-0 ma-0"> Configure New Calibration</v-card-subtitle>
<v-form ref="form" v-model="settingsValid" class="pl-4 mb-10 pr-5">
<pv-select
v-model="useStateStore().calibrationData.videoFormatIndex"
label="Resolution"
:select-cols="7"
:disabled="isCalibrating"
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
:items="getUniqueVideoResolutionStrings()"
/>
<pv-select
v-show="isCalibrating"
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
label="Decimation"
tooltip="Resolution to which camera frames are downscaled for detection. Calibration still uses full-res"
:items="calibrationDivisors"
:select-cols="7"
@input="(v) => useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: v }, false)"
/>
<pv-select
v-model="boardType"
label="Board Type"
tooltip="Calibration board pattern to use"
:select-cols="7"
:items="['Chessboard', 'Dotboard']"
:disabled="isCalibrating"
/>
<pv-number-input
v-model="squareSizeIn"
label="Pattern Spacing (in)"
tooltip="Spacing between pattern features in inches"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="5"
/>
<pv-number-input
v-model="patternWidth"
label="Board Width (in)"
tooltip="Width of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
:label-cols="5"
/>
<pv-number-input
v-model="patternHeight"
label="Board Height (in)"
tooltip="Height of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
:label-cols="5"
/>
</v-form>
<v-row justify="center">
<v-chip
v-show="isCalibrating"
label
:color="useStateStore().calibrationData.hasEnoughImages ? 'secondary' : 'gray'"
class="mb-6"
>
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
{{ useStateStore().calibrationData.minimumImageCount }}
</v-chip>
</v-row>
</v-row>
<v-row v-if="isCalibrating">
<v-col cols="12" class="pt-0">
Expand Down Expand Up @@ -427,7 +459,7 @@ const endCalibration = () => {
type="file"
accept=".json"
style="display: none"
@change="readImportedCalibration"
@change="readImportedCalibrationFromCalibDB"
/>
</v-col>
</v-row>
Expand Down Expand Up @@ -476,12 +508,16 @@ const endCalibration = () => {
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showCalDialog" width="80em">
<CameraCalibrationInfoCard :video-format="selectedVideoFormat" />
</v-dialog>
</div>
</template>

<style scoped lang="scss">
.v-data-table {
text-align: center;
width: 100%;

th,
td {
Expand All @@ -491,6 +527,7 @@ const endCalibration = () => {

tbody :hover td {
background-color: #005281 !important;
cursor: pointer;
}

::-webkit-scrollbar {
Expand Down
Loading