Mercurial > cortex
view org/worm_learn.clj @ 449:09b7c8dd4365
first chapter done, half of last chapter done.
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Wed, 26 Mar 2014 02:42:01 -0400 |
parents | 47cfbe84f00e |
children | 0a4362d1f138 |
line wrap: on
line source
1 (ns org.aurellem.worm-learn2 "General worm creation framework."3 {:author "Robert McIntyre"}4 (:use (cortex world util import body sense5 hearing touch vision proprioception movement6 test))7 (:import (com.jme3.math ColorRGBA Vector3f))8 (:import java.io.File)9 (:import com.jme3.audio.AudioNode)10 (:import com.aurellem.capture.RatchetTimer)11 (:import (com.aurellem.capture Capture IsoTimer))12 (:import (com.jme3.math Vector3f ColorRGBA)))14 (import org.apache.commons.math3.transform.TransformType)15 (import org.apache.commons.math3.transform.FastFourierTransformer)16 (import org.apache.commons.math3.transform.DftNormalization)18 (use 'clojure.pprint)19 (use 'clojure.set)20 (dorun (cortex.import/mega-import-jme3))21 (rlm.rlm-commands/help)23 (load-bullet)25 (def hand "Models/test-creature/hand.blend")27 (defn worm-model []28 (load-blender-model "Models/worm/worm.blend"))30 (defn worm []31 (let [model (load-blender-model "Models/worm/worm.blend")]32 {:body (doto model (body!))33 :touch (touch! model)34 :proprioception (proprioception! model)35 :muscles (movement! model)}))37 (def output-base (File. "/home/r/proj/cortex/render/worm-learn/curl"))40 (defn motor-control-program41 "Create a function which will execute the motor script"42 [muscle-labels43 script]44 (let [current-frame (atom -1)45 keyed-script (group-by first script)46 current-forces (atom {}) ]47 (fn [effectors]48 (let [indexed-effectors (vec effectors)]49 (dorun50 (for [[_ part force] (keyed-script (swap! current-frame inc))]51 (swap! current-forces (fn [m] (assoc m part force)))))52 (doall (map (fn [effector power]53 (effector (int power)))54 effectors55 (map #(@current-forces % 0) muscle-labels)))))))57 (defn worm-direct-control58 "Create keybindings and a muscle control program that will enable59 the user to control the worm via the keyboard."60 [muscle-labels activation-strength]61 (let [strengths (mapv (fn [_] (atom 0)) muscle-labels)62 activator63 (fn [n]64 (fn [world pressed?]65 (let [strength (if pressed? activation-strength 0)]66 (swap! (nth strengths n) (constantly strength)))))67 activators68 (map activator (range (count muscle-labels)))69 worm-keys70 ["key-f" "key-r"71 "key-g" "key-t"72 "key-h" "key-y"73 "key-j" "key-u"74 "key-k" "key-i"75 "key-l" "key-o"]]76 {:motor-control77 (fn [effectors]78 (doall79 (map (fn [strength effector]80 (effector (deref strength)))81 strengths effectors)))82 :keybindings83 ;; assume muscles are listed in pairs and map them to keys.84 (zipmap worm-keys activators)}))86 ;; These are scripts that direct the worm to move in two radically87 ;; different patterns -- a sinusoidal wiggling motion, and a curling88 ;; motions that causes the worm to form a circle.90 (def curl-script91 [[150 :d-flex 40]92 [250 :d-flex 0]])94 (def period 18)96 (def worm-muscle-labels97 [:base-ex :base-flex98 :a-ex :a-flex99 :b-ex :b-flex100 :c-ex :c-flex101 :d-ex :d-flex])103 (defn gen-wiggle [[flexor extensor :as muscle-pair] time-base]104 (let [period period105 power 45]106 [[time-base flexor power]107 [(+ time-base period) flexor 0]108 [(+ time-base period 1) extensor power]109 [(+ time-base (+ (* 2 period) 2)) extensor 0]]))111 (def wiggle-script112 (mapcat gen-wiggle (repeat 4000 [:a-ex :a-flex])113 (range 100 1000000 (+ 3 (* period 2)))))116 (defn shift-script [shift script]117 (map (fn [[time label power]] [(+ time shift) label power])118 script))120 (def do-all-the-things121 (concat122 curl-script123 [[300 :d-ex 40]124 [320 :d-ex 0]]125 (shift-script 280 (take 16 wiggle-script))))127 ;; Normally, we'd use unsupervised/supervised machine learning to pick128 ;; out the defining features of the different actions available to the129 ;; worm. For this project, I am going to explicitely define functions130 ;; that recognize curling and wiggling respectively. These functions131 ;; are defined using all the information available from an embodied132 ;; simulation of the action. Note how much easier they are to define133 ;; than if I only had vision to work with. Things like scale/position134 ;; invariance are complete non-issues here. This is the advantage of135 ;; body-centered action recognition and what I hope to show with this136 ;; thesis.139 ;; curled? relies on proprioception, resting? relies on touch,140 ;; wiggling? relies on a fourier analysis of muscle contraction, and141 ;; grand-circle? relies on touch and reuses curled? as a gaurd.143 (defn curled?144 "Is the worm curled up?"145 [experiences]146 (every?147 (fn [[_ _ bend]]148 (> (Math/sin bend) 0.64))149 (:proprioception (peek experiences))))151 (defn rect-region [[x0 y0] [x1 y1]]152 (vec153 (for [x (range x0 (inc x1))154 y (range y0 (inc y1))]155 [x y])))157 (def worm-segment-bottom (rect-region [8 15] [14 22]))159 (defn contact160 "Determine how much contact a particular worm segment has with161 other objects. Returns a value between 0 and 1, where 1 is full162 contact and 0 is no contact."163 [touch-region [coords contact :as touch]]164 (-> (zipmap coords contact)165 (select-keys touch-region)166 (vals)167 (#(map first %))168 (average)169 (* 10)170 (- 1)171 (Math/abs)))173 (defn resting?174 "Is the worm resting on the ground?"175 [experiences]176 (every?177 (fn [touch-data]178 (< 0.9 (contact worm-segment-bottom touch-data)))179 (:touch (peek experiences))))181 (defn vector:last-n [v n]182 (let [c (count v)]183 (if (< c n) v184 (subvec v (- c n) c))))186 (defn fft [nums]187 (map188 #(.getReal %)189 (.transform190 (FastFourierTransformer. DftNormalization/STANDARD)191 (double-array nums) TransformType/FORWARD)))193 (def indexed (partial map-indexed vector))195 (defn max-indexed [s]196 (first (sort-by (comp - second) (indexed s))))198 (defn wiggling?199 "Is the worm wiggling?"200 [experiences]201 (let [analysis-interval 0x40]202 (when (> (count experiences) analysis-interval)203 (let [a-flex 3204 a-ex 2205 muscle-activity206 (map :muscle (vector:last-n experiences analysis-interval))207 base-activity208 (map #(- (% a-flex) (% a-ex)) muscle-activity)]209 (= 2210 (first211 (max-indexed212 (map #(Math/abs %)213 (take 20 (fft base-activity))))))))))215 (def worm-segment-bottom-tip (rect-region [15 15] [22 22]))217 (def worm-segment-top-tip (rect-region [0 15] [7 22]))219 (defn grand-circle?220 "Does the worm form a majestic circle (one end touching the other)?"221 [experiences]222 (and (curled? experiences)223 (let [worm-touch (:touch (peek experiences))224 tail-touch (worm-touch 0)225 head-touch (worm-touch 4)]226 (and (< 0.55 (contact worm-segment-bottom-tip tail-touch))227 (< 0.55 (contact worm-segment-top-tip head-touch))))))230 (declare phi-space phi-scan debug-experience)234 (def standard-world-view235 [(Vector3f. 4.207176, -3.7366982, 3.0816958)236 (Quaternion. 0.11118768, 0.87678415, 0.24434438, -0.3989771)])238 (def worm-side-view239 [(Vector3f. 4.207176, -3.7366982, 3.0816958)240 (Quaternion. -0.11555642, 0.88188726, -0.2854942, -0.3569518)])242 (def degenerate-worm-view243 [(Vector3f. -0.0708936, -8.570261, 2.6487997)244 (Quaternion. -2.318909E-4, 0.9985348, 0.053941682, 0.004291452)])246 (defn worm-world-defaults []247 (let [direct-control (worm-direct-control worm-muscle-labels 40)]248 (merge direct-control249 {:view worm-side-view250 :record nil251 :experiences (atom [])252 :experience-watch debug-experience253 :worm-model worm-model254 :end-frame nil})))256 (defn dir! [file]257 (if-not (.exists file)258 (.mkdir file))259 file)261 (defn record-experience! [experiences data]262 (swap! experiences #(conj % data)))264 (defn enable-shadows [world]265 (let [bsr (doto266 (BasicShadowRenderer. (asset-manager) 512)267 (.setDirection (.normalizeLocal (Vector3f. 1 -1 -1))))]268 (.addProcessor (.getViewPort world) bsr)))270 (defn enable-good-shadows [world]271 (let [pssm272 (doto (PssmShadowRenderer. (asset-manager) 1024 3)273 (.setDirection (.normalizeLocal (Vector3f. -1 -3 -1)))274 (.setLambda (float 0.55))275 (.setShadowIntensity (float 0.6))276 (.setCompareMode PssmShadowRenderer$CompareMode/Software)277 (.setFilterMode PssmShadowRenderer$FilterMode/Bilinear))]278 (.addProcessor (.getViewPort world) pssm)))280 (defn debug-experience281 [experiences text]282 (cond283 (grand-circle? experiences) (.setText text "Grand Circle")284 (curled? experiences) (.setText text "Curled")285 (wiggling? experiences) (.setText text "Wiggling")286 (resting? experiences) (.setText text "Resting")))289 (defn worm-world290 [& {:keys [record motor-control keybindings view experiences291 worm-model end-frame experience-watch] :as settings}]292 (let [{:keys [record motor-control keybindings view experiences293 worm-model end-frame experience-watch]}294 (merge (worm-world-defaults) settings)296 touch-display (view-touch)297 prop-display (view-proprioception)298 muscle-display (view-movement)299 {:keys [proprioception touch muscles body]} (worm)301 floor302 (box 5 1 5 :position (Vector3f. 0 -10 0)303 :mass 0304 :texture "Textures/aurellem.png"305 :material "Common/MatDefs/Misc/Unshaded.j3md")306 timer (IsoTimer. 60)308 font (.loadFont (asset-manager) "Interface/Fonts/Console.fnt")309 worm-action (doto (BitmapText. font false)310 (.setSize 35)311 (.setColor (ColorRGBA/Black)))]313 (world314 (nodify [body floor])315 (merge standard-debug-controls keybindings)316 (fn [world]317 (.setLocalTranslation318 worm-action 20 470 0)319 (.attachChild (.getGuiNode world) worm-action)321 (enable-good-shadows world)322 (.setShadowMode body RenderQueue$ShadowMode/CastAndReceive)323 (.setShadowMode floor RenderQueue$ShadowMode/Receive)325 (.setBackgroundColor (.getViewPort world) (ColorRGBA/White))326 (.setDisplayStatView world false)327 (.setDisplayFps world false)328 (position-camera world view)329 (.setTimer world timer)330 ;;(display-dilated-time world timer)331 (when record332 (dir! record)333 (Capture/captureVideo334 world335 (dir! (File. record "main-view"))))336 (speed-up world)337 ;;(light-up-everything world)338 )339 (fn [world tpf]340 (if (and end-frame (> (.getTime timer) end-frame))341 (.stop world))342 (let [muscle-data (vec (motor-control muscles))343 proprioception-data (proprioception)344 touch-data (mapv #(% (.getRootNode world)) touch)]345 (when experiences346 (record-experience!347 experiences {:touch touch-data348 :proprioception proprioception-data349 :muscle muscle-data}))350 (when experience-watch351 (experience-watch @experiences worm-action))352 (muscle-display353 muscle-data354 (when record (dir! (File. record "muscle"))))355 (prop-display356 proprioception-data357 (when record (dir! (File. record "proprio"))))358 (touch-display359 touch-data360 (when record (dir! (File. record "touch")))))))))364 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;365 ;;;;;;;; Phi-Space ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;366 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;368 (defn generate-phi-space []369 (let [experiences (atom [])]370 (run-world371 (apply-map372 worm-world373 (merge374 (worm-world-defaults)375 {:end-frame 700376 :motor-control377 (motor-control-program worm-muscle-labels do-all-the-things)378 :experiences experiences})))379 @experiences))381 (defn bin [digits]382 (fn [angles]383 (->> angles384 (flatten)385 (map (juxt #(Math/sin %) #(Math/cos %)))386 (flatten)387 (mapv #(Math/round (* % (Math/pow 10 (dec digits))))))))389 ;; k-nearest neighbors with spatial binning. Only returns a result if390 ;; the propriceptive data is within 10% of a previously recorded391 ;; result in all dimensions.392 (defn gen-phi-scan [phi-space]393 (let [bin-keys (map bin [3 2 1])394 bin-maps395 (map (fn [bin-key]396 (group-by397 (comp bin-key :proprioception phi-space)398 (range (count phi-space)))) bin-keys)399 lookups (map (fn [bin-key bin-map]400 (fn [proprio] (bin-map (bin-key proprio))))401 bin-keys bin-maps)]402 (fn lookup [proprio-data]403 (set (some #(% proprio-data) lookups)))))406 (defn longest-thread407 "Find the longest thread from phi-index-sets. The index sets should408 be ordered from most recent to least recent."409 [phi-index-sets]410 (loop [result '()411 [thread-bases & remaining :as phi-index-sets] phi-index-sets]412 (if (empty? phi-index-sets)413 (vec result)414 (let [threads415 (for [thread-base thread-bases]416 (loop [thread (list thread-base)417 remaining remaining]418 (let [next-index (dec (first thread))]419 (cond (empty? remaining) thread420 (contains? (first remaining) next-index)421 (recur422 (cons next-index thread) (rest remaining))423 :else thread))))424 longest-thread425 (reduce (fn [thread-a thread-b]426 (if (> (count thread-a) (count thread-b))427 thread-a thread-b))428 '(nil)429 threads)]430 (recur (concat longest-thread result)431 (drop (count longest-thread) phi-index-sets))))))434 (defn init []435 (def phi-space (generate-phi-space))436 (def phi-scan (gen-phi-scan phi-space))437 )439 ;; (defn infer-nils-dyl [s]440 ;; (loop [closed ()441 ;; open s442 ;; anchor 0]443 ;; (if-not (empty? open)444 ;; (recur (conj closed445 ;; (or (peek open)446 ;; anchor))447 ;; (pop open)448 ;; (or (peek open) anchor))449 ;; closed)))451 ;; (defn infer-nils [s]452 ;; (for [i (range (count s))]453 ;; (or (get s i)454 ;; (some (comp not nil?) (vector:last-n (- (count s) i)))455 ;; 0)))458 (defn infer-nils459 "Replace nils with the next available non-nil element in the460 sequence, or barring that, 0."461 [s]462 (loop [i (dec (count s))463 v (transient s)]464 (if (zero? i) (persistent! v)465 (if-let [cur (v i)]466 (if (get v (dec i) 0)467 (recur (dec i) v)468 (recur (dec i) (assoc! v (dec i) cur)))469 (recur i (assoc! v i 0))))))471 ;; tests473 ;;(infer-nils [1 nil 1 1]) [1 1 1 1]474 ;;(infer-nils [1 1 1 nil]) [1 1 1 0]475 ;;(infer-nils [nil 2 1 1]) [2 2 1 1]478 (defn debug-experience-phi []479 (let [proprio (atom ())]480 (fn481 [experiences]482 (let [phi-indices (phi-scan (:proprioception (peek experiences)))]483 (swap! proprio (partial cons phi-indices))484 (let [exp-thread (longest-thread (take 300 @proprio))485 phi-loop (mapv phi-space (infer-nils exp-thread))]486 (println-repl (vector:last-n exp-thread 22))487 (cond488 (grand-circle? phi-loop) (println "Grand Circle")489 (curled? phi-loop) (println "Curled")490 (wiggling? phi-loop) (println "Wiggling")491 (resting? phi-loop) (println "Resting")492 :else (println "Unknown")))))))495 (defn init-interactive []496 (def phi-space497 (let [experiences (atom [])]498 (run-world499 (apply-map500 worm-world501 (merge502 (worm-world-defaults)503 {:experiences experiences})))504 @experiences))505 (def phi-scan (gen-phi-scan phi-space)))508 (defn run-experiment-1 []509 (.start (worm-world :experience-watch (debug-experience-phi))))