Run the formatter over the face branch before it reaches CI
🐳 Android image / Build and push (push) Successful in 2s
Build and test / android-image (push) Successful in 2s
Build and test / Desktop (Linux) (push) Successful in 1h21m32s
Build and test / Layer separation (push) Successful in 37s
Traceability / Requirement traces (push) Successful in 25s
Build and test / Android (aarch64) (push) Failing after 33m58s

The merge of the SCRFD/MobileFaceNet work brought 69 rustfmt diffs across
dr-catalog, dr-face and dr-ui with it, so `cargo fmt --all -- --check` fails
on master and the Desktop job stops at its Format step — before clippy, the
tests or the release build have run at all. That makes the whole desktop
half of CI blind: a real compile error behind this would look exactly the
same from the outside. There was nothing behind it, as it turns out — with
the formatting fixed, clippy, the test suite and the release build all pass.

Every .rs hunk is `cargo fmt --all` on the pinned 1.92.0 toolchain, not a
hand edit, but it is worth being precise about what that moved, because it
is more than whitespace. Besides reflowing signatures and call chains,
rustfmt reordered the `pub mod` and `pub use` items in dr-face/src/lib.rs so
the `#[cfg(feature = "inference")]` entries sort in place, added the trailing
semicolon inside `let ... else { return }` bodies in identity_ui.rs, wrapped
a bare closure body in braces in cluster.rs, adjusted trailing commas, and
dropped a stray blank line at the end of identity_ui.rs. All of it is
semantically inert; none of it changes behaviour.

