use bytes::Bytes;
use derive_more::{Display, IsVariant};
use mediaframe::frame::Dimensions;
use smol_str::SmolStr;
use crate::domain::{vo::LocalizedText, Rgba};
#[derive(Debug, Clone, Copy, PartialEq, Eq, IsVariant, thiserror::Error)]
#[non_exhaustive]
pub enum DetectionError {
#[error("confidence must be finite and within 0.0..=1.0")]
ConfidenceOutOfRange,
#[error("normalized coordinate must be finite and within 0.0..=1.0")]
CoordOutOfRange,
#[error("bounding box must not extend past the normalized image edge")]
BoxOutOfBounds,
#[error("bounding box width and height must be greater than zero")]
BoxDegenerate,
#[error("percentage must be finite and within 0.0..=100.0")]
PercentageOutOfRange,
#[error("document quad must not have coincident corners")]
QuadCollapsedCorners,
#[error("document quad must enclose a non-zero area")]
QuadZeroArea,
#[error("document quad must be simple (non-self-intersecting, consistent winding)")]
QuadSelfIntersecting,
#[error("mask dimensions width and height must be greater than zero")]
MaskDimensionsDegenerate,
#[error("mask data payload must not be empty")]
MaskPayloadEmpty,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Confidence(f32);
impl Confidence {
#[inline]
pub const fn try_new(value: f32) -> Result<Self, DetectionError> {
if value.is_finite() && value >= 0.0 && value <= 1.0 {
Ok(Self(value))
} else {
Err(DetectionError::ConfidenceOutOfRange)
}
}
#[inline(always)]
pub const fn get(self) -> f32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NormCoord(f32);
impl NormCoord {
#[inline]
pub const fn try_new(value: f32) -> Result<Self, DetectionError> {
if value.is_finite() && value >= 0.0 && value <= 1.0 {
Ok(Self(value))
} else {
Err(DetectionError::CoordOutOfRange)
}
}
#[inline(always)]
pub const fn get(self) -> f32 {
self.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Detection {
label: SmolStr,
confidence: Confidence,
}
impl Detection {
#[inline]
pub fn try_new(label: impl Into<SmolStr>, confidence: f32) -> Result<Self, DetectionError> {
Ok(Self {
label: label.into(),
confidence: Confidence::try_new(confidence)?,
})
}
#[inline(always)]
pub fn label(&self) -> &str {
self.label.as_str()
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[must_use]
#[inline(always)]
pub fn with_label(mut self, label: impl Into<SmolStr>) -> Self {
self.label = label.into();
self
}
#[inline]
pub fn try_with_confidence(mut self, confidence: f32) -> Result<Self, DetectionError> {
self.confidence = Confidence::try_new(confidence)?;
Ok(self)
}
#[inline(always)]
pub fn set_label(&mut self, label: impl Into<SmolStr>) -> &mut Self {
self.label = label.into();
self
}
#[inline]
pub fn try_set_confidence(&mut self, confidence: f32) -> Result<&mut Self, DetectionError> {
self.confidence = Confidence::try_new(confidence)?;
Ok(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BoundingBox {
x: NormCoord,
y: NormCoord,
width: NormCoord,
height: NormCoord,
}
impl BoundingBox {
#[inline]
pub const fn try_new(x: f32, y: f32, width: f32, height: f32) -> Result<Self, DetectionError> {
let x = match NormCoord::try_new(x) {
Ok(v) => v,
Err(e) => return Err(e),
};
let y = match NormCoord::try_new(y) {
Ok(v) => v,
Err(e) => return Err(e),
};
let width = match NormCoord::try_new(width) {
Ok(v) => v,
Err(e) => return Err(e),
};
let height = match NormCoord::try_new(height) {
Ok(v) => v,
Err(e) => return Err(e),
};
if width.get() <= 0.0 || height.get() <= 0.0 {
return Err(DetectionError::BoxDegenerate);
}
if x.get() + width.get() > 1.0 || y.get() + height.get() > 1.0 {
return Err(DetectionError::BoxOutOfBounds);
}
Ok(Self {
x,
y,
width,
height,
})
}
#[inline(always)]
pub const fn x(&self) -> f32 {
self.x.get()
}
#[inline(always)]
pub const fn y(&self) -> f32 {
self.y.get()
}
#[inline(always)]
pub const fn width(&self) -> f32 {
self.width.get()
}
#[inline(always)]
pub const fn height(&self) -> f32 {
self.height.get()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectDetection {
detection: Detection,
bbox: Option<BoundingBox>,
}
impl ObjectDetection {
#[inline(always)]
pub const fn new(detection: Detection, bbox: Option<BoundingBox>) -> Self {
Self { detection, bbox }
}
#[inline(always)]
pub const fn detection_ref(&self) -> &Detection {
&self.detection
}
#[inline(always)]
pub const fn bbox_ref(&self) -> Option<&BoundingBox> {
self.bbox.as_ref()
}
#[must_use]
#[inline(always)]
pub const fn with_bbox(mut self, bbox: Option<BoundingBox>) -> Self {
self.bbox = bbox;
self
}
#[inline(always)]
pub const fn set_bbox(&mut self, bbox: Option<BoundingBox>) -> &mut Self {
self.bbox = bbox;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ActionDetection {
detection: Detection,
}
impl ActionDetection {
#[inline(always)]
pub const fn new(detection: Detection) -> Self {
Self { detection }
}
#[inline(always)]
pub const fn detection_ref(&self) -> &Detection {
&self.detection
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TextDetection {
text: SmolStr,
confidence: Confidence,
bbox: BoundingBox,
}
impl TextDetection {
#[inline]
pub fn try_new(
text: impl Into<SmolStr>,
confidence: f32,
bbox: BoundingBox,
) -> Result<Self, DetectionError> {
Ok(Self {
text: text.into(),
confidence: Confidence::try_new(confidence)?,
bbox,
})
}
#[inline(always)]
pub fn text(&self) -> &str {
self.text.as_str()
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BarcodeDetection {
payload: SmolStr,
symbology: SmolStr,
confidence: Confidence,
bbox: BoundingBox,
}
impl BarcodeDetection {
#[inline]
pub fn try_new(
payload: impl Into<SmolStr>,
symbology: impl Into<SmolStr>,
confidence: f32,
bbox: BoundingBox,
) -> Result<Self, DetectionError> {
Ok(Self {
payload: payload.into(),
symbology: symbology.into(),
confidence: Confidence::try_new(confidence)?,
bbox,
})
}
#[inline(always)]
pub fn payload(&self) -> &str {
self.payload.as_str()
}
#[inline(always)]
pub fn symbology(&self) -> &str {
self.symbology.as_str()
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SaliencyRegion {
bbox: BoundingBox,
confidence: Confidence,
}
impl SaliencyRegion {
#[inline]
pub const fn try_new(bbox: BoundingBox, confidence: f32) -> Result<Self, DetectionError> {
match Confidence::try_new(confidence) {
Ok(confidence) => Ok(Self { bbox, confidence }),
Err(e) => Err(e),
}
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HorizonInfo {
angle: f32,
confidence: Confidence,
}
impl HorizonInfo {
#[inline]
pub const fn try_new(angle: f32, confidence: f32) -> Result<Self, DetectionError> {
match Confidence::try_new(confidence) {
Ok(confidence) => Ok(Self { angle, confidence }),
Err(e) => Err(e),
}
}
#[inline(always)]
pub const fn angle(&self) -> f32 {
self.angle
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DocumentSegment {
top_left: (NormCoord, NormCoord),
top_right: (NormCoord, NormCoord),
bottom_right: (NormCoord, NormCoord),
bottom_left: (NormCoord, NormCoord),
confidence: Confidence,
}
impl DocumentSegment {
#[inline]
pub const fn try_new(
top_left: (f32, f32),
top_right: (f32, f32),
bottom_right: (f32, f32),
bottom_left: (f32, f32),
confidence: f32,
) -> Result<Self, DetectionError> {
let top_left = match norm_pair(top_left) {
Ok(v) => v,
Err(e) => return Err(e),
};
let top_right = match norm_pair(top_right) {
Ok(v) => v,
Err(e) => return Err(e),
};
let bottom_right = match norm_pair(bottom_right) {
Ok(v) => v,
Err(e) => return Err(e),
};
let bottom_left = match norm_pair(bottom_left) {
Ok(v) => v,
Err(e) => return Err(e),
};
let confidence = match Confidence::try_new(confidence) {
Ok(v) => v,
Err(e) => return Err(e),
};
let corners = [
(top_left.0.get(), top_left.1.get()),
(top_right.0.get(), top_right.1.get()),
(bottom_right.0.get(), bottom_right.1.get()),
(bottom_left.0.get(), bottom_left.1.get()),
];
let mut i = 0;
while i < 4 {
let mut j = i + 1;
while j < 4 {
if corners[i].0 == corners[j].0 && corners[i].1 == corners[j].1 {
return Err(DetectionError::QuadCollapsedCorners);
}
j += 1;
}
i += 1;
}
let mut twice_area = 0.0f32;
let mut k = 0;
while k < 4 {
let (x0, y0) = corners[k];
let (x1, y1) = corners[(k + 1) % 4];
twice_area += x0 * y1 - x1 * y0;
k += 1;
}
if twice_area == 0.0 {
return Err(DetectionError::QuadZeroArea);
}
if !consistent_winding(&corners) {
return Err(DetectionError::QuadSelfIntersecting);
}
if segments_intersect(corners[0], corners[1], corners[2], corners[3])
|| segments_intersect(corners[1], corners[2], corners[3], corners[0])
{
return Err(DetectionError::QuadSelfIntersecting);
}
Ok(Self {
top_left,
top_right,
bottom_right,
bottom_left,
confidence,
})
}
#[inline(always)]
pub const fn top_left(&self) -> (f32, f32) {
(self.top_left.0.get(), self.top_left.1.get())
}
#[inline(always)]
pub const fn top_right(&self) -> (f32, f32) {
(self.top_right.0.get(), self.top_right.1.get())
}
#[inline(always)]
pub const fn bottom_right(&self) -> (f32, f32) {
(self.bottom_right.0.get(), self.bottom_right.1.get())
}
#[inline(always)]
pub const fn bottom_left(&self) -> (f32, f32) {
(self.bottom_left.0.get(), self.bottom_left.1.get())
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
}
const fn norm_pair(p: (f32, f32)) -> Result<(NormCoord, NormCoord), DetectionError> {
let x = match NormCoord::try_new(p.0) {
Ok(v) => v,
Err(e) => return Err(e),
};
let y = match NormCoord::try_new(p.1) {
Ok(v) => v,
Err(e) => return Err(e),
};
Ok((x, y))
}
#[inline]
const fn cross(a: (f32, f32), b: (f32, f32), c: (f32, f32)) -> f32 {
(b.0 - a.0) * (c.1 - a.1) - (b.1 - a.1) * (c.0 - a.0)
}
#[inline]
const fn consistent_winding(c: &[(f32, f32); 4]) -> bool {
let mut pos = false;
let mut neg = false;
let mut i = 0;
while i < 4 {
let t = cross(c[i], c[(i + 1) % 4], c[(i + 2) % 4]);
if t > 0.0 {
pos = true;
} else if t < 0.0 {
neg = true;
}
i += 1;
}
!(pos && neg)
}
#[inline]
const fn segments_intersect(
p1: (f32, f32),
p2: (f32, f32),
p3: (f32, f32),
p4: (f32, f32),
) -> bool {
let d1 = cross(p3, p4, p1);
let d2 = cross(p3, p4, p2);
let d3 = cross(p1, p2, p3);
let d4 = cross(p1, p2, p4);
((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
&& ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
}
#[derive(Debug, Clone, PartialEq)]
pub struct BodyPoseJoint {
name: SmolStr,
x: NormCoord,
y: NormCoord,
confidence: Confidence,
}
impl BodyPoseJoint {
#[inline]
pub fn try_new(
name: impl Into<SmolStr>,
x: f32,
y: f32,
confidence: f32,
) -> Result<Self, DetectionError> {
Ok(Self {
name: name.into(),
x: NormCoord::try_new(x)?,
y: NormCoord::try_new(y)?,
confidence: Confidence::try_new(confidence)?,
})
}
#[inline(always)]
pub fn name(&self) -> &str {
self.name.as_str()
}
#[inline(always)]
pub const fn x(&self) -> f32 {
self.x.get()
}
#[inline(always)]
pub const fn y(&self) -> f32 {
self.y.get()
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BodyPose3DJoint {
name: SmolStr,
x: f32,
y: f32,
z: f32,
confidence: Confidence,
}
impl BodyPose3DJoint {
#[inline]
pub fn try_new(
name: impl Into<SmolStr>,
x: f32,
y: f32,
z: f32,
confidence: f32,
) -> Result<Self, DetectionError> {
Ok(Self {
name: name.into(),
x,
y,
z,
confidence: Confidence::try_new(confidence)?,
})
}
#[inline(always)]
pub fn name(&self) -> &str {
self.name.as_str()
}
#[inline(always)]
pub const fn x(&self) -> f32 {
self.x
}
#[inline(always)]
pub const fn y(&self) -> f32 {
self.y
}
#[inline(always)]
pub const fn z(&self) -> f32 {
self.z
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, IsVariant, Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum HandChirality {
#[default]
Unknown,
Left,
Right,
}
impl HandChirality {
#[inline(always)]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Unknown => "unknown",
Self::Left => "left",
Self::Right => "right",
}
}
#[inline]
pub fn from_str(s: &str) -> Option<Self> {
Some(match s {
"unknown" => Self::Unknown,
"left" => Self::Left,
"right" => Self::Right,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, IsVariant, Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum BodyPose3DHeightEstimation {
#[default]
Unknown,
Reference,
Measured,
}
impl BodyPose3DHeightEstimation {
#[inline(always)]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Unknown => "unknown",
Self::Reference => "reference",
Self::Measured => "measured",
}
}
#[inline]
pub fn from_str(s: &str) -> Option<Self> {
Some(match s {
"unknown" => Self::Unknown,
"reference" => Self::Reference,
"measured" => Self::Measured,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BodyPoseDetection {
bbox: BoundingBox,
confidence: Confidence,
joints: std::vec::Vec<BodyPoseJoint>,
}
impl BodyPoseDetection {
#[inline]
pub fn try_new(
bbox: BoundingBox,
confidence: f32,
joints: impl Into<std::vec::Vec<BodyPoseJoint>>,
) -> Result<Self, DetectionError> {
Ok(Self {
bbox,
confidence: Confidence::try_new(confidence)?,
joints: joints.into(),
})
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub fn joints_slice(&self) -> &[BodyPoseJoint] {
&self.joints
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HandPoseDetection {
bbox: BoundingBox,
confidence: Confidence,
chirality: HandChirality,
joints: std::vec::Vec<BodyPoseJoint>,
}
impl HandPoseDetection {
#[inline]
pub fn try_new(
bbox: BoundingBox,
confidence: f32,
chirality: HandChirality,
joints: impl Into<std::vec::Vec<BodyPoseJoint>>,
) -> Result<Self, DetectionError> {
Ok(Self {
bbox,
confidence: Confidence::try_new(confidence)?,
chirality,
joints: joints.into(),
})
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub const fn chirality(&self) -> HandChirality {
self.chirality
}
#[inline(always)]
pub fn joints_slice(&self) -> &[BodyPoseJoint] {
&self.joints
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BodyPose3DDetection {
confidence: Confidence,
body_height: f32,
height_estimation: BodyPose3DHeightEstimation,
joints: std::vec::Vec<BodyPose3DJoint>,
}
impl BodyPose3DDetection {
#[inline]
pub fn try_new(
confidence: f32,
body_height: f32,
height_estimation: BodyPose3DHeightEstimation,
joints: impl Into<std::vec::Vec<BodyPose3DJoint>>,
) -> Result<Self, DetectionError> {
Ok(Self {
confidence: Confidence::try_new(confidence)?,
body_height,
height_estimation,
joints: joints.into(),
})
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub const fn body_height(&self) -> f32 {
self.body_height
}
#[inline(always)]
pub const fn height_estimation(&self) -> BodyPose3DHeightEstimation {
self.height_estimation
}
#[inline(always)]
pub fn joints_slice(&self) -> &[BodyPose3DJoint] {
&self.joints
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SubjectDetection {
detection: Detection,
bbox: BoundingBox,
}
impl SubjectDetection {
#[inline(always)]
pub const fn new(detection: Detection, bbox: BoundingBox) -> Self {
Self { detection, bbox }
}
#[inline(always)]
pub const fn detection_ref(&self) -> &Detection {
&self.detection
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FaceDetection {
bbox: BoundingBox,
confidence: Confidence,
capture_quality: f32,
roll: f32,
yaw: f32,
pitch: f32,
}
impl FaceDetection {
#[inline]
pub const fn try_new(
bbox: BoundingBox,
confidence: f32,
capture_quality: f32,
roll: f32,
yaw: f32,
pitch: f32,
) -> Result<Self, DetectionError> {
match Confidence::try_new(confidence) {
Ok(confidence) => Ok(Self {
bbox,
confidence,
capture_quality,
roll,
yaw,
pitch,
}),
Err(e) => Err(e),
}
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub const fn capture_quality(&self) -> f32 {
self.capture_quality
}
#[inline(always)]
pub const fn roll(&self) -> f32 {
self.roll
}
#[inline(always)]
pub const fn yaw(&self) -> f32 {
self.yaw
}
#[inline(always)]
pub const fn pitch(&self) -> f32 {
self.pitch
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FaceLandmarkRegion {
name: SmolStr,
points: std::vec::Vec<(NormCoord, NormCoord)>,
}
impl FaceLandmarkRegion {
#[inline]
pub fn try_new(
name: impl Into<SmolStr>,
points: impl IntoIterator<Item = (f32, f32)>,
) -> Result<Self, DetectionError> {
let points = points
.into_iter()
.map(norm_pair)
.collect::<Result<std::vec::Vec<_>, _>>()?;
Ok(Self {
name: name.into(),
points,
})
}
#[inline(always)]
pub fn name(&self) -> &str {
self.name.as_str()
}
#[inline]
pub fn points(&self) -> std::vec::Vec<(f32, f32)> {
self.points.iter().map(|p| (p.0.get(), p.1.get())).collect()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FaceLandmarksDetection {
bbox: BoundingBox,
confidence: Confidence,
regions: std::vec::Vec<FaceLandmarkRegion>,
}
impl FaceLandmarksDetection {
#[inline]
pub fn try_new(
bbox: BoundingBox,
confidence: f32,
regions: impl Into<std::vec::Vec<FaceLandmarkRegion>>,
) -> Result<Self, DetectionError> {
Ok(Self {
bbox,
confidence: Confidence::try_new(confidence)?,
regions: regions.into(),
})
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub fn regions_slice(&self) -> &[FaceLandmarkRegion] {
&self.regions
}
}
#[inline]
const fn validate_mask_dimensions(dimensions: Dimensions) -> Result<(), DetectionError> {
if dimensions.width() == 0 || dimensions.height() == 0 {
return Err(DetectionError::MaskDimensionsDegenerate);
}
Ok(())
}
#[derive(Debug, Clone, PartialEq)]
pub struct PersonInstanceMaskDetection {
bbox: BoundingBox,
confidence: Confidence,
instance_index: u32,
dimensions: Dimensions,
data: Bytes,
}
impl PersonInstanceMaskDetection {
#[inline]
pub fn try_new(
bbox: BoundingBox,
confidence: f32,
instance_index: u32,
dimensions: Dimensions,
data: impl Into<Bytes>,
) -> Result<Self, DetectionError> {
validate_mask_dimensions(dimensions)?;
let data = data.into();
if data.is_empty() {
return Err(DetectionError::MaskPayloadEmpty);
}
Ok(Self {
bbox,
confidence: Confidence::try_new(confidence)?,
instance_index,
dimensions,
data,
})
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub const fn instance_index(&self) -> u32 {
self.instance_index
}
#[inline(always)]
pub const fn dimensions(&self) -> Dimensions {
self.dimensions
}
#[inline(always)]
pub fn data(&self) -> &[u8] {
&self.data
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PersonSegmentationMask {
bbox: BoundingBox,
confidence: Confidence,
dimensions: Dimensions,
data: Bytes,
}
impl PersonSegmentationMask {
#[inline]
pub fn try_new(
bbox: BoundingBox,
confidence: f32,
dimensions: Dimensions,
data: impl Into<Bytes>,
) -> Result<Self, DetectionError> {
validate_mask_dimensions(dimensions)?;
let data = data.into();
if data.is_empty() {
return Err(DetectionError::MaskPayloadEmpty);
}
Ok(Self {
bbox,
confidence: Confidence::try_new(confidence)?,
dimensions,
data,
})
}
#[inline(always)]
pub const fn bbox_ref(&self) -> &BoundingBox {
&self.bbox
}
#[inline(always)]
pub const fn confidence(&self) -> f32 {
self.confidence.get()
}
#[inline(always)]
pub const fn dimensions(&self) -> Dimensions {
self.dimensions
}
#[inline(always)]
pub fn data(&self) -> &[u8] {
&self.data
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HumanAnalysis {
subjects: std::vec::Vec<SubjectDetection>,
faces: std::vec::Vec<FaceDetection>,
body_poses: std::vec::Vec<BodyPoseDetection>,
hand_poses: std::vec::Vec<HandPoseDetection>,
body_poses_3d: std::vec::Vec<BodyPose3DDetection>,
instance_masks: std::vec::Vec<PersonInstanceMaskDetection>,
face_rectangles: std::vec::Vec<FaceDetection>,
face_landmarks: std::vec::Vec<FaceLandmarksDetection>,
segmentation_masks: std::vec::Vec<PersonSegmentationMask>,
}
impl HumanAnalysis {
#[inline(always)]
pub const fn new() -> Self {
Self {
subjects: std::vec::Vec::new(),
faces: std::vec::Vec::new(),
body_poses: std::vec::Vec::new(),
hand_poses: std::vec::Vec::new(),
body_poses_3d: std::vec::Vec::new(),
instance_masks: std::vec::Vec::new(),
face_rectangles: std::vec::Vec::new(),
face_landmarks: std::vec::Vec::new(),
segmentation_masks: std::vec::Vec::new(),
}
}
#[inline(always)]
pub fn subjects_slice(&self) -> &[SubjectDetection] {
&self.subjects
}
#[inline(always)]
pub fn faces_slice(&self) -> &[FaceDetection] {
&self.faces
}
#[inline(always)]
pub fn body_poses_slice(&self) -> &[BodyPoseDetection] {
&self.body_poses
}
#[inline(always)]
pub fn hand_poses_slice(&self) -> &[HandPoseDetection] {
&self.hand_poses
}
#[inline(always)]
pub fn body_poses_3d_slice(&self) -> &[BodyPose3DDetection] {
&self.body_poses_3d
}
#[inline(always)]
pub fn instance_masks_slice(&self) -> &[PersonInstanceMaskDetection] {
&self.instance_masks
}
#[inline(always)]
pub fn face_rectangles_slice(&self) -> &[FaceDetection] {
&self.face_rectangles
}
#[inline(always)]
pub fn face_landmarks_slice(&self) -> &[FaceLandmarksDetection] {
&self.face_landmarks
}
#[inline(always)]
pub fn segmentation_masks_slice(&self) -> &[PersonSegmentationMask] {
&self.segmentation_masks
}
#[must_use]
#[inline(always)]
pub fn with_subjects(mut self, v: impl Into<std::vec::Vec<SubjectDetection>>) -> Self {
self.subjects = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_faces(mut self, v: impl Into<std::vec::Vec<FaceDetection>>) -> Self {
self.faces = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_body_poses(mut self, v: impl Into<std::vec::Vec<BodyPoseDetection>>) -> Self {
self.body_poses = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_hand_poses(mut self, v: impl Into<std::vec::Vec<HandPoseDetection>>) -> Self {
self.hand_poses = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_body_poses_3d(mut self, v: impl Into<std::vec::Vec<BodyPose3DDetection>>) -> Self {
self.body_poses_3d = v.into();
self
}
#[inline(always)]
pub fn with_instance_masks(
mut self,
v: impl Into<std::vec::Vec<PersonInstanceMaskDetection>>,
) -> Self {
self.instance_masks = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_face_rectangles(mut self, v: impl Into<std::vec::Vec<FaceDetection>>) -> Self {
self.face_rectangles = v.into();
self
}
#[inline(always)]
pub fn with_face_landmarks(
mut self,
v: impl Into<std::vec::Vec<FaceLandmarksDetection>>,
) -> Self {
self.face_landmarks = v.into();
self
}
#[inline(always)]
pub fn with_segmentation_masks(
mut self,
v: impl Into<std::vec::Vec<PersonSegmentationMask>>,
) -> Self {
self.segmentation_masks = v.into();
self
}
#[inline(always)]
pub fn set_subjects(&mut self, v: impl Into<std::vec::Vec<SubjectDetection>>) -> &mut Self {
self.subjects = v.into();
self
}
#[inline(always)]
pub fn set_faces(&mut self, v: impl Into<std::vec::Vec<FaceDetection>>) -> &mut Self {
self.faces = v.into();
self
}
#[inline(always)]
pub fn set_body_poses(&mut self, v: impl Into<std::vec::Vec<BodyPoseDetection>>) -> &mut Self {
self.body_poses = v.into();
self
}
#[inline(always)]
pub fn set_hand_poses(&mut self, v: impl Into<std::vec::Vec<HandPoseDetection>>) -> &mut Self {
self.hand_poses = v.into();
self
}
#[inline(always)]
pub fn set_body_poses_3d(
&mut self,
v: impl Into<std::vec::Vec<BodyPose3DDetection>>,
) -> &mut Self {
self.body_poses_3d = v.into();
self
}
#[inline(always)]
pub fn set_instance_masks(
&mut self,
v: impl Into<std::vec::Vec<PersonInstanceMaskDetection>>,
) -> &mut Self {
self.instance_masks = v.into();
self
}
#[inline(always)]
pub fn set_face_rectangles(&mut self, v: impl Into<std::vec::Vec<FaceDetection>>) -> &mut Self {
self.face_rectangles = v.into();
self
}
#[inline(always)]
pub fn set_face_landmarks(
&mut self,
v: impl Into<std::vec::Vec<FaceLandmarksDetection>>,
) -> &mut Self {
self.face_landmarks = v.into();
self
}
#[inline(always)]
pub fn set_segmentation_masks(
&mut self,
v: impl Into<std::vec::Vec<PersonSegmentationMask>>,
) -> &mut Self {
self.segmentation_masks = v.into();
self
}
}
impl Default for HumanAnalysis {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AnimalAnalysis {
subjects: std::vec::Vec<SubjectDetection>,
body_poses: std::vec::Vec<BodyPoseDetection>,
}
impl AnimalAnalysis {
#[inline(always)]
pub const fn new() -> Self {
Self {
subjects: std::vec::Vec::new(),
body_poses: std::vec::Vec::new(),
}
}
#[inline(always)]
pub fn subjects_slice(&self) -> &[SubjectDetection] {
&self.subjects
}
#[inline(always)]
pub fn body_poses_slice(&self) -> &[BodyPoseDetection] {
&self.body_poses
}
#[must_use]
#[inline(always)]
pub fn with_subjects(mut self, v: impl Into<std::vec::Vec<SubjectDetection>>) -> Self {
self.subjects = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_body_poses(mut self, v: impl Into<std::vec::Vec<BodyPoseDetection>>) -> Self {
self.body_poses = v.into();
self
}
#[inline(always)]
pub fn set_subjects(&mut self, v: impl Into<std::vec::Vec<SubjectDetection>>) -> &mut Self {
self.subjects = v.into();
self
}
#[inline(always)]
pub fn set_body_poses(&mut self, v: impl Into<std::vec::Vec<BodyPoseDetection>>) -> &mut Self {
self.body_poses = v.into();
self
}
}
impl Default for AnimalAnalysis {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Aesthetics {
overall_score: f32,
is_utility: bool,
}
impl Aesthetics {
#[inline(always)]
pub const fn new(overall_score: f32, is_utility: bool) -> Self {
Self {
overall_score,
is_utility,
}
}
#[inline(always)]
pub const fn overall_score(&self) -> f32 {
self.overall_score
}
#[inline(always)]
pub const fn is_utility(&self) -> bool {
self.is_utility
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DominantColor {
rgb: Rgba,
name: SmolStr,
percentage: f32,
population: u32,
}
impl DominantColor {
#[inline]
pub fn try_new(
rgb: Rgba,
name: impl Into<SmolStr>,
percentage: f32,
population: u32,
) -> Result<Self, DetectionError> {
if !(percentage.is_finite() && (0.0..=100.0).contains(&percentage)) {
return Err(DetectionError::PercentageOutOfRange);
}
Ok(Self {
rgb,
name: name.into(),
percentage,
population,
})
}
#[inline(always)]
pub const fn rgb(&self) -> Rgba {
self.rgb
}
#[inline(always)]
pub fn name(&self) -> &str {
self.name.as_str()
}
#[inline(always)]
pub const fn percentage(&self) -> f32 {
self.percentage
}
#[inline(always)]
pub const fn population(&self) -> u32 {
self.population
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VlmAnalysis {
categories: std::vec::Vec<LocalizedText>,
description: LocalizedText,
tags: std::vec::Vec<LocalizedText>,
shot_type: SmolStr,
objects: std::vec::Vec<LocalizedText>,
subjects: std::vec::Vec<LocalizedText>,
mood: std::vec::Vec<LocalizedText>,
emotion: std::vec::Vec<LocalizedText>,
lighting: std::vec::Vec<LocalizedText>,
}
impl VlmAnalysis {
#[inline(always)]
pub fn new() -> Self {
Self {
categories: std::vec::Vec::new(),
description: LocalizedText::new(),
tags: std::vec::Vec::new(),
shot_type: SmolStr::default(),
objects: std::vec::Vec::new(),
subjects: std::vec::Vec::new(),
mood: std::vec::Vec::new(),
emotion: std::vec::Vec::new(),
lighting: std::vec::Vec::new(),
}
}
#[inline(always)]
pub fn categories_slice(&self) -> &[LocalizedText] {
&self.categories
}
#[inline(always)]
pub const fn description_ref(&self) -> &LocalizedText {
&self.description
}
#[inline(always)]
pub fn tags_slice(&self) -> &[LocalizedText] {
&self.tags
}
#[inline(always)]
pub fn shot_type(&self) -> &str {
self.shot_type.as_str()
}
#[inline(always)]
pub fn objects_slice(&self) -> &[LocalizedText] {
&self.objects
}
#[inline(always)]
pub fn subjects_slice(&self) -> &[LocalizedText] {
&self.subjects
}
#[inline(always)]
pub fn mood_slice(&self) -> &[LocalizedText] {
&self.mood
}
#[inline(always)]
pub fn emotion_slice(&self) -> &[LocalizedText] {
&self.emotion
}
#[inline(always)]
pub fn lighting_slice(&self) -> &[LocalizedText] {
&self.lighting
}
#[must_use]
#[inline(always)]
pub fn with_categories(mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> Self {
self.categories = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_description(mut self, v: LocalizedText) -> Self {
self.description = v;
self
}
#[must_use]
#[inline(always)]
pub fn with_tags(mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> Self {
self.tags = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_shot_type(mut self, v: impl Into<SmolStr>) -> Self {
self.shot_type = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_objects(mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> Self {
self.objects = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_subjects(mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> Self {
self.subjects = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_mood(mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> Self {
self.mood = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_emotion(mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> Self {
self.emotion = v.into();
self
}
#[must_use]
#[inline(always)]
pub fn with_lighting(mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> Self {
self.lighting = v.into();
self
}
#[inline(always)]
pub fn set_categories(&mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> &mut Self {
self.categories = v.into();
self
}
#[inline(always)]
pub fn set_description(&mut self, v: LocalizedText) -> &mut Self {
self.description = v;
self
}
#[inline(always)]
pub fn set_tags(&mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> &mut Self {
self.tags = v.into();
self
}
#[inline(always)]
pub fn set_shot_type(&mut self, v: impl Into<SmolStr>) -> &mut Self {
self.shot_type = v.into();
self
}
#[inline(always)]
pub fn set_objects(&mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> &mut Self {
self.objects = v.into();
self
}
#[inline(always)]
pub fn set_subjects(&mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> &mut Self {
self.subjects = v.into();
self
}
#[inline(always)]
pub fn set_mood(&mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> &mut Self {
self.mood = v.into();
self
}
#[inline(always)]
pub fn set_emotion(&mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> &mut Self {
self.emotion = v.into();
self
}
#[inline(always)]
pub fn set_lighting(&mut self, v: impl Into<std::vec::Vec<LocalizedText>>) -> &mut Self {
self.lighting = v.into();
self
}
}
impl Default for VlmAnalysis {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn hand_chirality_slug_roundtrip() {
for v in [
HandChirality::Unknown,
HandChirality::Left,
HandChirality::Right,
] {
assert_eq!(HandChirality::from_str(v.as_str()), Some(v), "{v:?}");
}
assert_eq!(HandChirality::from_str("not_a_slug"), None);
}
#[test]
fn body_pose_3d_height_estimation_slug_roundtrip() {
for v in [
BodyPose3DHeightEstimation::Unknown,
BodyPose3DHeightEstimation::Reference,
BodyPose3DHeightEstimation::Measured,
] {
assert_eq!(
BodyPose3DHeightEstimation::from_str(v.as_str()),
Some(v),
"{v:?}"
);
}
assert_eq!(BodyPose3DHeightEstimation::from_str("not_a_slug"), None);
}
#[test]
fn confidence_rejects_nan_inf_and_out_of_range() {
for bad in [
f32::NAN,
f32::INFINITY,
f32::NEG_INFINITY,
-0.001,
1.001,
2.0,
] {
assert_eq!(
Confidence::try_new(bad).err(),
Some(DetectionError::ConfidenceOutOfRange),
"{bad} should be rejected"
);
}
for ok in [0.0, 0.5, 1.0] {
assert_eq!(Confidence::try_new(ok).unwrap().get(), ok);
}
}
#[test]
fn norm_coord_rejects_nan_inf_and_out_of_range() {
for bad in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, -0.5, 1.5] {
assert_eq!(
NormCoord::try_new(bad).err(),
Some(DetectionError::CoordOutOfRange),
"{bad} should be rejected"
);
}
for ok in [0.0, 0.25, 1.0] {
assert_eq!(NormCoord::try_new(ok).unwrap().get(), ok);
}
}
#[test]
fn detection_ctor_and_mutators_validate_confidence() {
assert_eq!(
Detection::try_new("dog", f32::NAN).err(),
Some(DetectionError::ConfidenceOutOfRange)
);
assert_eq!(
Detection::try_new("dog", 1.5).err(),
Some(DetectionError::ConfidenceOutOfRange)
);
let d = Detection::try_new("dog", 0.9).unwrap();
assert_eq!(d.confidence(), 0.9);
assert_eq!(
d.clone().try_with_confidence(f32::INFINITY).err(),
Some(DetectionError::ConfidenceOutOfRange)
);
let mut d = d;
assert_eq!(
d.try_set_confidence(-0.1).err(),
Some(DetectionError::ConfidenceOutOfRange)
);
assert_eq!(d.confidence(), 0.9);
d.try_set_confidence(0.2).unwrap();
assert_eq!(d.confidence(), 0.2);
assert!(DetectionError::ConfidenceOutOfRange.is_confidence_out_of_range());
}
#[test]
fn bounding_box_ctor_validates_every_component() {
let ok = BoundingBox::try_new(0.1, 0.2, 0.3, 0.4).unwrap();
assert_eq!(
(ok.x(), ok.y(), ok.width(), ok.height()),
(0.1, 0.2, 0.3, 0.4)
);
assert_eq!(
BoundingBox::try_new(f32::NAN, 0.0, 0.0, 0.0).err(),
Some(DetectionError::CoordOutOfRange)
);
assert_eq!(
BoundingBox::try_new(0.0, 1.5, 0.0, 0.0).err(),
Some(DetectionError::CoordOutOfRange)
);
assert_eq!(
BoundingBox::try_new(0.0, 0.0, -0.1, 0.0).err(),
Some(DetectionError::CoordOutOfRange)
);
assert_eq!(
BoundingBox::try_new(0.0, 0.0, 0.0, f32::INFINITY).err(),
Some(DetectionError::CoordOutOfRange)
);
}
#[test]
fn bounding_box_rejects_edge_overflow() {
assert_eq!(
BoundingBox::try_new(0.9, 0.0, 0.2, 0.1).err(),
Some(DetectionError::BoxOutOfBounds)
);
assert_eq!(
BoundingBox::try_new(0.0, 0.9, 0.1, 0.2).err(),
Some(DetectionError::BoxOutOfBounds)
);
assert!(DetectionError::BoxOutOfBounds.is_box_out_of_bounds());
assert!(BoundingBox::try_new(0.8, 0.7, 0.2, 0.3).is_ok());
assert!(BoundingBox::try_new(0.0, 0.0, 1.0, 1.0).is_ok());
}
#[test]
fn bounding_box_rejects_zero_extent() {
assert_eq!(
BoundingBox::try_new(0.1, 0.2, 0.0, 0.4).err(),
Some(DetectionError::BoxDegenerate)
);
assert_eq!(
BoundingBox::try_new(0.1, 0.2, 0.3, 0.0).err(),
Some(DetectionError::BoxDegenerate)
);
assert!(DetectionError::BoxDegenerate.is_box_degenerate());
assert!(BoundingBox::try_new(0.1, 0.2, 0.001, 0.001).is_ok());
}
#[test]
fn dominant_color_validates_percentage() {
let rgb = Rgba::default();
for bad in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, -0.001, 100.001] {
assert_eq!(
DominantColor::try_new(rgb, "red", bad, 1).err(),
Some(DetectionError::PercentageOutOfRange),
"{bad} should be rejected"
);
}
assert!(DetectionError::PercentageOutOfRange.is_percentage_out_of_range());
for ok in [0.0, 42.5, 100.0] {
assert_eq!(
DominantColor::try_new(rgb, "red", ok, 1)
.unwrap()
.percentage(),
ok
);
}
}
#[test]
fn detection_vos_reject_bad_confidence() {
let bb = BoundingBox::try_new(0.0, 0.0, 1.0, 1.0).unwrap();
assert!(TextDetection::try_new("hi", f32::NAN, bb).is_err());
assert!(BarcodeDetection::try_new("p", "qr", 2.0, bb).is_err());
assert!(SaliencyRegion::try_new(bb, -1.0).is_err());
assert!(HorizonInfo::try_new(0.0, f32::INFINITY).is_err());
assert!(BodyPoseJoint::try_new("j", 0.5, 0.5, 1.5).is_err());
assert!(BodyPoseJoint::try_new("j", 1.5, 0.5, 0.5).is_err());
assert!(BodyPose3DJoint::try_new("j", 0.0, 0.0, 0.0, f32::NAN).is_err());
assert!(BodyPoseDetection::try_new(bb, 9.0, std::vec::Vec::new()).is_err());
assert!(
HandPoseDetection::try_new(bb, f32::NAN, HandChirality::Left, std::vec::Vec::new()).is_err()
);
assert!(BodyPose3DDetection::try_new(
-0.5,
1.7,
BodyPose3DHeightEstimation::Measured,
std::vec::Vec::new()
)
.is_err());
assert!(FaceDetection::try_new(bb, 1.2, 0.0, 0.0, 0.0, 0.0).is_err());
assert!(FaceLandmarksDetection::try_new(bb, f32::NAN, std::vec::Vec::new()).is_err());
assert!(TextDetection::try_new("hi", 0.8, bb).is_ok());
assert!(BodyPoseJoint::try_new("j", 0.5, 0.5, 0.9).is_ok());
assert!(FaceDetection::try_new(bb, 0.9, 0.5, 0.1, 0.2, 0.3).is_ok());
}
#[test]
fn document_segment_and_landmark_region_validate_coords() {
let tl = (0.1, 0.1);
let tr = (0.9, 0.1);
let br = (0.9, 0.9);
let bl = (0.1, 0.9);
assert!(DocumentSegment::try_new(tl, tr, br, bl, 0.9).is_ok());
assert_eq!(
DocumentSegment::try_new((1.5, 0.5), tr, br, bl, 0.9).err(),
Some(DetectionError::CoordOutOfRange)
);
assert_eq!(
DocumentSegment::try_new(tl, tr, br, bl, 2.0).err(),
Some(DetectionError::ConfidenceOutOfRange)
);
assert!(FaceLandmarkRegion::try_new("leftEye", [(0.1, 0.2), (0.3, 0.4)]).is_ok());
assert_eq!(
FaceLandmarkRegion::try_new("leftEye", [(0.1, 0.2), (f32::NAN, 0.4)]).err(),
Some(DetectionError::CoordOutOfRange)
);
}
#[test]
fn document_segment_rejects_degenerate_quad() {
let p = (0.5, 0.5);
assert_eq!(
DocumentSegment::try_new(p, p, p, p, 0.9).err(),
Some(DetectionError::QuadCollapsedCorners)
);
assert_eq!(
DocumentSegment::try_new((0.1, 0.1), (0.9, 0.5), (0.9, 0.5), (0.1, 0.9), 0.9).err(),
Some(DetectionError::QuadCollapsedCorners)
);
assert!(DetectionError::QuadCollapsedCorners.is_quad_collapsed_corners());
assert_eq!(
DocumentSegment::try_new((0.1, 0.1), (0.2, 0.2), (0.3, 0.3), (0.4, 0.4), 0.9).err(),
Some(DetectionError::QuadZeroArea)
);
assert!(DetectionError::QuadZeroArea.is_quad_zero_area());
assert!(DocumentSegment::try_new((0.1, 0.1), (0.9, 0.1), (0.9, 0.9), (0.1, 0.9), 0.9).is_ok());
}
#[test]
fn document_segment_rejects_self_intersecting_quad() {
let bowtie = DocumentSegment::try_new(
(0.1, 0.1), (0.2, 0.2), (0.1, 0.2), (0.9, 0.1), 0.9,
);
assert_eq!(bowtie.err(), Some(DetectionError::QuadSelfIntersecting));
assert!(DetectionError::QuadSelfIntersecting.is_quad_self_intersecting());
let bowtie2 = DocumentSegment::try_new(
(0.1, 0.1), (0.1, 0.2), (0.2, 0.1), (0.2, 0.8), 0.9,
);
assert_eq!(bowtie2.err(), Some(DetectionError::QuadSelfIntersecting));
assert!(
DocumentSegment::try_new((0.12, 0.10), (0.88, 0.14), (0.90, 0.86), (0.10, 0.90), 0.9).is_ok()
);
}
#[test]
fn instance_mask_rejects_zero_dimensions() {
let bbox = BoundingBox::try_new(0.1, 0.1, 0.5, 0.5).unwrap();
assert_eq!(
PersonInstanceMaskDetection::try_new(bbox, 0.9, 0, Dimensions::new(0, 0), &b""[..]).err(),
Some(DetectionError::MaskDimensionsDegenerate)
);
assert_eq!(
PersonInstanceMaskDetection::try_new(bbox, 0.9, 0, Dimensions::new(0, 32), &b""[..]).err(),
Some(DetectionError::MaskDimensionsDegenerate)
);
assert_eq!(
PersonInstanceMaskDetection::try_new(bbox, 0.9, 0, Dimensions::new(32, 0), &b""[..]).err(),
Some(DetectionError::MaskDimensionsDegenerate)
);
assert!(DetectionError::MaskDimensionsDegenerate.is_mask_dimensions_degenerate());
assert!(PersonInstanceMaskDetection::try_new(
bbox,
0.9,
0,
Dimensions::new(64, 48),
&b"\x00\xff"[..]
)
.is_ok());
}
#[test]
fn instance_mask_rejects_empty_payload() {
let bbox = BoundingBox::try_new(0.1, 0.1, 0.5, 0.5).unwrap();
assert_eq!(
PersonInstanceMaskDetection::try_new(bbox, 0.9, 0, Dimensions::new(64, 48), &b""[..]).err(),
Some(DetectionError::MaskPayloadEmpty)
);
assert!(DetectionError::MaskPayloadEmpty.is_mask_payload_empty());
}
#[test]
fn segmentation_mask_rejects_zero_dimensions() {
let bbox = BoundingBox::try_new(0.0, 0.0, 1.0, 1.0).unwrap();
assert_eq!(
PersonSegmentationMask::try_new(bbox, 0.9, Dimensions::new(0, 0), &b""[..]).err(),
Some(DetectionError::MaskDimensionsDegenerate)
);
assert_eq!(
PersonSegmentationMask::try_new(bbox, 0.9, Dimensions::new(0, 32), &b""[..]).err(),
Some(DetectionError::MaskDimensionsDegenerate)
);
assert_eq!(
PersonSegmentationMask::try_new(bbox, 0.9, Dimensions::new(32, 0), &b""[..]).err(),
Some(DetectionError::MaskDimensionsDegenerate)
);
assert!(
PersonSegmentationMask::try_new(bbox, 0.9, Dimensions::new(64, 48), &b"\x00\xff"[..]).is_ok()
);
}
#[test]
fn segmentation_mask_rejects_empty_payload() {
let bbox = BoundingBox::try_new(0.0, 0.0, 1.0, 1.0).unwrap();
assert_eq!(
PersonSegmentationMask::try_new(bbox, 0.9, Dimensions::new(64, 48), &b""[..]).err(),
Some(DetectionError::MaskPayloadEmpty)
);
assert!(DetectionError::MaskPayloadEmpty.is_mask_payload_empty());
}
}