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 all 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
134 changes: 134 additions & 0 deletions devTools/calibrationUtils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import base64
from dataclasses import dataclass
import json
import os
import cv2
import numpy as np


@dataclass
class Resolution:
width: int
height: int


@dataclass
class JsonMatOfDoubles:
rows: int
cols: int
type: int
data: list[float]


@dataclass
class JsonMat:
rows: int
cols: int
type: int
data: str # Base64-encoded PNG data


@dataclass
class Point2:
x: float
y: float


@dataclass
class Translation3d:
x: float
y: float
z: float


@dataclass
class Quaternion:
X: float
Y: float
Z: float
W: float


@dataclass
class Rotation3d:
quaternion: Quaternion


@dataclass
class Pose3d:
translation: Translation3d
rotation: Rotation3d


@dataclass
class Point3:
x: float
y: float
z: float


@dataclass
class Observation:
# Expected feature 3d location in the camera frame
locationInObjectSpace: list[Point3]
# Observed location in pixel space
locationInImageSpace: list[Point2]
# (measured location in pixels) - (expected from FK)
reprojectionErrors: list[Point2]
# Solver optimized board poses
optimisedCameraToObject: Pose3d
# If we should use this observation when re-calculating camera calibration
includeObservationInCalibration: bool
snapshotName: str
# The actual image the snapshot is from
snapshotData: JsonMat


@dataclass
class CameraCalibration:
resolution: Resolution
cameraIntrinsics: JsonMatOfDoubles
distCoeffs: JsonMatOfDoubles
observations: list[Observation]


def convert_photon_to_mrcal(photon_cal_json_path: str, output_folder: str):
"""
Unpack a Photon calibration JSON (eg, photon_calibration_Microsoft_LifeCam_HD-3000_800x600.json) into
the output_folder directory with images and corners.vnl file for use with mrcal.
"""
with open(photon_cal_json_path, "r") as cal_json:
# Convert to nested objects instead of nameddicts on json-loads
class Generic:
@classmethod
def from_dict(cls, dict):
obj = cls()
obj.__dict__.update(dict)
return obj

camera_cal_data: CameraCalibration = json.loads(
cal_json.read(), object_hook=Generic.from_dict
)

# Create output_folder if not exists
if not os.path.exists(output_folder):
os.makedirs(output_folder)

# Decode each image and save it as a png
for obs in camera_cal_data.observations:
image = obs.snapshotData.data
decoded_data = base64.b64decode(image)
np_data = np.frombuffer(decoded_data, np.uint8)
img = cv2.imdecode(np_data, cv2.IMREAD_UNCHANGED)
cv2.imwrite(f"{output_folder}/{obs.snapshotName}", img)

# And create a VNL file for use with mrcal
with open(f"{output_folder}/corners.vnl", "w+") as vnl_file:
vnl_file.write("# filename x y level\n")

for obs in camera_cal_data.observations:
for corner in obs.locationInImageSpace:
# Always level zero
vnl_file.write(f"{obs.snapshotName} {corner.x} {corner.y} 0\n")

vnl_file.flush()
61 changes: 40 additions & 21 deletions photon-client/src/components/cameras/CameraCalibrationCard.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<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 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 @@ -12,37 +12,40 @@ import PvSelect from "@/components/common/pv-select.vue";
import PvNumberInput from "@/components/common/pv-number-input.vue";
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
import { getResolutionString, resolutionsAreEqual } from "@/lib/PhotonUtils";
import CameraCalibrationInfoCard from "@/components/cameras/CameraCalibrationInfoCard.vue";
const settingsValid = ref(true);
const getCalibrationCoeffs = (resolution: Resolution) => {
return useCameraSettingsStore().currentCameraSettings.completeCalibrations.find((cal) =>
resolutionsAreEqual(cal.resolution, resolution)
);
};
const getUniqueVideoResolutions = (): VideoFormat[] => {
const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
const uniqueResolutions: VideoFormat[] = [];
useCameraSettingsStore().currentCameraSettings.validVideoFormats.forEach((format, index) => {
if (!uniqueResolutions.some((v) => resolutionsAreEqual(v.resolution, format.resolution))) {
format.index = index;
const calib = getCalibrationCoeffs(format.resolution);
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
if (calib !== undefined) {
format.standardDeviation = calib.standardDeviation;
format.mean =
calib.perViewErrors === null
? Number.NaN
: 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 +58,7 @@ const getUniqueVideoResolutions = (): VideoFormat[] => {
return uniqueResolutions;
};
const getUniqueVideoResolutionStrings = (): { name: string; value: number }[] =>
getUniqueVideoResolutions().map<{ name: string; value: number }>((f) => ({
getUniqueVideoFormatsByResolution().map<{ name: string; value: number }>((f) => ({
name: `${getResolutionString(f.resolution)}`,
// Index won't ever be undefined
value: f.index || 0
Expand Down Expand Up @@ -150,7 +153,7 @@ const importCalibrationFromCalibDB = ref();
const openCalibUploadPrompt = () => {
importCalibrationFromCalibDB.value.click();
};
const readImportedCalibration = () => {
const readImportedCalibrationFromCalibDB = () => {
const files = importCalibrationFromCalibDB.value.files;
if (files.length === 0) return;
Expand Down Expand Up @@ -214,6 +217,13 @@ const endCalibration = () => {
isCalibrating.value = false;
});
};
let showCalDialog = ref(false);
let selectedVideoFormat = ref<VideoFormat | undefined>(undefined);
const setSelectedVideoFormat = (format: VideoFormat) => {
selectedVideoFormat.value = format;
showCalDialog.value = true;
};
</script>

<template>
Expand All @@ -234,7 +244,12 @@ const endCalibration = () => {
</tr>
</thead>
<tbody>
<tr v-for="(value, index) in getUniqueVideoResolutions()" :key="index">
<tr
v-for="(value, index) in getUniqueVideoFormatsByResolution()"
:key="index"
title="Click to get calibration specific information"
@click="setSelectedVideoFormat(value)"
>
<td>{{ getResolutionString(value.resolution) }}</td>
<td>
{{ value.mean !== undefined ? (isNaN(value.mean) ? "NaN" : value.mean.toFixed(2) + "px") : "-" }}
Expand Down Expand Up @@ -429,7 +444,7 @@ const endCalibration = () => {
type="file"
accept=".json"
style="display: none"
@change="readImportedCalibration"
@change="readImportedCalibrationFromCalibDB"
/>
</v-col>
</v-row>
Expand Down Expand Up @@ -478,6 +493,9 @@ const endCalibration = () => {
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showCalDialog" width="80em">
<CameraCalibrationInfoCard v-if="selectedVideoFormat" :video-format="selectedVideoFormat" />
</v-dialog>
</div>
</template>

Expand All @@ -494,6 +512,7 @@ const endCalibration = () => {

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

::-webkit-scrollbar {
Expand Down
Loading