mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 07:38:24 +00:00
S2.44: confidence on the Roto Brush span bar
Frames a tracker followed are coloured by its confidence (green, amber, red; slate where the object was absent) and drawn taller; roto_trust reports them. Not checked visually: no headless way to track without weights in a snapshot. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e0db685bf
commit
1ae5bb9839
3 files changed
+67
-3
No files matched your search
@@ -838,6 +838,25 @@ impl Session {
|
||||
changed
|
||||
}
|
||||
|
||||
/// How far to trust the frames a tracker followed (SAM 2), by layer frame: its confidence and
|
||||
/// whether the object was absent. Frames the classic engine made have no entry.
|
||||
pub fn roto_trust(&self, comp: ItemId, layer: LayerId, effect: Uid) -> BTreeMap<i64, fx::FrameTrust> {
|
||||
let mut out = BTreeMap::new();
|
||||
let Some(c) = self.project.comp(comp) else { return out };
|
||||
let Some(l) = c.layer(layer) else { return out };
|
||||
let Some(g) = l.props.find_group(effect) else { return out };
|
||||
if is_object_matte(g) {
|
||||
return out;
|
||||
}
|
||||
let ch = fx::Chain::new(¶ms_static(g), layer_size(&self.project, c, l), c.frame_rate.as_f64(), 1.0);
|
||||
for f in ch.keys.keys() {
|
||||
if let Some(t) = ch.trust_at(*f) {
|
||||
out.insert(*f, t);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The computed frames of an instance (full resolution), by layer frame.
|
||||
pub fn roto_computed(&self, comp: ItemId, layer: LayerId, effect: Uid) -> BTreeMap<i64, bool> {
|
||||
let mut out = BTreeMap::new();
|
||||
|
||||
@@ -977,7 +977,9 @@ impl effectcraft_segment::ObjectTracker for Recorder {
|
||||
/// corrected frame; the frames before it keep their tracked results.
|
||||
#[test]
|
||||
fn a_correction_re_tracks_only_the_frames_it_changes() {
|
||||
let (mut s, clip, _) = painted(center, "disk-retrack.mov");
|
||||
let (mut s, clip, uid) = painted(center, "disk-retrack.mov");
|
||||
let cid = s.active_comp_id().unwrap();
|
||||
assert!(s.roto_trust(cid, clip, uid).is_empty(), "no tracker data before tracking");
|
||||
let rec = std::sync::Arc::new(Recorder::default());
|
||||
effectcraft_effects::roto::set_model(Some(rec.clone()));
|
||||
let run = |s: &mut Session| s.execute_checked("roto.propagate", json!({"layer": clip.0, "direction": "forward", "wait": true}));
|
||||
@@ -1000,4 +1002,8 @@ fn a_correction_re_tracks_only_the_frames_it_changes() {
|
||||
let st = s.execute("roto.status", json!({"layer": clip.0, "frame": 3})).unwrap();
|
||||
assert_eq!(st["source"], "tracker", "{st}");
|
||||
assert_eq!(st["tracker"]["frames"].as_object().unwrap().len(), 21, "{st}");
|
||||
// The span bar's data: the tracker's confidence for every tracked frame.
|
||||
let trust = s.roto_trust(cid, clip, uid);
|
||||
assert_eq!(trust.len(), 21);
|
||||
assert!(trust.values().all(|t| t.confidence > 0.9 && !t.occluded));
|
||||
}
|
||||
@@ -243,6 +243,20 @@ fn run_stroke(app: &mut EffectcraftApp, layer: u64, kind: &str, points: &[[f64;
|
||||
}
|
||||
}
|
||||
|
||||
/// The span bar's colour for a frame a tracker followed: green when it is sure, amber when it is
|
||||
/// doubtful, red when it is not, slate when the object was absent.
|
||||
pub(crate) fn trust_color(confidence: f32, occluded: bool) -> Color32 {
|
||||
if occluded {
|
||||
Color32::from_rgb(110, 118, 150)
|
||||
} else if confidence >= 0.85 {
|
||||
Color32::from_rgb(70, 200, 90)
|
||||
} else if confidence >= 0.5 {
|
||||
Color32::from_rgb(230, 180, 50)
|
||||
} else {
|
||||
Color32::from_rgb(220, 70, 60)
|
||||
}
|
||||
}
|
||||
|
||||
/// The segmentation span bar (above the panel's bottom bar): the layer's frames, the span, the
|
||||
/// base frame (yellow), computed frames (green) and stroke frames. Click to go to a frame; drag
|
||||
/// the span's ends to change it.
|
||||
@@ -264,10 +278,17 @@ pub fn span_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter,
|
||||
let sp = Rect::from_min_max(pos2(x_of(d.span[0] as f64), track.min.y), pos2(x_of(d.span[1] as f64 + 1.0), track.max.y));
|
||||
let frozen = fx::is_frozen(¶ms);
|
||||
p.rect_filled(sp, 2.0, if frozen { Color32::from_rgb(60, 110, 190) } else { Color32::from_rgb(90, 90, 96) });
|
||||
// Computed frames: green, or (frames a tracker followed) coloured by its confidence, with
|
||||
// frames where the object was absent in slate.
|
||||
let trust = app.session.roto_trust(cid, layer.id, uid);
|
||||
for (f, done) in app.session.roto_computed(cid, layer.id, uid) {
|
||||
if done {
|
||||
let cr = Rect::from_min_max(pos2(x_of(f as f64), track.max.y - 4.0), pos2(x_of(f as f64 + 1.0), track.max.y));
|
||||
p.rect_filled(cr, 0.0, Color32::from_rgb(70, 200, 90));
|
||||
let (color, h) = match trust.get(&f) {
|
||||
Some(t) => (trust_color(t.confidence, t.occluded), 6.0),
|
||||
None => (Color32::from_rgb(70, 200, 90), 4.0),
|
||||
};
|
||||
let cr = Rect::from_min_max(pos2(x_of(f as f64), track.max.y - h), pos2(x_of(f as f64 + 1.0), track.max.y));
|
||||
p.rect_filled(cr, 0.0, color);
|
||||
}
|
||||
}
|
||||
for f in d.stroke_frames() {
|
||||
@@ -363,3 +384,21 @@ pub fn bar_buttons(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painte
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn confidence_colours_run_from_green_to_red_and_absent_is_slate() {
|
||||
let (g, a, r, s) = (trust_color(0.99, false), trust_color(0.6, false), trust_color(0.1, false), trust_color(0.99, true));
|
||||
assert_eq!(g, Color32::from_rgb(70, 200, 90));
|
||||
assert_eq!(a, Color32::from_rgb(230, 180, 50));
|
||||
assert_eq!(r, Color32::from_rgb(220, 70, 60));
|
||||
assert_eq!(s, Color32::from_rgb(110, 118, 150));
|
||||
// the boundaries
|
||||
assert_eq!(trust_color(0.85, false), g);
|
||||
assert_eq!(trust_color(0.5, false), a);
|
||||
assert_eq!(trust_color(f32::NAN, false), r);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user