#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CropDimensions {
width: usize,
height: usize,
}
impl CropDimensions {
#[inline(always)]
pub const fn new(width: usize, height: usize) -> Self {
Self { width, height }
}
#[inline(always)]
pub const fn width(&self) -> usize {
self.width
}
#[inline(always)]
pub const fn height(&self) -> usize {
self.height
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CropDataLength {
got: usize,
expected: usize,
}
impl CropDataLength {
#[inline(always)]
pub const fn new(got: usize, expected: usize) -> Self {
Self { got, expected }
}
#[inline(always)]
pub const fn got(&self) -> usize {
self.got
}
#[inline(always)]
pub const fn expected(&self) -> usize {
self.expected
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DegenerateLandmarks {
spread: f32,
}
impl DegenerateLandmarks {
#[inline(always)]
pub const fn new(spread: f32) -> Self {
Self { spread }
}
#[inline(always)]
pub const fn spread(&self) -> f32 {
self.spread
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
pub enum LandmarkSet {
Source,
Target,
}
impl LandmarkSet {
#[inline(always)]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Source => "source",
Self::Target => "target",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonFiniteLandmark {
set: LandmarkSet,
index: usize,
}
impl NonFiniteLandmark {
#[inline(always)]
pub const fn new(set: LandmarkSet, index: usize) -> Self {
Self { set, index }
}
#[inline(always)]
pub const fn set(&self) -> LandmarkSet {
self.set
}
#[inline(always)]
pub const fn index(&self) -> usize {
self.index
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
pub enum TransformParameter {
A,
B,
Tx,
Ty,
}
impl TransformParameter {
#[inline(always)]
pub const fn as_str(&self) -> &'static str {
match self {
Self::A => "a",
Self::B => "b",
Self::Tx => "tx",
Self::Ty => "ty",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonFiniteTransform {
parameter: TransformParameter,
}
impl NonFiniteTransform {
#[inline(always)]
pub const fn new(parameter: TransformParameter) -> Self {
Self { parameter }
}
#[inline(always)]
pub const fn parameter(&self) -> TransformParameter {
self.parameter
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NonInvertibleTransform {
scale: f64,
}
impl NonInvertibleTransform {
#[inline(always)]
pub const fn new(scale: f64) -> Self {
Self { scale }
}
#[inline(always)]
pub const fn scale(&self) -> f64 {
self.scale
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
pub enum CoordinateAxis {
X,
Y,
}
impl CoordinateAxis {
#[inline(always)]
pub const fn as_str(&self) -> &'static str {
match self {
Self::X => "horizontal",
Self::Y => "vertical",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
pub enum CoordinateTerm {
ColumnDelta,
RowOrigin,
Sum,
}
impl CoordinateTerm {
#[inline(always)]
pub const fn as_str(&self) -> &'static str {
match self {
Self::ColumnDelta => "per-column term",
Self::RowOrigin => "per-row term",
Self::Sum => "summed term",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CoordinateOverflow {
axis: CoordinateAxis,
term: CoordinateTerm,
value: f64,
}
impl CoordinateOverflow {
#[inline(always)]
pub const fn new(axis: CoordinateAxis, term: CoordinateTerm, value: f64) -> Self {
Self { axis, term, value }
}
#[inline(always)]
pub const fn axis(&self) -> CoordinateAxis {
self.axis
}
#[inline(always)]
pub const fn term(&self) -> CoordinateTerm {
self.term
}
#[inline(always)]
pub const fn value(&self) -> f64 {
self.value
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContractMismatch {
feature: String,
expected: String,
actual: String,
}
impl ContractMismatch {
#[inline(always)]
pub const fn new(feature: String, expected: String, actual: String) -> Self {
Self {
feature,
expected,
actual,
}
}
#[inline(always)]
pub fn feature(&self) -> &str {
&self.feature
}
#[inline(always)]
pub fn expected(&self) -> &str {
&self.expected
}
#[inline(always)]
pub fn actual(&self) -> &str {
&self.actual
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutputShape {
got: Vec<usize>,
expected: Vec<usize>,
}
impl OutputShape {
#[inline(always)]
pub const fn new(got: Vec<usize>, expected: Vec<usize>) -> Self {
Self { got, expected }
}
#[inline(always)]
pub fn got(&self) -> &[usize] {
&self.got
}
#[inline(always)]
pub fn expected(&self) -> &[usize] {
&self.expected
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutputElementCount {
got: usize,
expected: usize,
}
impl OutputElementCount {
#[inline(always)]
pub const fn new(got: usize, expected: usize) -> Self {
Self { got, expected }
}
#[inline(always)]
pub const fn got(&self) -> usize {
self.got
}
#[inline(always)]
pub const fn expected(&self) -> usize {
self.expected
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BatchRow {
row: usize,
}
impl BatchRow {
#[inline(always)]
pub const fn new(row: usize) -> Self {
Self { row }
}
#[inline(always)]
pub const fn row(&self) -> usize {
self.row
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonFiniteOutput {
row: usize,
component: usize,
}
impl NonFiniteOutput {
#[inline(always)]
pub const fn new(row: usize, component: usize) -> Self {
Self { row, component }
}
#[inline(always)]
pub const fn row(&self) -> usize {
self.row
}
#[inline(always)]
pub const fn component(&self) -> usize {
self.component
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
pub enum EmbeddingSpaceField {
Artifact,
InputFeature,
OutputFeature,
Dim,
ChannelOrder,
TensorLayout,
PreprocessingScale,
PreprocessingBias,
}
impl EmbeddingSpaceField {
#[inline(always)]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Artifact => "artifact digest",
Self::InputFeature => "input feature name",
Self::OutputFeature => "output feature name",
Self::Dim => "dim",
Self::ChannelOrder => "preprocessing channel order",
Self::TensorLayout => "preprocessing tensor layout",
Self::PreprocessingScale => "preprocessing scale",
Self::PreprocessingBias => "preprocessing bias",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IncomparableEmbeddings {
field: EmbeddingSpaceField,
}
impl IncomparableEmbeddings {
#[inline(always)]
pub const fn new(field: EmbeddingSpaceField) -> Self {
Self { field }
}
#[inline(always)]
pub const fn field(&self) -> EmbeddingSpaceField {
self.field
}
}
#[derive(Debug, thiserror::Error)]
#[error("failed to hash the model artifact at `{path}`: {source}")]
pub struct DigestFailure {
path: std::path::PathBuf,
#[source]
source: std::io::Error,
}
impl DigestFailure {
#[inline(always)]
pub const fn new(path: std::path::PathBuf, source: std::io::Error) -> Self {
Self { path, source }
}
#[inline(always)]
pub fn path(&self) -> &std::path::Path {
&self.path
}
#[inline(always)]
pub const fn source(&self) -> &std::io::Error {
&self.source
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
pub enum PreprocessingField {
#[display("scale")]
Scale,
#[display("bias[{_0}]")]
Bias(usize),
#[display("{_0}")]
Map(PreprocessingMap),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("byte {byte} · scale + bias[{channel}]")]
pub struct PreprocessingMap {
channel: usize,
byte: u8,
}
impl PreprocessingMap {
#[inline(always)]
pub const fn new(channel: usize, byte: u8) -> Self {
Self { channel, byte }
}
#[inline(always)]
pub const fn channel(&self) -> usize {
self.channel
}
#[inline(always)]
pub const fn byte(&self) -> u8 {
self.byte
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonFinitePreprocessing {
field: PreprocessingField,
}
impl NonFinitePreprocessing {
#[inline(always)]
pub const fn new(field: PreprocessingField) -> Self {
Self { field }
}
#[inline(always)]
pub const fn field(&self) -> PreprocessingField {
self.field
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ZeroEmbeddingWidth {
output: &'static str,
}
impl ZeroEmbeddingWidth {
#[inline(always)]
pub const fn new(output: &'static str) -> Self {
Self { output }
}
#[inline(always)]
pub const fn output(&self) -> &'static str {
self.output
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
pub enum PredictionTensor {
#[display("input")]
Input,
#[display("output")]
Output,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ElementCountOverflow {
tensor: PredictionTensor,
batch: usize,
per_row: usize,
}
impl ElementCountOverflow {
#[inline(always)]
pub const fn new(tensor: PredictionTensor, batch: usize, per_row: usize) -> Self {
Self {
tensor,
batch,
per_row,
}
}
#[inline(always)]
pub const fn tensor(&self) -> PredictionTensor {
self.tensor
}
#[inline(always)]
pub const fn batch(&self) -> usize {
self.batch
}
#[inline(always)]
pub const fn per_row(&self) -> usize {
self.per_row
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AllocationFailed {
tensor: PredictionTensor,
elements: usize,
}
impl AllocationFailed {
#[inline(always)]
pub const fn new(tensor: PredictionTensor, elements: usize) -> Self {
Self { tensor, elements }
}
#[inline(always)]
pub const fn tensor(&self) -> PredictionTensor {
self.tensor
}
#[inline(always)]
pub const fn elements(&self) -> usize {
self.elements
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ResultAllocationFailed {
faces: usize,
}
impl ResultAllocationFailed {
#[inline(always)]
pub const fn new(faces: usize) -> Self {
Self { faces }
}
#[inline(always)]
pub const fn faces(&self) -> usize {
self.faces
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("failed to load model: {0}")]
Load(#[from] crate::LoadError),
#[error("prediction failed: {0}")]
Prediction(#[from] crate::PredictionError),
#[error("tensor failed: {0}")]
Tensor(#[from] crate::TensorError),
#[error(
"crop dimensions {}×{} are unusable (zero axis, an axis past {}, or w·h·3 overflows)",
.0.width(),
.0.height(),
crate::embeddings::face::MAX_CROP_AXIS
)]
CropDimensions(CropDimensions),
#[error("crop data length mismatch: expected {} bytes (w·h·3), got {}", .0.expected(), .0.got())]
CropDataLength(CropDataLength),
#[error("{} landmark {} has a non-finite coordinate", .0.set(), .0.index())]
NonFiniteLandmark(NonFiniteLandmark),
#[error("the solved similarity transform has a non-finite `{}`", .0.parameter())]
NonFiniteTransform(NonFiniteTransform),
#[error(
"the solved similarity transform has no inverse (scale = {:e}); no template pixel can be \
mapped back into the crop",
.0.scale()
)]
NonInvertibleTransform(NonInvertibleTransform),
#[error(
"the five landmarks have no usable spread (Σ‖pᵢ−p̄‖² = {}); no similarity transform is \
determined",
.0.spread()
)]
DegenerateLandmarks(DegenerateLandmarks),
#[error(
"the {} source coordinate's {} is {:e} (units of 1/1024 px), outside the `int` domain \
`cv2.warpAffine` computes it in; no template pixel can be sampled through this transform",
.0.axis(),
.0.term(),
.0.value()
)]
CoordinateOverflow(CoordinateOverflow),
#[error(transparent)]
ArtifactDigest(#[from] DigestFailure),
#[error(
"the manifest's preprocessing `{}` does not stay in `f32`; values it writes into the input \
tensor would be non-finite, and the space stamped on the embeddings would carry it",
.0.field()
)]
NonFinitePreprocessing(NonFinitePreprocessing),
#[error(
"the manifest declares an embedding width of 0 for the output feature `{}`; a zero-width \
contract is satisfied by a prediction of no elements, and cutting one into rows of zero \
is not defined",
.0.output()
)]
ZeroEmbeddingWidth(ZeroEmbeddingWidth),
#[error("model contract mismatch on `{}`: expected {}, got {}", .0.feature(), .0.expected(), .0.actual())]
ContractMismatch(ContractMismatch),
#[error(
"model declares a required input `{0}` that this door never supplies; it sends the \
manifest's input feature and nothing else, so every prediction would fail"
)]
UnsatisfiableInput(String),
#[error(
"model declares the state buffer `{0}`, and this door predicts through the stateless \
API; a stateful graph needs an `MLState` on every prediction"
)]
UnsatisfiableState(String),
#[error("output shape mismatch: expected {:?}, got {:?}", .0.expected(), .0.got())]
OutputShape(OutputShape),
#[error("output element count mismatch: expected {}, got {}", .0.expected(), .0.got())]
OutputElementCount(OutputElementCount),
#[error("model output row {} contains a non-finite value at component {}", .0.row(), .0.component())]
NonFiniteOutput(NonFiniteOutput),
#[error("model output row {} has zero magnitude and cannot be normalized", .0.row())]
EmbeddingZero(BatchRow),
#[error(
"the graph's batch of {} makes the {} tensor {} · {} elements long, which does not fit \
`usize`; no buffer for it can be described, let alone filled",
.0.batch(), .0.tensor(), .0.batch(), .0.per_row()
)]
ElementCountOverflow(ElementCountOverflow),
#[error(
"could not allocate the {} tensor's {} `f32` elements",
.0.tensor(), .0.elements()
)]
AllocationFailed(AllocationFailed),
#[error(
"could not allocate the result vector's {} embeddings",
.0.faces()
)]
ResultAllocationFailed(ResultAllocationFailed),
#[error(
"these two embeddings come from different spaces (their {} differs); no similarity between \
them is defined",
.0.field()
)]
IncomparableEmbeddings(IncomparableEmbeddings),
}
pub type Result<T> = core::result::Result<T, Error>;