Hi! I'm Nicolas and I'm interested in information visualization, JavaScript and web standards.
I currently work as a Data Visualization Scientist at Twitter. I wrote PhiloGL, the JavaScript InfoVis Toolkit and V8-GL.
Using OCaml to visualize Radiohead's HoC music video (part 1)
Posted in:
ocaml
, visualization
, opengl
So I was looking for some excuse to learn OCaml + OpenGL, and I run into Radiohead's House of Cards video dataset hosted at google code.
The dataset is made of many CSV files, one for each frame of the HoC video.
The data is also shipped with an application that uses Processing to create an image for each frame of the video.
I decided to do the same program in OCaml + OpenGL: for each CSV file, the program loads it, renders it in OpenGL, and then saves that rendered data into a jpg (or bmp) image.
You can merge the generated image frames with the sample mp3 provided at google code, by using ffmpeg:
Anyway, the result is quite interesting, and it gives us a good ground to build better visualizations:
(This youtube video quality is pretty lame, I'd recommend you to right click here and save link as...).
This post is going to be about the making of this simple application.
Further posts on this "project" will cover advanced camera movement and particle transformations.
The Code
This app was made in one single file, but it contains two important parts:
A data object containing information about the location of the CSV and generated image files, along with some methods to load CSV files and save OpenGL rendered pictures into image files (bmp and jpeg formats).
This object uses camlimages for saving images in different formats, and the OpenGL/GLUT bindings provided by lablGL.
(* Loads csv frames and saves the rendered OpenGL image *)letdata=object(self)valpath_to_file="path_to_folder_containing_csv_files"valpath_to_image_file="path_to_folder_that_will_contain_imgs"valmutablecurrent_frame=1valtotal_frames=2101valtime_interval=33methodget_time_interval=time_intervalmethodload_filefilename=letchannel=open_in(path_to_file^filename)inletans=ref[]intrywhiletruedoletline=input_linechannelinletsp=split(regexp",")(subline0(pred(lengthline)))inmatchList.mapfloat_of_stringspwith|[x;y;z;d]->ans:=DVertex(x,y,z,d)::!ans|_->raise(Invalid_argument"not a depth vertex")done;!answithEnd_of_file|Invalid_argument_->close_in_noerrchannel;!ansmethodsave_image=letimg_rgb=newOImages.rgb24600400inletpixels=GlPix.read~x:0~y:0~width:600~height:400~format:`rgb~kind:`ubyteinletpraw=GlPix.to_rawpixelsinletraw=Raw.gets~pos:0~len:(Raw.byte_sizepraw)prawinletw=GlPix.widthpixelsinleth=GlPix.heightpixelsinfori=0topred(w*h)doletcolor_rgb={r=raw.(i*3+2);g=raw.(i*3+1);b=raw.(i*3+0)}inimg_rgb#set(imodw)(i/w)color_rgb;done;img_rgb#save(path_to_image_file^"img"^(string_of_intcurrent_frame)^".jpg")None[]methodnext_frame=current_frame<-(current_frame+1)modtotal_frames;ifcurrent_frame=0thencurrent_frame<-total_frames;self#load_file((string_of_intcurrent_frame)^".csv")end
This object is included in the main.ml file, which is the main entry point for the OpenGL application.
This file defines functions for initializing and binding events to the main openGL app. You'll find this code familiar if you know some OpenGL.
openStropenStringopenColoropenVertexType(* Initializes openGL scene components*)letinitwidthheight=GlDraw.shade_model`smooth;GlClear.color(0.0,0.0,0.0);GlClear.depth1.0;GlClear.clear[`color;`depth];Gl.enable`depth_test;GlFunc.depth_func`lequal;GlMisc.hint`perspective_correction`nicest(* Draws the image*)letdraw()=GlClear.clear[`color;`depth];GlMat.load_identity();GlMat.translate3(-150.0,-150.0,-400.0);GlDraw.begins`points;List.iter(fun(DVertex(x,y,z,d))->letcolor=d/.255.inGlDraw.color(color,color,color);GlDraw.vertex~x:x~y:y~z:z())data#next_frame;GlDraw.ends();Glut.swapBuffers();data#save_image(* Handle window resize *)letreshape_cb~w~h=letratio=(float_of_intw)/.(float_of_inth)inGlDraw.viewport00wh;GlMat.mode`projection;GlMat.load_identity();GluMat.perspective45.0ratio(0.1,1300.0);GlMat.mode`modelview;GlMat.load_identity()(* Handle keyboard events *)letkeyboard_cb~key~x~y=matchkeywith|27(* ESC *)->exit0|_->()(* A timer function setted to draw a new frame each time_interval ms *)letrectimervalue=Glut.postRedisplay();Glut.timerFunc~ms:data#get_time_interval~cb:(fun~value:x->(timerx))~value:value(* Main program function*)letmain()=letwidth=640andheight=480inignore(Glut.initSys.argv);Glut.initDisplayMode~alpha:true~depth:true~double_buffer:true();Glut.initWindowSizewidthheight;ignore(Glut.createWindow"Radiohead HoC");Glut.displayFuncdraw;Glut.keyboardFunckeyboard_cb;Glut.reshapeFuncreshape_cb;Glut.timerFunc~ms:data#get_time_interval~cb:(fun~value:x->(timerx))~value:1;initwidthheight;Glut.mainLoop()let_=main()
You can download the source here.
You can compile the files with the bytecode compiler:
.
Just remember that you have to install OCaml + lablGL + camlimages to be able to use this.
Any comment about the code will be well appreciated, since I'm an OCaml beginner :) .