docs/traceability.md rides along because it has to. The matrix records each
TRACES tag by line number, and reflowing develop.rs, lib.rs, faces.rs,
identity.rs and identity_ui.rs moved them — FR-CAT-8, FR-CAT-9, FR-CULL-10,
FR-DEV-3, FR-DEV-3a and FR-DEV-3c all shift by a line or two. The matrix was
verified up to date on d777f7f before this commit, so this is drift these
formatting changes introduced, not pre-existing staleness being swept up.
Leaving it for a follow-up commit would hand traceability-check.yml a
failure caused entirely by a whitespace change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 13:12:49 +02:00
co-authored by Claude Opus 5
parent d777f7f44d
commit b846b312b8
18 changed files with 285 additions and 207 deletions
+5 -5
View File
@@ -106,7 +106,10 @@ impl Similarity {
let det = self.a * self.a + self.b * self.b;
let du = u - self.tx;
let dv = v - self.ty;
((self.a * du + self.b * dv) / det, (-self.b * du + self.a * dv) / det)
(
(self.a * du + self.b * dv) / det,
(-self.b * du + self.a * dv) / det,
)
}
}
@@ -253,10 +256,7 @@ mod tests {
let (s, c) = (rot.sin(), rot.cos());
let mut out = [(0.0, 0.0); 5];
for (i, &(x, y)) in ARCFACE_TEMPLATE.iter().enumerate() {
out[i] = (
scale * (c * x - s * y) + dx,
scale * (s * x + c * y) + dy,
);
out[i] = (scale * (c * x - s * y) + dx, scale * (s * x + c * y) + dy);
}
out
}
+5 -5
View File
@@ -108,10 +108,7 @@ impl Calibration {
/// What turns "merge above 0.9" into one comparison against a stored
/// similarity, rather than a sigmoid evaluated per candidate edge.
pub fn boundary_at(&self, p: f32, min_crop_px: f32, log_prior_odds: f32) -> f32 {
((p / (1.0 - p)).ln()
- self.b
- self.w_size * min_crop_px.max(1.0).log2()
- log_prior_odds)
((p / (1.0 - p)).ln() - self.b - self.w_size * min_crop_px.max(1.0).log2() - log_prior_odds)
/ self.a
}
}
@@ -416,7 +413,10 @@ mod tests {
b.count
);
}
assert!(checked >= 2, "only {checked} bands had enough pairs to check");
assert!(
checked >= 2,
"only {checked} bands had enough pairs to check"
);
}
#[test]
+7 -6
View File
@@ -75,11 +75,7 @@ pub struct Cluster {
/// probability between two groups. Deterministic: the same input yields the
/// same clusters, because the merge order is by score with the index pair as
/// the tiebreak.
pub fn cluster(
faces: &[Candidate],
cal: &Calibration,
min_probability: f32,
) -> Vec<Cluster> {
pub fn cluster(faces: &[Candidate], cal: &Calibration, min_probability: f32) -> Vec<Cluster> {
if faces.is_empty() {
return Vec::new();
}
@@ -150,7 +146,12 @@ pub fn cluster(
})
.collect();
// Largest first: the People view shows the best-evidenced groups at the top.
out.sort_by(|x, y| y.members.len().cmp(&x.members.len()).then(x.members[0].cmp(&y.members[0])));
out.sort_by(|x, y| {
y.members
.len()
.cmp(&x.members.len())
.then(x.members[0].cmp(&y.members[0]))
});
out
}
+1 -2
View File
@@ -123,8 +123,7 @@ impl Detector {
for s in 0..fmc {
let idx = group * fmc + s;
let out = &session.outputs()[idx];
let last: Option<i64> =
out.dtype().tensor_shape().and_then(|d| d.last().copied());
let last: Option<i64> = out.dtype().tensor_shape().and_then(|d| d.last().copied());
if last != Some(expected_last) {
return Err(FaceError::WrongModel {
expected: "InsightFace SCRFD",
+6 -5
View File
@@ -23,10 +23,7 @@ pub struct Embedder {
}
impl Embedder {
pub fn from_path(
path: impl AsRef<std::path::Path>,
model: ModelId,
) -> Result<Self, FaceError> {
pub fn from_path(path: impl AsRef<std::path::Path>, model: ModelId) -> Result<Self, FaceError> {
let bytes = std::fs::read(path).map_err(FaceError::ModelRead)?;
Self::from_bytes(&bytes, model)
}
@@ -50,7 +47,11 @@ impl Embedder {
if last != Some(EMBEDDING_DIM as i64) {
return Err(FaceError::WrongModel {
expected: "ArcFace",
detail: format!("output '{}' is {:?}-wide, expected {EMBEDDING_DIM}", out.name(), last),
detail: format!(
"output '{}' is {:?}-wide, expected {EMBEDDING_DIM}",
out.name(),
last
),
});
}
+12 -2
View File
@@ -116,7 +116,13 @@ fn f32_to_f16_bits(x: f32) -> u16 {
// Overflow, inf, or NaN. Embeddings are unit-norm so this is the
// broken-model path; infinity is the honest answer, not a clamp that
// hides it.
return sign | 0x7c00 | if mant != 0 && exp == 0x1f + 112 { 0x200 } else { 0 };
return sign
| 0x7c00
| if mant != 0 && exp == 0x1f + 112 {
0x200
} else {
0
};
}
if exp <= 0 {
// Subnormal or underflow. A component of a unit 512-vector is ~0.04,
@@ -195,7 +201,11 @@ mod tests {
let a = unit(1);
let mut b = unit(1);
b.model = ModelId::new("other");
assert_eq!(a.cosine(&b), None, "a cross-model cosine must not be a number");
assert_eq!(
a.cosine(&b),
None,
"a cross-model cosine must not be a number"
);
}
/// The claim docs/faces.md §6 makes about the storage format: the f16
+4 -4
View File
@@ -33,22 +33,22 @@
pub mod align;
pub mod calibrate;
pub mod cluster;
pub mod embedding;
pub mod naming;
#[cfg(feature = "inference")]
pub mod detect;
#[cfg(feature = "inference")]
pub mod embed;
pub mod embedding;
pub mod naming;
pub use align::{warp, Aligned112, Similarity, ALIGNED_EDGE, ARCFACE_TEMPLATE};
pub use calibrate::{Calibration, Pairs, ReliabilityBand};
pub use cluster::{cluster, split, Candidate, Cluster, DEFAULT_MERGE_PROBABILITY};
pub use embedding::{Embedding, ModelId, EMBEDDING_DIM};
pub use naming::{name_for_instance, name_instances, NamedFace};
#[cfg(feature = "inference")]
pub use detect::{DetectOptions, Detection, Detector};
#[cfg(feature = "inference")]
pub use embed::Embedder;
pub use embedding::{Embedding, ModelId, EMBEDDING_DIM};
pub use naming::{name_for_instance, name_instances, NamedFace};
/// What can go wrong between an image and a face.
#[derive(Debug, thiserror::Error)]
+24 -7
View File
@@ -152,14 +152,20 @@ mod tests {
fn a_tiny_face_in_a_large_person_still_matches() {
let tall = (0.0, 0.0, 500.0, 2000.0);
let small = (240.0, 40.0, 280.0, 100.0);
assert_eq!(name_for_instance(tall, &[named(small, "Anna")]), Some("Anna"));
assert_eq!(
name_for_instance(tall, &[named(small, "Anna")]),
Some("Anna")
);
}
#[test]
fn a_face_mostly_outside_the_person_is_not_theirs() {
// Overlapping the person's edge, but only just.
let straddling = (60.0, 80.0, 140.0, 220.0);
assert_eq!(name_for_instance(PERSON, &[named(straddling, "Anna")]), None);
assert_eq!(
name_for_instance(PERSON, &[named(straddling, "Anna")]),
None
);
}
/// A tight portrait, where the segmenter's "person" is head and shoulders
@@ -175,7 +181,10 @@ mod tests {
fn a_tight_portrait_is_named_rather_than_treated_as_suspicious() {
let head = (100.0, 50.0, 400.0, 400.0);
let face = (110.0, 60.0, 390.0, 390.0);
assert_eq!(name_for_instance(head, &[named(face, "Anna")]), Some("Anna"));
assert_eq!(
name_for_instance(head, &[named(face, "Anna")]),
Some("Anna")
);
}
/// A face box *larger* than the instance is a genuine disagreement, and
@@ -185,7 +194,10 @@ mod tests {
fn a_face_larger_than_the_instance_does_not_name_it() {
let small_instance = (200.0, 200.0, 260.0, 260.0);
let huge_face = (100.0, 100.0, 500.0, 500.0);
assert_eq!(name_for_instance(small_instance, &[named(huge_face, "Anna")]), None);
assert_eq!(
name_for_instance(small_instance, &[named(huge_face, "Anna")]),
None
);
}
/// Two people merged into one blob: naming either would be a coin toss,
@@ -226,8 +238,14 @@ mod tests {
#[test]
fn degenerate_boxes_name_nothing_rather_than_panicking() {
assert_eq!(name_for_instance((0.0, 0.0, 0.0, 0.0), &[named(FACE, "A")]), None);
assert_eq!(name_for_instance(PERSON, &[named((5.0, 5.0, 5.0, 5.0), "A")]), None);
assert_eq!(
name_for_instance((0.0, 0.0, 0.0, 0.0), &[named(FACE, "A")]),
None
);
assert_eq!(
name_for_instance(PERSON, &[named((5.0, 5.0, 5.0, 5.0), "A")]),
None
);
assert_eq!(name_for_instance(PERSON, &[]), None);
}
@@ -282,4 +300,3 @@ mod tests {
assert_eq!(instances[0].class, "person");
}
}