use serde::{Deserialize, Serialize};
use calib_targets_chessboard::DetectorParams;
use calib_targets_core::{GridAlignment, GridCoords, LabeledCorner, TargetDetection, TargetKind};
use nalgebra::Point2;
use crate::circle_score::{CirclePolarity, CircleScoreParams};
use crate::coords::{CellCoords, CellOffset};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct MarkerCircleSpec {
pub cell: CellCoords,
pub polarity: CirclePolarity,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MarkerBoardSpec {
pub rows: u32,
pub cols: u32,
#[serde(default)]
pub cell_size: Option<f32>,
pub circles: [MarkerCircleSpec; 3],
}
impl Default for MarkerBoardSpec {
fn default() -> Self {
Self {
rows: 6,
cols: 8,
cell_size: None,
circles: [
MarkerCircleSpec {
cell: CellCoords { i: 2, j: 2 },
polarity: CirclePolarity::White,
},
MarkerCircleSpec {
cell: CellCoords { i: 3, j: 2 },
polarity: CirclePolarity::Black,
},
MarkerCircleSpec {
cell: CellCoords { i: 2, j: 3 },
polarity: CirclePolarity::White,
},
],
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CircleMatchParams {
pub max_candidates_per_polarity: usize,
pub max_distance_cells: Option<f32>,
pub min_offset_inliers: usize,
}
impl Default for CircleMatchParams {
fn default() -> Self {
Self {
max_candidates_per_polarity: 6,
max_distance_cells: None,
min_offset_inliers: 1,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MarkerBoardParams {
pub layout: MarkerBoardSpec,
#[serde(default = "default_marker_chessboard_params")]
pub chessboard: DetectorParams,
#[serde(default)]
pub circle_score: CircleScoreParams,
#[serde(default)]
pub match_params: CircleMatchParams,
#[serde(default)]
pub roi_cells: Option<[i32; 4]>,
}
impl MarkerBoardParams {
pub fn new(layout: MarkerBoardSpec) -> Self {
Self {
layout,
chessboard: default_marker_chessboard_params(),
circle_score: CircleScoreParams::default(),
match_params: CircleMatchParams::default(),
roi_cells: None,
}
}
}
impl Default for MarkerBoardParams {
fn default() -> Self {
Self::new(MarkerBoardSpec::default())
}
}
fn default_marker_chessboard_params() -> DetectorParams {
DetectorParams::default()
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CircleMatch {
pub expected: MarkerCircleSpec,
pub matched_index: Option<usize>,
pub distance_cells: Option<f32>,
pub offset_cells: Option<CellOffset>,
}
#[non_exhaustive]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MarkerBoardDetectionResult {
pub corners: Vec<MarkerBoardCorner>,
pub alignment: Option<GridAlignment>,
}
impl MarkerBoardDetectionResult {
pub fn new(corners: Vec<MarkerBoardCorner>, alignment: Option<GridAlignment>) -> Self {
Self { corners, alignment }
}
pub(crate) fn from_target_detection(
detection: TargetDetection,
alignment: Option<GridAlignment>,
) -> Self {
debug_assert_eq!(detection.kind, TargetKind::CheckerboardMarker);
let input_len = detection.corners.len();
let corners: Vec<MarkerBoardCorner> = detection
.corners
.into_iter()
.filter_map(MarkerBoardCorner::from_labeled)
.collect();
debug_assert_eq!(corners.len(), input_len);
Self::new(corners, alignment)
}
pub fn target_detection(&self) -> TargetDetection {
TargetDetection::new(
TargetKind::CheckerboardMarker,
self.corners
.iter()
.map(MarkerBoardCorner::to_labeled)
.collect(),
)
}
pub fn aligned_detection(&self) -> Option<TargetDetection> {
self.alignment.map(|_| self.target_detection())
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MarkerBoardCorner {
pub position: Point2<f32>,
pub grid: GridCoords,
pub id: Option<u32>,
#[serde(default)]
pub target_position: Option<Point2<f32>>,
pub score: f32,
}
impl MarkerBoardCorner {
pub fn new(position: Point2<f32>, grid: GridCoords, score: f32) -> Self {
Self {
position,
grid,
id: None,
target_position: None,
score,
}
}
pub(crate) fn from_labeled(corner: LabeledCorner) -> Option<Self> {
Some(Self {
position: corner.position,
grid: corner.grid?,
id: corner.id,
target_position: corner.target_position,
score: corner.score,
})
}
#[must_use]
pub fn with_id(mut self, id: u32) -> Self {
self.id = Some(id);
self
}
#[must_use]
pub fn with_target_position(mut self, target_position: Point2<f32>) -> Self {
self.target_position = Some(target_position);
self
}
pub fn to_labeled(&self) -> LabeledCorner {
let mut corner = LabeledCorner::new(self.position, self.score).with_grid(self.grid);
if let Some(id) = self.id {
corner = corner.with_id(id);
}
if let Some(target_position) = self.target_position {
corner = corner.with_target_position(target_position);
}
corner
}
}