diff --git a/docs/animation-model.md b/docs/animation-model.md index 9b16489..647f798 100644 --- a/docs/animation-model.md +++ b/docs/animation-model.md @@ -434,7 +434,7 @@ selected head placement, so it aligns with the vectors drawn over it. :mouth :mouth-in :teeth :lid-r :lid-l :brow-r :brow-l … ``` -Changing anchor keys edits `:head` and never touches `:place`, so it cannot move +Changing anchor keys edits `:head` and never touches `:face`, so it cannot move something that was placed by hand. A group node is free, and keeping the authored and the measured transform apart is the whole reason the transform is decomposed in the first place. @@ -679,7 +679,7 @@ Proof that it covers what exists, not just what is wanted: | square pupil | node `:pupil-r`, `:kind :rect`, parent `:iris-r`, stencil `:iris-r` | | brow ring + quantised raise | node `:brow-r`, `[:geom :pts]` dense (the traced ring with height removed), `[:xform :pos]` dense (the quantised raise). **The decomposition design.md insists on is two channels.** | | head plate, kept frames | node `:head`, `:symbol` per instance, keys on `[:symbol]` at kept frames | -| `makeXform` face-oval crop | **gone.** Placement is `[:xform :*]` on the face's own `:place`; the stage clips | +| `makeXform` face-oval crop | **gone.** Placement is `[:xform :*]` on `:face`; the stage clips | | `stabilize` transforms | dense `[:xform :*]` on `:head`, read through its optional `:anchors` map | | registered underlay | not data — a UI layer riding `(world-of resolver :head)` | | painted background cel | node per layer, `[:geom :pts]` **framed**, `[:style :color]` framed | diff --git a/docs/port-plan.md b/docs/port-plan.md index 6556278..f9a67e3 100644 --- a/docs/port-plan.md +++ b/docs/port-plan.md @@ -333,12 +333,11 @@ per-frame header. numbers: it centres on the face oval's bbox and zooms until the face is 80% of the raster height, so every vertex carries a cropping decision made once from one frame's landmarks. Dropping it is a deletion. Placement becomes `[:xform :*]` on -an authored `:place` node inside the face, the stage clips whatever hangs off, -and project +an authored `:face` node, the stage clips whatever hangs off, and project dimensions stop being tied to the footage. See "What space geometry is in" in `docs/animation-model.md`. -The anchor transform freezes onto `:head`, one level under `:place`, and the +The anchor transform freezes onto `:head`, one level under `:face`, and the normalise on/off/per-plate toggle is which of the three channel shapes that node carries. Always measure and always store factored, whatever the toggle says: smoothing and velocity-minimum key selection both require the split to exist in diff --git a/docs/timing-model.md b/docs/timing-model.md index 029132d..b8fe700 100644 --- a/docs/timing-model.md +++ b/docs/timing-model.md @@ -39,7 +39,7 @@ measurements without losing the anchor choices. A source image used for tracing should be registered with that image's measured stabilizing transform, then the selected head transform, then the authored -`:place` placement the face carries. This makes the photo and head-local vectors share the same +`:face` placement. This makes the photo and head-local vectors share the same orientation and position. Tracing-photo selection is a separate editor address; it does not choose the head anchor. diff --git a/frontend/test/arthur/domain/trace_test.cljs b/frontend/test/arthur/domain/trace_test.cljs index e0ccbc9..00cf994 100644 --- a/frontend/test/arthur/domain/trace_test.cljs +++ b/frontend/test/arthur/domain/trace_test.cljs @@ -4,7 +4,6 @@ [arthur.domain.clip :as clip] [arthur.domain.leaf :as leaf] [arthur.domain.nest :as nest] - [arthur.domain.node :as node] [arthur.domain.palette :as pal] [arthur.domain.symbol :as symbol] [arthur.domain.trace :as trace] @@ -59,30 +58,15 @@ (vec (array-seq (trace/photo-matrix (symbol/world-of r :head) h @store (trace/photo-frame (trace/of h) f) 1000))))) -(defn- filmed - "Where a photo sitting exactly where it was filmed must land: the face's OWN - placement, over image height, and nothing else. - - `:place` carries the source-to-stage mapping now, above `:head` — so a face - opened in its own tab draws at the size it is on the stage, and its photo has - to come with it. That is the whole reason the mapping belongs to the face: the - head still cancels out, which is what these assertions are about, but it - cancels against a placement rather than against nothing." - [c f] - (let [r (symbol/resolver (clip/symbol c :face-1) @store pal/index-of nil)] - (r f) - (vec (array-seq (node/mul! (node/mat) (symbol/world-of r :place) - (js/Float64Array. #js [0.001 0 0 0.001 0 0])))))) - (deftest the-photo-registers-to-the-face-it-was-filmed-with - (let [a (traced {:frames [] :origin :continuous}) - b (traced {:frames [12] :origin :keys}) - d (traced {:frames [12] :origin :continuous})] - (is (near? (filmed a 30) (photo-at a 30)) + ;; Face-1 is its own symbol and its head is its root, so a photo sitting where + ;; it was filmed is image pixels over image height and nothing else. + (let [filmed [0.001 0 0 0.001 0 0]] + (is (near? filmed (photo-at (traced {:frames [] :origin :continuous}) 30)) "a head showing the photo's own frame cancels out") - (is (near? (filmed b 30) (photo-at b 30)) + (is (near? filmed (photo-at (traced {:frames [12] :origin :keys}) 30)) "held at the trace frame, face and photo both stand still") - (is (not (near? (filmed d 30) (photo-at d 30))) + (is (not (near? filmed (photo-at (traced {:frames [12] :origin :continuous}) 30))) "a continuous head carries the held photo along with it"))) (defn- wrapped diff --git a/frontend/test/arthur/flow/freeze_test.cljs b/frontend/test/arthur/flow/freeze_test.cljs index 2c62d8c..1fe8139 100644 --- a/frontend/test/arthur/flow/freeze_test.cljs +++ b/frontend/test/arthur/flow/freeze_test.cljs @@ -86,19 +86,13 @@ (is (empty? (clip/problems c)) (str spec ": " (pr-str (clip/problems c))))))) (deftest the-tree-is-the-one-the-model-specifies - ;; :main HOLDS INSTANCES AND NOTHING ELSE. The source-to-stage placement used - ;; to be a `:face` group here, above them all; it is each face's own `:place` - ;; now, so the take is a container that knows nothing and a face is the right - ;; size wherever it is put. Same transform, same subtree, one level lower. - (is (= [:face-1 :root] (symbol/lineage (:nodes (clip/symbol @clip* :main)) :face-1))) - (is (nil? (get-in @clip* [:symbols :main :nodes :face]))) + (is (= [:face :root] (symbol/lineage (:nodes (clip/symbol @clip* :main)) :face))) (is (= #{:face-1} (node/sources (get-in @clip* [:symbols :main :nodes :face-1])))) - (is (= [:place] (symbol/lineage (:nodes @sym*) :place))) - (is (= [:head :place] (symbol/lineage (:nodes @sym*) :head))) - (is (= [:mouth :head :place] (symbol/lineage (:nodes @sym*) :mouth))) - (is (= [:mouth-in :mouth :head :place] (symbol/lineage (:nodes @sym*) :mouth-in))) + (is (= [:head] (symbol/lineage (:nodes @sym*) :head))) + (is (= [:mouth :head] (symbol/lineage (:nodes @sym*) :mouth))) + (is (= [:mouth-in :mouth :head] (symbol/lineage (:nodes @sym*) :mouth-in))) (is (= {:mode :map :expose 2} (:time (node :root)))) - (is (every? #(nil? (:time (node %))) [:place :head :mouth :mouth-in]))) + (is (every? #(nil? (:time (node %))) [:face :head :mouth :mouth-in]))) (deftest geometry-is-flat-and-dense-and-the-interior-shares-the-outline-s-block (doseq [id [:mouth :mouth-in]] @@ -127,11 +121,11 @@ (abs (- a b))))] (is (<= gap (/ q 2)) (str "worst quantisation error " gap " against a quantum of " q)) - ;; And in stage pixels, which is the unit anyone can judge. `:place`'s scale is + ;; And in stage pixels, which is the unit anyone can judge. `:face`'s scale is ;; stage px per image height, so the whole quantum is q·k — a twentieth of a ;; pixel at this placement, two orders below anything the rasteriser can ;; express, which is the argument for Int16 geometry stated as a measurement. - (let [k (first (:value (chan :place [:xform :scale])))] + (let [k (first (:value (chan :face [:xform :scale])))] (is (< (* q k) 0.1) (str "the quantum is " (* q k) " stage pixels at " k " px per image height")) (is (< (* gap k) (* q k)))))) @@ -196,15 +190,15 @@ (deftest head-local-geometry-composed-through-head-and-face-lands-on-the-stage ;; The end-to-end claim of the split, asserted against the OPS the resolver ;; actually emits rather than against an intermediate: stored head-local, put - ;; back through `:head`, placed by `:place`, the mouth is where the composition + ;; back through `:head`, placed by `:face`, the mouth is where the composition ;; of the two says it is. A test that recomputed the chain would only be ;; checking arithmetic against itself; this checks `node/local!`, `node/world!` ;; and `emit` as well. (let [c (freeze/head-mode {} @frozen) res (clip/resolver c :main @store pal/index-of nil) - k (first (:value (chan :place [:xform :scale]))) - anc (:value (chan :place [:xform :anchor])) - pos (:value (chan :place [:xform :pos])) + k (first (:value (chan :face [:xform :scale]))) + anc (:value (chan :face [:xform :anchor])) + pos (:value (chan :face [:xform :pos])) tfs (:transforms @take/measured)] (doseq [f (range 0 take/frames 13)] (let [ops (res f) @@ -223,7 +217,7 @@ ;; the fit removed the head's motion, so putting it back is the fit's ;; inverse. Node :head carries exactly this. im (geom/apply-sim (freeze/invert (nth tfs ef)) g) - ;; And where :place puts it: scaled about the anchor, then translated, + ;; And where :face puts it: scaled about the anchor, then translated, ;; which is p ↦ k(p - anchor) + anchor + pos. wx (+ (* k (- (:x im) (nth anc 0))) (nth anc 0) (nth pos 0)) wy (+ (* k (- (:y im) (nth anc 1))) (nth anc 1) (nth pos 1))] @@ -278,8 +272,8 @@ b (freeze/head-mode {:trace {:origin :start}} @frozen) c (freeze/head-mode {:trace {:origin :keys :frames [0 40]}} @frozen)] (doseq [x [b c]] - (is (= (get (nodes a) :place) (get (nodes x) :place)) - ":place moved") + (is (= (get (nodes a) :face) (get (nodes x) :face)) + ":face moved") (is (= (dissoc (nodes a) :head) (dissoc (nodes x) :head)) "a node other than :head changed") ;; The measurement does not go away when the head is locked: always measure, @@ -310,7 +304,7 @@ ;; transform on a node, which a hand can revise, and it claims no generator that ;; would offer to overwrite it. (doseq [path [[:xform :pos] [:xform :rot] [:xform :scale] [:xform :anchor]]] - (let [c (get (node/channels (node :place)) path)] + (let [c (get (node/channels (node :face)) path)] (is (= :framed (ch/describe c)) (str path " is not framed")) (is (nil? (:generated c)) (str path " claims provenance"))))) @@ -330,8 +324,8 @@ (let [c @clip*] (doseq [sid [:main :face-1] id (keys (get-in c [:symbols sid :nodes])) - ;; `:place` authors its own in `face-placement`, upstream of this. - :when (not= [:face-1 :place] [sid id])] + ;; `:face` authors its own in `face-placement`, upstream of this. + :when (not= [:main :face] [sid id])] (let [n (get-in c [:symbols sid :nodes id]) frames (range (get-in c [:symbols sid :frames])) anchor (:value (get-in n [:channels [:xform :anchor]])) @@ -370,11 +364,11 @@ ;; differently, it has been applied to a node whose transform is not the ;; identity, which is the one way it could go wrong. (let [c @clip* - ;; Every node the freeze makes EXCEPT `:place`, whose anchor + ;; Every node the freeze makes EXCEPT `:face`, whose anchor ;; `face-placement` authors — which is the set `pivoted` writes. every (for [sid [:main :face-1] id (keys (get-in c [:symbols sid :nodes])) - :when (and (not= [:face-1 :place] [sid id]) + :when (and (not= [:main :face] [sid id]) (seq (get-in c [:symbols sid :nodes id :channels])))] [sid id]) anchors #(into {} (for [[sid id] every] @@ -391,13 +385,13 @@ (is (= (render bare f) (render again f)) (str "frame " f " draws differently once every part has a pivot"))))) -(deftest the-place-puts-the-head-s-centre-where-it-says-it-does +(deftest the-face-puts-the-head-s-centre-where-it-says-it-does ;; anchor + pos is where the anchor lands in the parent, which is what makes ;; `:anchor` the registration point: scale and rotation happen about the head's ;; centre rather than about the corner of the footage, where MediaPipe's ;; normalised space has its origin. - (let [anc (:value (chan :place [:xform :anchor])) - pos (:value (chan :place [:xform :pos])) + (let [anc (:value (chan :face [:xform :anchor])) + pos (:value (chan :face [:xform :pos])) c (geom/centroid (:ref @take/measured))] (is (< (abs (- (nth anc 0) (:x c))) 1e-12)) (is (< (abs (- (nth anc 1) (:y c))) 1e-12)) @@ -424,8 +418,8 @@ (vec (array-seq (:data (get (:store big) (key-of (:clip big))))))) "the geometry is the same numbers at either stage size") (is (not= (:value (get-in (nodes (:clip big)) - [:place :channels [:xform :scale]])) - (:value (chan :place [:xform :scale])))))) + [:face :channels [:xform :scale]])) + (:value (chan :face [:xform :scale])))))) ;; --------------------------------------------------------------------------- ;; the aperture, as [:vis] diff --git a/frontend/test/arthur/flow/multi_face_test.cljs b/frontend/test/arthur/flow/multi_face_test.cljs index 98af740..7099adb 100644 --- a/frontend/test/arthur/flow/multi_face_test.cljs +++ b/frontend/test/arthur/flow/multi_face_test.cljs @@ -177,7 +177,7 @@ flat (assoc-in clip [:symbols :main :nodes] (merge (dissoc (:nodes (clip/symbol clip :main)) :face-1) (assoc-in (get-in clip [:symbols :face-1 :nodes]) - [:head :parent] :place))) + [:head :parent] :face))) nested (clip/resolver clip :main store pal/index-of nil) reference (clip/resolver flat :main store pal/index-of nil)] (doseq [f [0 1 7 20 39]] diff --git a/frontend/test/arthur/flow/regenerate_test.cljs b/frontend/test/arthur/flow/regenerate_test.cljs index 232e7a6..e0b7a0b 100644 --- a/frontend/test/arthur/flow/regenerate_test.cljs +++ b/frontend/test/arthur/flow/regenerate_test.cljs @@ -46,8 +46,8 @@ (channel after :iris-l [:xform :pos]))) (is (= (channel before :mouth [:geom :pts]) (channel after :mouth [:geom :pts]))) - (is (= (get-in before [:clip :symbols :face-1 :nodes :place]) - (get-in after [:clip :symbols :face-1 :nodes :place]))) + (is (= (get-in before [:clip :symbols :main :nodes :face]) + (get-in after [:clip :symbols :main :nodes :face]))) (is (= (channel (full-at {:gaze-gain 2}) :iris-r [:xform :pos]) (channel after :iris-r [:xform :pos]))))) @@ -85,8 +85,8 @@ (get-in after [:clip :symbols :face-1 :nodes :head :measured]))) (is (= (get-in full [:clip :symbols :face-1 :nodes :head :measured]) (get-in after [:clip :symbols :face-1 :nodes :head :measured]))) - (is (= (get-in before [:clip :symbols :face-1 :nodes :place]) - (get-in after [:clip :symbols :face-1 :nodes :place]))))) + (is (= (get-in before [:clip :symbols :main :nodes :face]) + (get-in after [:clip :symbols :main :nodes :face]))))) (deftest contour-edit-does-not-rebuild-head-or-teeth (let [before @initial