Mercurial > cortex
comparison org/vision.org @ 216:f5ea63245b3b
completed vision demonstration video and first draft of vision.org
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Fri, 10 Feb 2012 11:34:07 -0700 |
parents | f283c62bd212 |
children | ac46ee4e574a |
comparison
equal
deleted
inserted
replaced
215:f283c62bd212 | 216:f5ea63245b3b |
---|---|
6 #+SETUPFILE: ../../aurellem/org/setup.org | 6 #+SETUPFILE: ../../aurellem/org/setup.org |
7 #+INCLUDE: ../../aurellem/org/level-0.org | 7 #+INCLUDE: ../../aurellem/org/level-0.org |
8 #+babel: :mkdirp yes :noweb yes :exports both | 8 #+babel: :mkdirp yes :noweb yes :exports both |
9 | 9 |
10 * Vision | 10 * Vision |
11 | 11 |
12 | |
13 Vision is one of the most important senses for humans, so I need to | 12 Vision is one of the most important senses for humans, so I need to |
14 build a simulated sense of vision for my AI. I will do this with | 13 build a simulated sense of vision for my AI. I will do this with |
15 simulated eyes. Each eye can be independely moved and should see its | 14 simulated eyes. Each eye can be independely moved and should see its |
16 own version of the world depending on where it is. | 15 own version of the world depending on where it is. |
17 | 16 |
141 maintain a reference to sensor-data which is periodically updated | 140 maintain a reference to sensor-data which is periodically updated |
142 by the continuation function established by its init-function. | 141 by the continuation function established by its init-function. |
143 They can be queried every cycle, but their information may not | 142 They can be queried every cycle, but their information may not |
144 necessairly be different every cycle. | 143 necessairly be different every cycle. |
145 | 144 |
146 | |
147 | |
148 * Physical Eyes | 145 * Physical Eyes |
149 | 146 |
150 The vision pipeline described above handles the flow of rendered | 147 The vision pipeline described above handles the flow of rendered |
151 images. Now, we need simulated eyes to serve as the source of these | 148 images. Now, we need simulated eyes to serve as the source of these |
152 images. | 149 images. |
159 with =(bind-sense=). | 156 with =(bind-sense=). |
160 | 157 |
161 #+name: add-eye | 158 #+name: add-eye |
162 #+begin_src clojure | 159 #+begin_src clojure |
163 (in-ns 'cortex.vision) | 160 (in-ns 'cortex.vision) |
164 | |
165 (import com.jme3.math.Vector3f) | |
166 | |
167 (def blender-rotation-correction | |
168 (doto (Quaternion.) | |
169 (.fromRotationMatrix | |
170 (doto (Matrix3f.) | |
171 (.setColumn 0 | |
172 (Vector3f. 1 0 0)) | |
173 (.setColumn 1 | |
174 (Vector3f. 0 -1 0)) | |
175 (.setColumn 2 | |
176 (Vector3f. 0 0 -1))) | |
177 | |
178 (doto (Matrix3f.) | |
179 (.setColumn 0 | |
180 (Vector3f. | |
181 | |
182 | 161 |
183 (defn add-eye! | 162 (defn add-eye! |
184 "Create a Camera centered on the current position of 'eye which | 163 "Create a Camera centered on the current position of 'eye which |
185 follows the closest physical node in 'creature and sends visual | 164 follows the closest physical node in 'creature and sends visual |
186 data to 'continuation. The camera will point in the X direction and | 165 data to 'continuation. The camera will point in the X direction and |
195 (.setLocation cam (.getWorldTranslation eye)) | 174 (.setLocation cam (.getWorldTranslation eye)) |
196 (.lookAtDirection cam (.mult rot Vector3f/UNIT_X) | 175 (.lookAtDirection cam (.mult rot Vector3f/UNIT_X) |
197 ;; this part is consistent with using Z in | 176 ;; this part is consistent with using Z in |
198 ;; blender as the UP vector. | 177 ;; blender as the UP vector. |
199 (.mult rot Vector3f/UNIT_Y)) | 178 (.mult rot Vector3f/UNIT_Y)) |
200 | |
201 (println-repl "eye unit-z ->" (.mult rot Vector3f/UNIT_Z)) | |
202 (println-repl "eye unit-y ->" (.mult rot Vector3f/UNIT_Y)) | |
203 (println-repl "eye unit-x ->" (.mult rot Vector3f/UNIT_X)) | |
204 (.setFrustumPerspective | 179 (.setFrustumPerspective |
205 cam 45 (/ (.getWidth cam) (.getHeight cam)) 1 1000) | 180 cam 45 (/ (.getWidth cam) (.getHeight cam)) 1 1000) |
206 (bind-sense target cam) cam)) | 181 (bind-sense target cam) cam)) |
207 #+end_src | 182 #+end_src |
208 | 183 |
277 =(retina-sensor-profile)= extracts a map from the eye-node in the same | 252 =(retina-sensor-profile)= extracts a map from the eye-node in the same |
278 format as the example maps above. =(eye-dimensions)= finds the | 253 format as the example maps above. =(eye-dimensions)= finds the |
279 dimansions of the smallest image required to contain all the retinal | 254 dimansions of the smallest image required to contain all the retinal |
280 sensor maps. | 255 sensor maps. |
281 | 256 |
257 #+name: retina | |
282 #+begin_src clojure | 258 #+begin_src clojure |
283 (defn retina-sensor-profile | 259 (defn retina-sensor-profile |
284 "Return a map of pixel sensitivity numbers to BufferedImages | 260 "Return a map of pixel sensitivity numbers to BufferedImages |
285 describing the distribution of light-sensitive components of this | 261 describing the distribution of light-sensitive components of this |
286 eye. :red, :green, :blue, :gray are already defined as extracting | 262 eye. :red, :green, :blue, :gray are already defined as extracting |
304 | 280 |
305 * Eye Creation | 281 * Eye Creation |
306 | 282 |
307 First off, get the children of the "eyes" empty node to find all the | 283 First off, get the children of the "eyes" empty node to find all the |
308 eyes the creature has. | 284 eyes the creature has. |
309 | 285 #+name: eye-node |
310 #+begin_src clojure | 286 #+begin_src clojure |
311 (defvar | 287 (defvar |
312 ^{:arglists '([creature])} | 288 ^{:arglists '([creature])} |
313 eyes | 289 eyes |
314 (sense-nodes "eyes") | 290 (sense-nodes "eyes") |
316 #+end_src | 292 #+end_src |
317 | 293 |
318 Then, add the camera created by =(add-eye!)= to the simulation by | 294 Then, add the camera created by =(add-eye!)= to the simulation by |
319 creating a new viewport. | 295 creating a new viewport. |
320 | 296 |
297 #+name: add-camera | |
321 #+begin_src clojure | 298 #+begin_src clojure |
322 (defn add-camera! | 299 (defn add-camera! |
323 "Add a camera to the world, calling continuation on every frame | 300 "Add a camera to the world, calling continuation on every frame |
324 produced." | 301 produced." |
325 [#^Application world camera continuation] | 302 [#^Application world camera continuation] |
341 sensors sensitivity. I have the option to do this filtering in native | 318 sensors sensitivity. I have the option to do this filtering in native |
342 code for a slight gain in speed. I could also do it in the GPU for a | 319 code for a slight gain in speed. I could also do it in the GPU for a |
343 massive gain in speed. =(vision-kernel)= generates a list of such | 320 massive gain in speed. =(vision-kernel)= generates a list of such |
344 continuation functions, one for each channel of the eye. | 321 continuation functions, one for each channel of the eye. |
345 | 322 |
323 #+name: kernel | |
346 #+begin_src clojure | 324 #+begin_src clojure |
347 (in-ns 'cortex.vision) | 325 (in-ns 'cortex.vision) |
348 | 326 |
349 (defrecord attached-viewport [vision-fn viewport-fn] | 327 (defrecord attached-viewport [vision-fn viewport-fn] |
350 clojure.lang.IFn | 328 clojure.lang.IFn |
351 (invoke [this world] (vision-fn world)) | 329 (invoke [this world] (vision-fn world)) |
352 (applyTo [this args] (apply vision-fn args))) | 330 (applyTo [this args] (apply vision-fn args))) |
331 | |
332 (defn pixel-sense [sensitivity pixel] | |
333 (let [s-r (bit-shift-right (bit-and 0xFF0000 sensitivity) 16) | |
334 s-g (bit-shift-right (bit-and 0x00FF00 sensitivity) 8) | |
335 s-b (bit-and 0x0000FF sensitivity) | |
336 | |
337 p-r (bit-shift-right (bit-and 0xFF0000 pixel) 16) | |
338 p-g (bit-shift-right (bit-and 0x00FF00 pixel) 8) | |
339 p-b (bit-and 0x0000FF pixel) | |
340 | |
341 total-sensitivity (* 255 (+ s-r s-g s-b))] | |
342 (float (/ (+ (* s-r p-r) | |
343 (* s-g p-g) | |
344 (* s-b p-b)) | |
345 total-sensitivity)))) | |
353 | 346 |
354 (defn vision-kernel | 347 (defn vision-kernel |
355 "Returns a list of functions, each of which will return a color | 348 "Returns a list of functions, each of which will return a color |
356 channel's worth of visual information when called inside a running | 349 channel's worth of visual information when called inside a running |
357 simulation." | 350 simulation." |
376 (vec | 369 (vec |
377 (map | 370 (map |
378 (fn [[key image]] | 371 (fn [[key image]] |
379 (let [whites (white-coordinates image) | 372 (let [whites (white-coordinates image) |
380 topology (vec (collapse whites)) | 373 topology (vec (collapse whites)) |
381 mask (color-channel-presets key key)] | 374 sensitivity (sensitivity-presets key key)] |
382 (attached-viewport. | 375 (attached-viewport. |
383 (fn [world] | 376 (fn [world] |
384 (register-eye! world) | 377 (register-eye! world) |
385 (vector | 378 (vector |
386 topology | 379 topology |
387 (vec | 380 (vec |
388 (for [[x y] whites] | 381 (for [[x y] whites] |
389 (bit-and | 382 (pixel-sense |
390 mask (.getRGB @vision-image x y)))))) | 383 sensitivity |
384 (.getRGB @vision-image x y)))))) | |
391 register-eye!))) | 385 register-eye!))) |
392 retinal-map)))) | 386 retinal-map)))) |
393 | 387 |
394 (defn gen-fix-display | 388 (defn gen-fix-display |
395 "Create a function to call to restore a simulation's display when it | 389 "Create a function to call to restore a simulation's display when it |
396 is disrupted by a Viewport." | 390 is disrupted by a Viewport." |
397 [] | 391 [] |
398 (runonce | 392 (runonce |
399 (fn [world] | 393 (fn [world] |
400 (add-camera! world (.getCamera world) no-op)))) | 394 (add-camera! world (.getCamera world) no-op)))) |
401 | |
402 #+end_src | 395 #+end_src |
403 | 396 |
404 Note that since each of the functions generated by =(vision-kernel)= | 397 Note that since each of the functions generated by =(vision-kernel)= |
405 shares the same =(register-eye!)= function, the eye will be registered | 398 shares the same =(register-eye!)= function, the eye will be registered |
406 only once the first time any of the functions from the list returned | 399 only once the first time any of the functions from the list returned |
417 | 410 |
418 All the hard work has been done, all that remains is to apply | 411 All the hard work has been done, all that remains is to apply |
419 =(vision-kernel)= to each eye in the creature and gather the results | 412 =(vision-kernel)= to each eye in the creature and gather the results |
420 into one list of functions. | 413 into one list of functions. |
421 | 414 |
415 #+name: main | |
422 #+begin_src clojure | 416 #+begin_src clojure |
423 (defn vision! | 417 (defn vision! |
424 "Returns a function which returns visual sensory data when called | 418 "Returns a function which returns visual sensory data when called |
425 inside a running simulation" | 419 inside a running simulation" |
426 [#^Node creature & {skip :skip :or {skip 0}}] | 420 [#^Node creature & {skip :skip :or {skip 0}}] |
434 | 428 |
435 It's vital to have a visual representation for each sense. Here I use | 429 It's vital to have a visual representation for each sense. Here I use |
436 =(view-sense)= to construct a function that will create a display for | 430 =(view-sense)= to construct a function that will create a display for |
437 visual data. | 431 visual data. |
438 | 432 |
433 #+name: display | |
439 #+begin_src clojure | 434 #+begin_src clojure |
435 (in-ns 'cortex.vision) | |
436 | |
440 (defn view-vision | 437 (defn view-vision |
441 "Creates a function which accepts a list of visual sensor-data and | 438 "Creates a function which accepts a list of visual sensor-data and |
442 displays each element of the list to the screen." | 439 displays each element of the list to the screen." |
443 [] | 440 [] |
444 (view-sense | 441 (view-sense |
446 [[coords sensor-data]] | 443 [[coords sensor-data]] |
447 (let [image (points->image coords)] | 444 (let [image (points->image coords)] |
448 (dorun | 445 (dorun |
449 (for [i (range (count coords))] | 446 (for [i (range (count coords))] |
450 (.setRGB image ((coords i) 0) ((coords i) 1) | 447 (.setRGB image ((coords i) 0) ((coords i) 1) |
451 (sensor-data i)))) | 448 (gray (int (* 255 (sensor-data i))))))) |
452 image)))) | 449 image)))) |
453 #+end_src | 450 #+end_src |
454 | 451 |
455 * Tests | 452 * Tests |
456 | 453 |
545 : "(let [retina \"Models/test-creature/retina-small.png\"] | 542 : "(let [retina \"Models/test-creature/retina-small.png\"] |
546 : {:all retina :red retina :green retina :blue retina})" | 543 : {:all retina :red retina :green retina :blue retina})" |
547 | 544 |
548 This is the approximation to the human eye described earlier. | 545 This is the approximation to the human eye described earlier. |
549 | 546 |
547 #+name: test-2 | |
550 #+begin_src clojure | 548 #+begin_src clojure |
551 (in-ns 'cortex.test.vision) | 549 (in-ns 'cortex.test.vision) |
552 | 550 |
553 (import com.aurellem.capture.Capture) | 551 (defn change-color [obj color] |
552 (println-repl obj) | |
553 (if obj | |
554 (.setColor (.getMaterial obj) "Color" color))) | |
555 | |
556 (defn colored-cannon-ball [color] | |
557 (comp #(change-color % color) | |
558 (fire-cannon-ball))) | |
554 | 559 |
555 (defn test-worm-vision [] | 560 (defn test-worm-vision [] |
556 (let [the-worm (doto (worm)(body!)) | 561 (let [the-worm (doto (worm)(body!)) |
557 vision (vision! the-worm) | 562 vision (vision! the-worm) |
558 vision-display (view-vision) | 563 vision-display (view-vision) |
564 y-axis | 569 y-axis |
565 (box 0.01 1 0.01 :physical? false :color ColorRGBA/Green | 570 (box 0.01 1 0.01 :physical? false :color ColorRGBA/Green |
566 :position (Vector3f. 0 -5 0)) | 571 :position (Vector3f. 0 -5 0)) |
567 z-axis | 572 z-axis |
568 (box 0.01 0.01 1 :physical? false :color ColorRGBA/Blue | 573 (box 0.01 0.01 1 :physical? false :color ColorRGBA/Blue |
569 :position (Vector3f. 0 -5 0))] | 574 :position (Vector3f. 0 -5 0)) |
575 timer (RatchetTimer. 60)] | |
570 | 576 |
571 (world (nodify [(floor) the-worm x-axis y-axis z-axis me]) | 577 (world (nodify [(floor) the-worm x-axis y-axis z-axis me]) |
572 standard-debug-controls | 578 (assoc standard-debug-controls |
579 "key-r" (colored-cannon-ball ColorRGBA/Red) | |
580 "key-b" (colored-cannon-ball ColorRGBA/Blue) | |
581 "key-g" (colored-cannon-ball ColorRGBA/Green)) | |
573 (fn [world] | 582 (fn [world] |
574 (light-up-everything world) | 583 (light-up-everything world) |
584 (speed-up world) | |
585 (.setTimer world timer) | |
586 (display-dialated-time world timer) | |
575 ;; add a view from the worm's perspective | 587 ;; add a view from the worm's perspective |
576 (add-camera! | 588 (add-camera! |
577 world | 589 world |
578 (add-eye! the-worm | 590 (add-eye! the-worm |
579 (.getChild | 591 (.getChild |
581 (comp | 593 (comp |
582 (view-image | 594 (view-image |
583 (File. "/home/r/proj/cortex/render/worm-vision/worm-view")) | 595 (File. "/home/r/proj/cortex/render/worm-vision/worm-view")) |
584 BufferedImage!)) | 596 BufferedImage!)) |
585 (set-gravity world Vector3f/ZERO) | 597 (set-gravity world Vector3f/ZERO) |
586 (Capture/captureVideo | 598 (try |
587 world | 599 (Capture/captureVideo |
588 (File. "/home/r/proj/cortex/render/worm-vision/main-view"))) | 600 world |
601 (File. "/home/r/proj/cortex/render/worm-vision/main-view")))) | |
602 | |
589 (fn [world _ ] | 603 (fn [world _ ] |
590 (.setLocalTranslation me (.getLocation (.getCamera world))) | 604 (.setLocalTranslation me (.getLocation (.getCamera world))) |
591 (vision-display | 605 (vision-display |
592 (map #(% world) vision) | 606 (map #(% world) vision) |
593 (File. "/home/r/proj/cortex/render/worm-vision")) | 607 (File. "/home/r/proj/cortex/render/worm-vision")) |
594 (fix-display world))))) | 608 (fix-display world))))) |
595 #+end_src | 609 #+end_src |
596 | 610 |
611 ** Methods to Generate the Worm Video | |
612 #+name: magick2 | |
613 #+begin_src clojure | |
614 (ns cortex.video.magick2 | |
615 (:import java.io.File) | |
616 (:use clojure.contrib.shell-out)) | |
617 | |
618 (defn images [path] | |
619 (sort (rest (file-seq (File. path))))) | |
620 | |
621 (def base "/home/r/proj/cortex/render/worm-vision/") | |
622 | |
623 (defn pics [file] | |
624 (images (str base file))) | |
625 | |
626 (defn combine-images [] | |
627 (let [main-view (pics "main-view") | |
628 worm-view (pics "worm-view") | |
629 blue (pics "0") | |
630 green (pics "1") | |
631 red (pics "2") | |
632 gray (pics "3") | |
633 blender (let [b-pics (pics "blender")] | |
634 (concat b-pics (repeat 9001 (last b-pics)))) | |
635 background (repeat 9001 (File. (str base "background.png"))) | |
636 targets (map | |
637 #(File. (str base "out/" (format "%07d.png" %))) | |
638 (range 0 (count main-view)))] | |
639 (dorun | |
640 (pmap | |
641 (comp | |
642 (fn [[background main-view worm-view red green blue gray blender target]] | |
643 (println target) | |
644 (sh "convert" | |
645 background | |
646 main-view "-geometry" "+18+17" "-composite" | |
647 worm-view "-geometry" "+677+17" "-composite" | |
648 green "-geometry" "+685+430" "-composite" | |
649 red "-geometry" "+788+430" "-composite" | |
650 blue "-geometry" "+894+430" "-composite" | |
651 gray "-geometry" "+1000+430" "-composite" | |
652 blender "-geometry" "+0+0" "-composite" | |
653 target)) | |
654 (fn [& args] (map #(.getCanonicalPath %) args))) | |
655 background main-view worm-view red green blue gray blender targets)))) | |
656 #+end_src | |
657 | |
658 #+begin_src sh :results silent | |
659 cd /home/r/proj/cortex/render/worm-vision | |
660 ffmpeg -r 25 -b 9001k -i out/%07d.png -vcodec libtheora worm-vision.ogg | |
661 #+end_src | |
662 | |
663 * Demonstration of Vision | |
664 #+begin_html | |
665 <div class="figure"> | |
666 <video controls="controls" width="755"> | |
667 <source src="../video/worm-vision.ogg" type="video/ogg" | |
668 preload="none" poster="../images/aurellem-1280x480.png" /> | |
669 </video> | |
670 <p>Simulated Vision in a Virtual Environment</p> | |
671 </div> | |
672 #+end_html | |
673 | |
597 * Headers | 674 * Headers |
598 | 675 |
599 #+name: vision-header | 676 #+name: vision-header |
600 #+begin_src clojure | 677 #+begin_src clojure |
601 (ns cortex.vision | 678 (ns cortex.vision |
602 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple | 679 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple |
603 eyes from different positions to observe the same world, and pass | 680 eyes from different positions to observe the same world, and pass |
604 the observed data to any arbitray function. Automatically reads | 681 the observed data to any arbitray function. Automatically reads |
605 eye-nodes from specially prepared blender files and instanttiates | 682 eye-nodes from specially prepared blender files and instantiates |
606 them in the world as actual eyes." | 683 them in the world as actual eyes." |
607 {:author "Robert McIntyre"} | 684 {:author "Robert McIntyre"} |
608 (:use (cortex world sense util)) | 685 (:use (cortex world sense util)) |
609 (:use clojure.contrib.def) | 686 (:use clojure.contrib.def) |
610 (:import com.jme3.post.SceneProcessor) | 687 (:import com.jme3.post.SceneProcessor) |
611 (:import (com.jme3.util BufferUtils Screenshots)) | 688 (:import (com.jme3.util BufferUtils Screenshots)) |
612 (:import java.nio.ByteBuffer) | 689 (:import java.nio.ByteBuffer) |
613 (:import java.awt.image.BufferedImage) | 690 (:import java.awt.image.BufferedImage) |
614 (:import (com.jme3.renderer ViewPort Camera)) | 691 (:import (com.jme3.renderer ViewPort Camera)) |
615 (:import com.jme3.math.ColorRGBA) | 692 (:import (com.jme3.math ColorRGBA Vector3f Matrix3f)) |
616 (:import com.jme3.renderer.Renderer) | 693 (:import com.jme3.renderer.Renderer) |
617 (:import com.jme3.app.Application) | 694 (:import com.jme3.app.Application) |
618 (:import com.jme3.texture.FrameBuffer) | 695 (:import com.jme3.texture.FrameBuffer) |
619 (:import (com.jme3.scene Node Spatial))) | 696 (:import (com.jme3.scene Node Spatial))) |
620 #+end_src | 697 #+end_src |
630 (:import java.awt.Dimension) | 707 (:import java.awt.Dimension) |
631 (:import javax.swing.JFrame) | 708 (:import javax.swing.JFrame) |
632 (:import com.jme3.math.ColorRGBA) | 709 (:import com.jme3.math.ColorRGBA) |
633 (:import com.jme3.scene.Node) | 710 (:import com.jme3.scene.Node) |
634 (:import com.jme3.math.Vector3f) | 711 (:import com.jme3.math.Vector3f) |
635 (:import java.io.File)) | 712 (:import java.io.File) |
636 #+end_src | 713 (:import (com.aurellem.capture Capture RatchetTimer))) |
637 | 714 #+end_src |
638 | 715 |
639 | 716 * Onward! |
640 - As a neat bonus, this idea behind simulated vision also enables one | 717 - As a neat bonus, this idea behind simulated vision also enables one |
641 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]]. | 718 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]]. |
719 - Now that we have vision, it's time to tackle [[./hearing.org][hearing]]. | |
720 | |
721 * Source Listing | |
722 - [[../src/cortex/vision.clj][cortex.vision]] | |
723 - [[../src/cortex/test/vision.clj][cortex.test.vision]] | |
724 - [[../src/cortex/video/magick2.clj][cortex.video.magick2]] | |
725 - [[../assets/Models/subtitles/worm-vision-subtitles.blend][worm-vision-subtitles.blend]] | |
726 #+html: <ul> <li> <a href="../org/sense.org">This org file</a> </li> </ul> | |
727 - [[http://hg.bortreb.com ][source-repository]] | |
728 | |
642 | 729 |
643 | 730 |
644 * COMMENT Generate Source | 731 * COMMENT Generate Source |
645 #+begin_src clojure :tangle ../src/cortex/vision.clj | 732 #+begin_src clojure :tangle ../src/cortex/vision.clj |
646 <<eyes>> | 733 <<vision-header>> |
734 <<pipeline-1>> | |
735 <<pipeline-2>> | |
736 <<retina>> | |
737 <<add-eye>> | |
738 <<sensitivity>> | |
739 <<eye-node>> | |
740 <<add-camera>> | |
741 <<kernel>> | |
742 <<main>> | |
743 <<display>> | |
647 #+end_src | 744 #+end_src |
648 | 745 |
649 #+begin_src clojure :tangle ../src/cortex/test/vision.clj | 746 #+begin_src clojure :tangle ../src/cortex/test/vision.clj |
650 <<test-header>> | 747 <<test-header>> |
651 <<test-1>> | 748 <<test-1>> |
652 #+end_src | 749 <<test-2>> |
750 #+end_src | |
751 | |
752 #+begin_src clojure :tangle ../src/cortex/video/magick2.clj | |
753 <<magick2>> | |
754 #+end_src |