view org/vision.org @ 264:f8227f6d4ac6

Some section renaming and minor other changes in vision.
author Dylan Holmes <ocsenave@gmail.com>
date Mon, 13 Feb 2012 07:29:29 -0600
parents 0e85237d27a7
children e57d8c52f12f
line wrap: on
line source
1 #+title: Simulated Sense of Sight
2 #+author: Robert McIntyre
3 #+email: rlm@mit.edu
4 #+description: Simulated sight for AI research using JMonkeyEngine3 and clojure
5 #+keywords: computer vision, jMonkeyEngine3, clojure
6 #+SETUPFILE: ../../aurellem/org/setup.org
7 #+INCLUDE: ../../aurellem/org/level-0.org
8 #+babel: :mkdirp yes :noweb yes :exports both
10 #* Vision
11 * JMonkeyEngine natively supports multiple views of the same world.
13 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
15 simulated eyes. Each eye can be independely moved and should see its
16 own version of the world depending on where it is.
18 Making these simulated eyes a reality is simple bacause jMonkeyEngine
19 already conatains extensive support for multiple views of the same 3D
20 simulated world. The reason jMonkeyEngine has this support is because
21 the support is necessary to create games with split-screen
22 views. Multiple views are also used to create efficient
23 pseudo-reflections by rendering the scene from a certain perspective
24 and then projecting it back onto a surface in the 3D world.
26 #+caption: jMonkeyEngine supports multiple views to enable split-screen games, like GoldenEye, which was one of the first games to use split-screen views.
27 [[../images/goldeneye-4-player.png]]
29 ** =ViewPorts=, =SceneProcessors=, and the =RenderManager=.
30 # =Viewports= are cameras; =RenderManger= takes snapshots each frame.
31 #* A Brief Description of jMonkeyEngine's Rendering Pipeline
33 jMonkeyEngine allows you to create a =ViewPort=, which represents a
34 view of the simulated world. You can create as many of these as you
35 want. Every frame, the =RenderManager= iterates through each
36 =ViewPort=, rendering the scene in the GPU. For each =ViewPort= there
37 is a =FrameBuffer= which represents the rendered image in the GPU.
39 #+caption: =ViewPorts= are cameras in the world. During each frame, the =Rendermanager= records a snapshot of what each view is currently seeing.
40 #+attr_html:width="400"
41 [[../images/diagram_rendermanager.png]]
43 Each =ViewPort= can have any number of attached =SceneProcessor=
44 objects, which are called every time a new frame is rendered. A
45 =SceneProcessor= recieves its =ViewPort's= =FrameBuffer= and can do
46 whatever it wants to the data. Often this consists of invoking GPU
47 specific operations on the rendered image. The =SceneProcessor= can
48 also copy the GPU image data to RAM and process it with the CPU.
50 ** From Views to Vision
51 # Appropriating Views for Vision.
53 Each eye in the simulated creature needs its own =ViewPort= so that
54 it can see the world from its own perspective. To this =ViewPort=, I
55 add a =SceneProcessor= that feeds the visual data to any arbitray
56 continuation function for further processing. That continuation
57 function may perform both CPU and GPU operations on the data. To make
58 this easy for the continuation function, the =SceneProcessor=
59 maintains appropriatly sized buffers in RAM to hold the data. It does
60 not do any copying from the GPU to the CPU itself because it is a slow
61 operation.
63 #+name: pipeline-1
64 #+begin_src clojure
65 (defn vision-pipeline
66 "Create a SceneProcessor object which wraps a vision processing
67 continuation function. The continuation is a function that takes
68 [#^Renderer r #^FrameBuffer fb #^ByteBuffer b #^BufferedImage bi],
69 each of which has already been appropiately sized."
70 [continuation]
71 (let [byte-buffer (atom nil)
72 renderer (atom nil)
73 image (atom nil)]
74 (proxy [SceneProcessor] []
75 (initialize
76 [renderManager viewPort]
77 (let [cam (.getCamera viewPort)
78 width (.getWidth cam)
79 height (.getHeight cam)]
80 (reset! renderer (.getRenderer renderManager))
81 (reset! byte-buffer
82 (BufferUtils/createByteBuffer
83 (* width height 4)))
84 (reset! image (BufferedImage.
85 width height
86 BufferedImage/TYPE_4BYTE_ABGR))))
87 (isInitialized [] (not (nil? @byte-buffer)))
88 (reshape [_ _ _])
89 (preFrame [_])
90 (postQueue [_])
91 (postFrame
92 [#^FrameBuffer fb]
93 (.clear @byte-buffer)
94 (continuation @renderer fb @byte-buffer @image))
95 (cleanup []))))
96 #+end_src
98 The continuation function given to =(vision-pipeline)= above will be
99 given a =Renderer= and three containers for image data. The
100 =FrameBuffer= references the GPU image data, but the pixel data can
101 not be used directly on the CPU. The =ByteBuffer= and =BufferedImage=
102 are initially "empty" but are sized to hold the data in the
103 =FrameBuffer=. I call transfering the GPU image data to the CPU
104 structures "mixing" the image data. I have provided three functions to
105 do this mixing.
107 #+name: pipeline-2
108 #+begin_src clojure
109 (defn frameBuffer->byteBuffer!
110 "Transfer the data in the graphics card (Renderer, FrameBuffer) to
111 the CPU (ByteBuffer)."
112 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb]
113 (.readFrameBuffer r fb bb) bb)
115 (defn byteBuffer->bufferedImage!
116 "Convert the C-style BGRA image data in the ByteBuffer bb to the AWT
117 style ABGR image data and place it in BufferedImage bi."
118 [#^ByteBuffer bb #^BufferedImage bi]
119 (Screenshots/convertScreenShot bb bi) bi)
121 (defn BufferedImage!
122 "Continuation which will grab the buffered image from the materials
123 provided by (vision-pipeline)."
124 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb #^BufferedImage bi]
125 (byteBuffer->bufferedImage!
126 (frameBuffer->byteBuffer! r fb bb) bi))
127 #+end_src
129 Note that it is possible to write vision processing algorithms
130 entirely in terms of =BufferedImage= inputs. Just compose that
131 =BufferedImage= algorithm with =(BufferedImage!)=. However, a vision
132 processing algorithm that is entirely hosted on the GPU does not have
133 to pay for this convienence.
135 * COMMENT asdasd
137 (vision creature) will take an optional :skip argument which will
138 inform the continuations in scene processor to skip the given
139 number of cycles 0 means that no cycles will be skipped.
141 (vision creature) will return [init-functions sensor-functions].
142 The init-functions are each single-arg functions that take the
143 world and register the cameras and must each be called before the
144 corresponding sensor-functions. Each init-function returns the
145 viewport for that eye which can be manipulated, saved, etc. Each
146 sensor-function is a thunk and will return data in the same
147 format as the tactile-sensor functions the structure is
148 [topology, sensor-data]. Internally, these sensor-functions
149 maintain a reference to sensor-data which is periodically updated
150 by the continuation function established by its init-function.
151 They can be queried every cycle, but their information may not
152 necessairly be different every cycle.
154 * Optical sensor arrays are described as images and stored as metadata.
156 The vision pipeline described above handles the flow of rendered
157 images. Now, we need simulated eyes to serve as the source of these
158 images.
160 An eye is described in blender in the same way as a joint. They are
161 zero dimensional empty objects with no geometry whose local coordinate
162 system determines the orientation of the resulting eye. All eyes are
163 childern of a parent node named "eyes" just as all joints have a
164 parent named "joints". An eye binds to the nearest physical object
165 with =(bind-sense=).
167 #+name: add-eye
168 #+begin_src clojure
169 (in-ns 'cortex.vision)
171 (defn add-eye!
172 "Create a Camera centered on the current position of 'eye which
173 follows the closest physical node in 'creature and sends visual
174 data to 'continuation. The camera will point in the X direction and
175 use the Z vector as up as determined by the rotation of these
176 vectors in blender coordinate space. Use XZY rotation for the node
177 in blender."
178 [#^Node creature #^Spatial eye]
179 (let [target (closest-node creature eye)
180 [cam-width cam-height] (eye-dimensions eye)
181 cam (Camera. cam-width cam-height)
182 rot (.getWorldRotation eye)]
183 (.setLocation cam (.getWorldTranslation eye))
184 (.lookAtDirection
185 cam ; this part is not a mistake and
186 (.mult rot Vector3f/UNIT_X) ; is consistent with using Z in
187 (.mult rot Vector3f/UNIT_Y)) ; blender as the UP vector.
188 (.setFrustumPerspective
189 cam 45 (/ (.getWidth cam) (.getHeight cam)) 1 1000)
190 (bind-sense target cam) cam))
191 #+end_src
193 Here, the camera is created based on metadata on the eye-node and
194 attached to the nearest physical object with =(bind-sense)=
197 ** The Retina
199 An eye is a surface (the retina) which contains many discrete sensors
200 to detect light. These sensors have can have different light-sensing
201 properties. In humans, each discrete sensor is sensitive to red,
202 blue, green, or gray. These different types of sensors can have
203 different spatial distributions along the retina. In humans, there is
204 a fovea in the center of the retina which has a very high density of
205 color sensors, and a blind spot which has no sensors at all. Sensor
206 density decreases in proportion to distance from the fovea.
208 I want to be able to model any retinal configuration, so my eye-nodes
209 in blender contain metadata pointing to images that describe the
210 percise position of the individual sensors using white pixels. The
211 meta-data also describes the percise sensitivity to light that the
212 sensors described in the image have. An eye can contain any number of
213 these images. For example, the metadata for an eye might look like
214 this:
216 #+begin_src clojure
217 {0xFF0000 "Models/test-creature/retina-small.png"}
218 #+end_src
220 #+caption: The retinal profile image "Models/test-creature/retina-small.png". White pixels are photo-sensitive elements. The distribution of white pixels is denser in the middle and falls off at the edges and is inspired by the human retina.
221 [[../assets/Models/test-creature/retina-small.png]]
223 Together, the number 0xFF0000 and the image image above describe the
224 placement of red-sensitive sensory elements.
226 Meta-data to very crudely approximate a human eye might be something
227 like this:
229 #+begin_src clojure
230 (let [retinal-profile "Models/test-creature/retina-small.png"]
231 {0xFF0000 retinal-profile
232 0x00FF00 retinal-profile
233 0x0000FF retinal-profile
234 0xFFFFFF retinal-profile})
235 #+end_src
237 The numbers that serve as keys in the map determine a sensor's
238 relative sensitivity to the channels red, green, and blue. These
239 sensitivity values are packed into an integer in the order =|_|R|G|B|=
240 in 8-bit fields. The RGB values of a pixel in the image are added
241 together with these sensitivities as linear weights. Therfore,
242 0xFF0000 means sensitive to red only while 0xFFFFFF means sensitive to
243 all colors equally (gray).
245 For convienence I've defined a few symbols for the more common
246 sensitivity values.
248 #+name: sensitivity
249 #+begin_src clojure
250 (defvar sensitivity-presets
251 {:all 0xFFFFFF
252 :red 0xFF0000
253 :blue 0x0000FF
254 :green 0x00FF00}
255 "Retinal sensitivity presets for sensors that extract one channel
256 (:red :blue :green) or average all channels (:all)")
257 #+end_src
259 ** Metadata Processing
261 =(retina-sensor-profile)= extracts a map from the eye-node in the same
262 format as the example maps above. =(eye-dimensions)= finds the
263 dimensions of the smallest image required to contain all the retinal
264 sensor maps.
266 #+name: retina
267 #+begin_src clojure
268 (defn retina-sensor-profile
269 "Return a map of pixel sensitivity numbers to BufferedImages
270 describing the distribution of light-sensitive components of this
271 eye. :red, :green, :blue, :gray are already defined as extracting
272 the red, green, blue, and average components respectively."
273 [#^Spatial eye]
274 (if-let [eye-map (meta-data eye "eye")]
275 (map-vals
276 load-image
277 (eval (read-string eye-map)))))
279 (defn eye-dimensions
280 "Returns [width, height] determined by the metadata of the eye."
281 [#^Spatial eye]
282 (let [dimensions
283 (map #(vector (.getWidth %) (.getHeight %))
284 (vals (retina-sensor-profile eye)))]
285 [(apply max (map first dimensions))
286 (apply max (map second dimensions))]))
287 #+end_src
289 * Putting it all together: Importing and parsing descriptions of eyes.
290 First off, get the children of the "eyes" empty node to find all the
291 eyes the creature has.
292 #+name: eye-node
293 #+begin_src clojure
294 (defvar
295 ^{:arglists '([creature])}
296 eyes
297 (sense-nodes "eyes")
298 "Return the children of the creature's \"eyes\" node.")
299 #+end_src
301 Then, add the camera created by =(add-eye!)= to the simulation by
302 creating a new viewport.
304 #+name: add-camera
305 #+begin_src clojure
306 (defn add-camera!
307 "Add a camera to the world, calling continuation on every frame
308 produced."
309 [#^Application world camera continuation]
310 (let [width (.getWidth camera)
311 height (.getHeight camera)
312 render-manager (.getRenderManager world)
313 viewport (.createMainView render-manager "eye-view" camera)]
314 (doto viewport
315 (.setClearFlags true true true)
316 (.setBackgroundColor ColorRGBA/Black)
317 (.addProcessor (vision-pipeline continuation))
318 (.attachScene (.getRootNode world)))))
319 #+end_src
322 The eye's continuation function should register the viewport with the
323 simulation the first time it is called, use the CPU to extract the
324 appropriate pixels from the rendered image and weight them by each
325 sensor's sensitivity. I have the option to do this processing in
326 native code for a slight gain in speed. I could also do it in the GPU
327 for a massive gain in speed. =(vision-kernel)= generates a list of
328 such continuation functions, one for each channel of the eye.
330 #+name: kernel
331 #+begin_src clojure
332 (in-ns 'cortex.vision)
334 (defrecord attached-viewport [vision-fn viewport-fn]
335 clojure.lang.IFn
336 (invoke [this world] (vision-fn world))
337 (applyTo [this args] (apply vision-fn args)))
339 (defn pixel-sense [sensitivity pixel]
340 (let [s-r (bit-shift-right (bit-and 0xFF0000 sensitivity) 16)
341 s-g (bit-shift-right (bit-and 0x00FF00 sensitivity) 8)
342 s-b (bit-and 0x0000FF sensitivity)
344 p-r (bit-shift-right (bit-and 0xFF0000 pixel) 16)
345 p-g (bit-shift-right (bit-and 0x00FF00 pixel) 8)
346 p-b (bit-and 0x0000FF pixel)
348 total-sensitivity (* 255 (+ s-r s-g s-b))]
349 (float (/ (+ (* s-r p-r)
350 (* s-g p-g)
351 (* s-b p-b))
352 total-sensitivity))))
354 (defn vision-kernel
355 "Returns a list of functions, each of which will return a color
356 channel's worth of visual information when called inside a running
357 simulation."
358 [#^Node creature #^Spatial eye & {skip :skip :or {skip 0}}]
359 (let [retinal-map (retina-sensor-profile eye)
360 camera (add-eye! creature eye)
361 vision-image
362 (atom
363 (BufferedImage. (.getWidth camera)
364 (.getHeight camera)
365 BufferedImage/TYPE_BYTE_BINARY))
366 register-eye!
367 (runonce
368 (fn [world]
369 (add-camera!
370 world camera
371 (let [counter (atom 0)]
372 (fn [r fb bb bi]
373 (if (zero? (rem (swap! counter inc) (inc skip)))
374 (reset! vision-image
375 (BufferedImage! r fb bb bi))))))))]
376 (vec
377 (map
378 (fn [[key image]]
379 (let [whites (white-coordinates image)
380 topology (vec (collapse whites))
381 sensitivity (sensitivity-presets key key)]
382 (attached-viewport.
383 (fn [world]
384 (register-eye! world)
385 (vector
386 topology
387 (vec
388 (for [[x y] whites]
389 (pixel-sense
390 sensitivity
391 (.getRGB @vision-image x y))))))
392 register-eye!)))
393 retinal-map))))
395 (defn gen-fix-display
396 "Create a function to call to restore a simulation's display when it
397 is disrupted by a Viewport."
398 []
399 (runonce
400 (fn [world]
401 (add-camera! world (.getCamera world) no-op))))
402 #+end_src
404 Note that since each of the functions generated by =(vision-kernel)=
405 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
407 by =(vision-kernel)= is called. Each of the functions returned by
408 =(vision-kernel)= also allows access to the =Viewport= through which
409 it recieves images.
411 The in-game display can be disrupted by all the viewports that the
412 functions greated by =(vision-kernel)= add. This doesn't affect the
413 simulation or the simulated senses, but can be annoying.
414 =(gen-fix-display)= restores the in-simulation display.
416 ** Vision!
418 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
420 into one list of functions.
422 #+name: main
423 #+begin_src clojure
424 (defn vision!
425 "Returns a function which returns visual sensory data when called
426 inside a running simulation."
427 [#^Node creature & {skip :skip :or {skip 0}}]
428 (reduce
429 concat
430 (for [eye (eyes creature)]
431 (vision-kernel creature eye))))
432 #+end_src
434 ** Visualization of Vision
436 It's vital to have a visual representation for each sense. Here I use
437 =(view-sense)= to construct a function that will create a display for
438 visual data.
440 #+name: display
441 #+begin_src clojure
442 (in-ns 'cortex.vision)
444 (defn view-vision
445 "Creates a function which accepts a list of visual sensor-data and
446 displays each element of the list to the screen."
447 []
448 (view-sense
449 (fn
450 [[coords sensor-data]]
451 (let [image (points->image coords)]
452 (dorun
453 (for [i (range (count coords))]
454 (.setRGB image ((coords i) 0) ((coords i) 1)
455 (gray (int (* 255 (sensor-data i)))))))
456 image))))
457 #+end_src
459 * Demonstrations
460 ** Demonstrating the vision pipeline.
462 This is a basic test for the vision system. It only tests the
463 vision-pipeline and does not deal with loading eyes from a blender
464 file. The code creates two videos of the same rotating cube from
465 different angles.
467 #+name: test-1
468 #+begin_src clojure
469 (in-ns 'cortex.test.vision)
471 (defn test-pipeline
472 "Testing vision:
473 Tests the vision system by creating two views of the same rotating
474 object from different angles and displaying both of those views in
475 JFrames.
477 You should see a rotating cube, and two windows,
478 each displaying a different view of the cube."
479 []
480 (let [candy
481 (box 1 1 1 :physical? false :color ColorRGBA/Blue)]
482 (world
483 (doto (Node.)
484 (.attachChild candy))
485 {}
486 (fn [world]
487 (let [cam (.clone (.getCamera world))
488 width (.getWidth cam)
489 height (.getHeight cam)]
490 (add-camera! world cam
491 (comp
492 (view-image
493 (File. "/home/r/proj/cortex/render/vision/1"))
494 BufferedImage!))
495 (add-camera! world
496 (doto (.clone cam)
497 (.setLocation (Vector3f. -10 0 0))
498 (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))
499 (comp
500 (view-image
501 (File. "/home/r/proj/cortex/render/vision/2"))
502 BufferedImage!))
503 ;; This is here to restore the main view
504 ;; after the other views have completed processing
505 (add-camera! world (.getCamera world) no-op)))
506 (fn [world tpf]
507 (.rotate candy (* tpf 0.2) 0 0)))))
508 #+end_src
510 #+begin_html
511 <div class="figure">
512 <video controls="controls" width="755">
513 <source src="../video/spinning-cube.ogg" type="video/ogg"
514 preload="none" poster="../images/aurellem-1280x480.png" />
515 </video>
516 <p>A rotating cube viewed from two different perspectives.</p>
517 </div>
518 #+end_html
520 Creating multiple eyes like this can be used for stereoscopic vision
521 simulation in a single creature or for simulating multiple creatures,
522 each with their own sense of vision.
523 ** Demonstrating eye import and parsing.
525 To the worm from the last post, I add a new node that describes its
526 eyes.
528 #+attr_html: width=755
529 #+caption: The worm with newly added empty nodes describing a single eye.
530 [[../images/worm-with-eye.png]]
532 The node highlighted in yellow is the root level "eyes" node. It has
533 a single child, highlighted in orange, which describes a single
534 eye. This is the "eye" node. It is placed so that the worm will have
535 an eye located in the center of the flat portion of its lower
536 hemispherical section.
538 The two nodes which are not highlighted describe the single joint of
539 the worm.
541 The metadata of the eye-node is:
543 #+begin_src clojure :results verbatim :exports both
544 (cortex.sense/meta-data
545 (.getChild (.getChild (cortex.test.body/worm) "eyes") "eye") "eye")
546 #+end_src
548 #+results:
549 : "(let [retina \"Models/test-creature/retina-small.png\"]
550 : {:all retina :red retina :green retina :blue retina})"
552 This is the approximation to the human eye described earlier.
554 #+name: test-2
555 #+begin_src clojure
556 (in-ns 'cortex.test.vision)
558 (defn change-color [obj color]
559 (println-repl obj)
560 (if obj
561 (.setColor (.getMaterial obj) "Color" color)))
563 (defn colored-cannon-ball [color]
564 (comp #(change-color % color)
565 (fire-cannon-ball)))
567 (defn test-worm-vision [record]
568 (let [the-worm (doto (worm)(body!))
569 vision (vision! the-worm)
570 vision-display (view-vision)
571 fix-display (gen-fix-display)
572 me (sphere 0.5 :color ColorRGBA/Blue :physical? false)
573 x-axis
574 (box 1 0.01 0.01 :physical? false :color ColorRGBA/Red
575 :position (Vector3f. 0 -5 0))
576 y-axis
577 (box 0.01 1 0.01 :physical? false :color ColorRGBA/Green
578 :position (Vector3f. 0 -5 0))
579 z-axis
580 (box 0.01 0.01 1 :physical? false :color ColorRGBA/Blue
581 :position (Vector3f. 0 -5 0))
582 timer (RatchetTimer. 60)]
584 (world (nodify [(floor) the-worm x-axis y-axis z-axis me])
585 (assoc standard-debug-controls
586 "key-r" (colored-cannon-ball ColorRGBA/Red)
587 "key-b" (colored-cannon-ball ColorRGBA/Blue)
588 "key-g" (colored-cannon-ball ColorRGBA/Green))
589 (fn [world]
590 (light-up-everything world)
591 (speed-up world)
592 (.setTimer world timer)
593 (display-dialated-time world timer)
594 ;; add a view from the worm's perspective
595 (if record
596 (Capture/captureVideo
597 world
598 (File.
599 "/home/r/proj/cortex/render/worm-vision/main-view")))
601 (add-camera!
602 world
603 (add-eye! the-worm
604 (.getChild
605 (.getChild the-worm "eyes") "eye"))
606 (comp
607 (view-image
608 (if record
609 (File.
610 "/home/r/proj/cortex/render/worm-vision/worm-view")))
611 BufferedImage!))
613 (set-gravity world Vector3f/ZERO))
615 (fn [world _ ]
616 (.setLocalTranslation me (.getLocation (.getCamera world)))
617 (vision-display
618 (map #(% world) vision)
619 (if record (File. "/home/r/proj/cortex/render/worm-vision")))
620 (fix-display world)))))
621 #+end_src
623 The world consists of the worm and a flat gray floor. I can shoot red,
624 green, blue and white cannonballs at the worm. The worm is initially
625 looking down at the floor, and there is no gravity. My perspective
626 (the Main View), the worm's perspective (Worm View) and the 4 sensor
627 channels that comprise the worm's eye are all saved frame-by-frame to
628 disk.
630 * Demonstration of Vision
631 #+begin_html
632 <div class="figure">
633 <video controls="controls" width="755">
634 <source src="../video/worm-vision.ogg" type="video/ogg"
635 preload="none" poster="../images/aurellem-1280x480.png" />
636 </video>
637 <p>Simulated Vision in a Virtual Environment</p>
638 </div>
639 #+end_html
641 ** Generate the Worm Video from Frames
642 #+name: magick2
643 #+begin_src clojure
644 (ns cortex.video.magick2
645 (:import java.io.File)
646 (:use clojure.contrib.shell-out))
648 (defn images [path]
649 (sort (rest (file-seq (File. path)))))
651 (def base "/home/r/proj/cortex/render/worm-vision/")
653 (defn pics [file]
654 (images (str base file)))
656 (defn combine-images []
657 (let [main-view (pics "main-view")
658 worm-view (pics "worm-view")
659 blue (pics "0")
660 green (pics "1")
661 red (pics "2")
662 gray (pics "3")
663 blender (let [b-pics (pics "blender")]
664 (concat b-pics (repeat 9001 (last b-pics))))
665 background (repeat 9001 (File. (str base "background.png")))
666 targets (map
667 #(File. (str base "out/" (format "%07d.png" %)))
668 (range 0 (count main-view)))]
669 (dorun
670 (pmap
671 (comp
672 (fn [[background main-view worm-view red green blue gray blender target]]
673 (println target)
674 (sh "convert"
675 background
676 main-view "-geometry" "+18+17" "-composite"
677 worm-view "-geometry" "+677+17" "-composite"
678 green "-geometry" "+685+430" "-composite"
679 red "-geometry" "+788+430" "-composite"
680 blue "-geometry" "+894+430" "-composite"
681 gray "-geometry" "+1000+430" "-composite"
682 blender "-geometry" "+0+0" "-composite"
683 target))
684 (fn [& args] (map #(.getCanonicalPath %) args)))
685 background main-view worm-view red green blue gray blender targets))))
686 #+end_src
688 #+begin_src sh :results silent
689 cd /home/r/proj/cortex/render/worm-vision
690 ffmpeg -r 25 -b 9001k -i out/%07d.png -vcodec libtheora worm-vision.ogg
691 #+end_src
693 * Headers
695 #+name: vision-header
696 #+begin_src clojure
697 (ns cortex.vision
698 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple
699 eyes from different positions to observe the same world, and pass
700 the observed data to any arbitray function. Automatically reads
701 eye-nodes from specially prepared blender files and instantiates
702 them in the world as actual eyes."
703 {:author "Robert McIntyre"}
704 (:use (cortex world sense util))
705 (:use clojure.contrib.def)
706 (:import com.jme3.post.SceneProcessor)
707 (:import (com.jme3.util BufferUtils Screenshots))
708 (:import java.nio.ByteBuffer)
709 (:import java.awt.image.BufferedImage)
710 (:import (com.jme3.renderer ViewPort Camera))
711 (:import (com.jme3.math ColorRGBA Vector3f Matrix3f))
712 (:import com.jme3.renderer.Renderer)
713 (:import com.jme3.app.Application)
714 (:import com.jme3.texture.FrameBuffer)
715 (:import (com.jme3.scene Node Spatial)))
716 #+end_src
718 #+name: test-header
719 #+begin_src clojure
720 (ns cortex.test.vision
721 (:use (cortex world sense util body vision))
722 (:use cortex.test.body)
723 (:import java.awt.image.BufferedImage)
724 (:import javax.swing.JPanel)
725 (:import javax.swing.SwingUtilities)
726 (:import java.awt.Dimension)
727 (:import javax.swing.JFrame)
728 (:import com.jme3.math.ColorRGBA)
729 (:import com.jme3.scene.Node)
730 (:import com.jme3.math.Vector3f)
731 (:import java.io.File)
732 (:import (com.aurellem.capture Capture RatchetTimer)))
733 #+end_src
735 * Onward!
736 - As a neat bonus, this idea behind simulated vision also enables one
737 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]].
738 - Now that we have vision, it's time to tackle [[./hearing.org][hearing]].
740 * Source Listing
741 - [[../src/cortex/vision.clj][cortex.vision]]
742 - [[../src/cortex/test/vision.clj][cortex.test.vision]]
743 - [[../src/cortex/video/magick2.clj][cortex.video.magick2]]
744 - [[../assets/Models/subtitles/worm-vision-subtitles.blend][worm-vision-subtitles.blend]]
745 #+html: <ul> <li> <a href="../org/sense.org">This org file</a> </li> </ul>
746 - [[http://hg.bortreb.com ][source-repository]]
750 * COMMENT Generate Source
751 #+begin_src clojure :tangle ../src/cortex/vision.clj
752 <<vision-header>>
753 <<pipeline-1>>
754 <<pipeline-2>>
755 <<retina>>
756 <<add-eye>>
757 <<sensitivity>>
758 <<eye-node>>
759 <<add-camera>>
760 <<kernel>>
761 <<main>>
762 <<display>>
763 #+end_src
765 #+begin_src clojure :tangle ../src/cortex/test/vision.clj
766 <<test-header>>
767 <<test-1>>
768 <<test-2>>
769 #+end_src
771 #+begin_src clojure :tangle ../src/cortex/video/magick2.clj
772 <<magick2>>
773 #+end_src