[localhost:~]$ cat * > /dev/ragfield

Friday, April 24, 2009

My first WebKit patch

Several years ago I spent a few hours implementing a feature in Mozilla, and then probably 10 times that amount of time dealing with the hassle of getting my code (which I was trying to give them for free) approved and into their sources. The incident left a bitter aftertaste.

Fast forward to present day. I was just reading through some WebKit sources and noticed a typo in one of the comments. So I decided to fix the typo and submit a patch to see how painful the process is for the competing project.

Index: JavaScriptCore/ChangeLog
===================================================================
--- JavaScriptCore/ChangeLog	(revision 42833)
+++ JavaScriptCore/ChangeLog	(working copy)
@@ -1,3 +1,9 @@
+2009-04-24  Rob Raguet-Schofield  
+
+        Reviewed by NOBODY (OOPS!).
+
+        * wtf/CurrentTime.h:
+
 2009-04-23  Mark Rowe  
 
         With great sadness and a heavy heart I switch us back from YARR to WREC in
Index: JavaScriptCore/wtf/CurrentTime.h
===================================================================
--- JavaScriptCore/wtf/CurrentTime.h	(revision 42833)
+++ JavaScriptCore/wtf/CurrentTime.h	(working copy)
@@ -36,7 +36,7 @@ namespace WTF {
 
     // Returns the current system (UTC) time in seconds, starting January 1, 1970.
     // Precision varies depending on a platform but usually is as good or better 
-    // then a millisecond.
+    // than a millisecond.
     double currentTime();
 
 } // namespace WTF

Picky? Sure. But why not?

Update: patch approved after 5 hours.

Monday, April 20, 2009

Video on Aiptek PocketCinema

Pocket projector

The documentation for the Aiptek PocketCinema pico-projector claims to support the following video formats:

- MJPEG AVI (recommended)
- MPEG-4 ASF

So how do I convert video into one of these formats on a Mac with QuickTime Pro?

Test: Use QuickTime Player to Export Movie to MPEG-4.
Result: FAIL. PocketCinema hangs when attempting to preview video and needs to be reset by removing battery.

Test: Use QuickTime Player to Export Movie to AVI. MJPEG is not support. Try uncompressed, BMP, and Cinepak.
Result: FAIL. PocketCinema recognizes AVI file, does not hang, but also does not play any of the files (uncompressed, BMP, or Cinepak).

Test: Use QuickTime Player to Export Movie to QuickTime Movie with Motion JPEG codec.
Result: FAIL. PocketCinema does not recognize .mov file.

Test: Use QuickTime Player to Export to MPEG-4, then use Flv Crunch to convert to MPEG-4 again.
Result: Success! Apparently the projector has a problem with the MPEG4 generated by QuickTime, but does not have a problem with the MPEG4 generated by ffmpeg.

Behind the scenes Flv Crunch is just using the ffmpeg command line tool. The following command did the trick for me:

ffmpeg -i source.mp4 dest.mp4

Friday, April 17, 2009

Mapping GPS Data

I have written a blog entry titled Mapping GPS Data that has just been posted to the Wolfram Research blog.

Thursday, April 16, 2009

Twittering with Mathematica

A couple weeks ago James Rohal wrote about "Updating Twitter from Mathematica." His technique is interesting, but it has a number of drawbacks. It requires special PHP code to run on his server. The user name and password appear to be hardcoded. The only functionality it provides is updating the status.
Twitter has a REST API which can be called directly from a number of languages, including Mathematica. Several months ago I started, but didn't finish, a Mathematica package to wrap the Twitter API. After I read James' post I took the opportunity to clean it up and fill in the missing pieces.
Let's take a look a few of the things this Twitter package can do. Begin by loading the package in the usual manner.
<<Twitter`
TwitterSession
Next we'll open a session. This will bring up a login dialog.
session = TwitterSessionOpen[]
TwitterSession[<ragfield>]
TwitterSessionAuthorizedQ[session]
True
TwitterSessionScreenName[session]
ragfield
TwitterSessionClose[session]
It's also possible to specify the user name and/or login programatically to avoid the manual step. However, doing so will require a hardcoded password.
session = TwitterSessionOpen["User"->"ragfield"]
TwitterSession[<ragfield>]
TwitterUser
The next object is a user. The session is associated with the user who logged in.
user = TwitterSessionUser[session]
TwitterUser[<ragfield>]
We can query the user for several attributes.
TwitterUserID[user]
12920162
TwitterUserName[user]
Rob Raguet-Schofield
TwitterUserScreenName[user]
ragfield
TwitterUserLocation[user]
Urbana, Illinois
TwitterUserHomePage[user]
http://rob.ragfield.com
TwitterUserDescription[user]
My name is Rob. I write software, ride bicycles, and occasionally write software while riding bicycles.
TwitterUserProfilePage[user]
http://twitter.com/ragfield
TwitterUserInfo[user]
Name Rob Raguet-Schofield
Location Urbana, Illinois
Web http://rob.ragfield.com
Bio My name is Rob. I write software, ride bicycles, and occasionally write software while riding bicycles.
And all this information can be displayed graphically with hyperlinks, tooltips, etc.
TwitterUserUI[user]
2009-04-16-TwitteringWithMathematica1
TwitterStatus
The final, and perhaps most important, object is the status. A status can be accessed directly by its unique ID, or it can be retrieved from a timeline (see below).
status = TwitterStatus[671394002]
TwitterStatus[<ragfield: shopping for underpants>]
We can also query the status message for several attributes.
TwitterStatusID[status]
671394002
TwitterStatusUser[status]
TwitterUser[<ragfield>]
TwitterStatusDate[status]
Sat 2 Feb 2008 19:02:02
TwitterStatusText[status]
shopping for underpants
TwitterStatusPage[status]
http://twitter.com/ragfield/statuses/671394002
TwitterStatusSource[status]
TwitterStatusUI[status]
2009-04-16-TwitteringWithMathematica2
Setting the status
We can set our status.
status = TwitterSetStatus[session, "Tweeting from Mathematica"]
TwitterStatus[<ragfield: Tweeting from Mathematica>]
TwitterStatusUI[status]
2009-04-16-TwitteringWithMathematica9
We can also delete any of our tweets if we decided we don't like them.
status = TwitterSetStatus[session, "I am dumb"]
TwitterStatus[<ragfield: I am dumb>]
id = TwitterStatusID[status]
1537789527
TwitterDeleteStatus[session, status]
TwitterStatus[<ragfield: I am dumb>]
TwitterStatus[id]
FetchURL::"conopen": "\!\(\*StyleBox[\"\\\"The connection to URL \\\"\", \"MT\"]\)\!\(\*StyleBox[\!\(\"http://twitter.com/statuses/show/1537789527.xml\"\), \"MT\"]\)\!\(\*StyleBox[\"\\\" cannot be opened. If the URL is correct, you might need to configure your firewall program, or you might need to set a proxy in the Internet connectivity tab of the Preferences dialog (or by calling SetInternetProxy). For HTTPS connections, you might need to inspect the authenticity of the server's SSL certificate and choose to accept it.\\\"\", \"MT\"]\)"
$Failed
The tweet has been deleted, so any attempt to download it again fails.
Timelines
A list of status messages can be retrieved from a specific user.
Take[TwitterUserTimeline[user], 4]
{TwitterStatus[<ragfield: Tweeting from Mathematica>], TwitterStatus[<ragfield: testing...>], TwitterStatus[<ragfield: 4 mile recovery/photo walk>], TwitterStatus[<ragfield: Marge, if anyone asks, you require 24 hour nursing care, Lisa's a clergyman, Maggie is seven people, and Bart was wounded in Vietnam>]}
Column[TwitterStatusUI/@%]
2009-04-16-TwitteringWithMathematica3
And more...
Take[TwitterUserTimeline[user, "Page"->2], 4]
{TwitterStatus[<ragfield: off to run the marathon. wish me luck.>], TwitterStatus[<ragfield: @esmithrunner good luck to you and your family as well>], TwitterStatus[<ragfield: It's just another race>], TwitterStatus[<ragfield: Little Miss C (kindergarten) after successfully adding 95+3, "I probably know just about all maths.">]}
Column[TwitterStatusUI/@%]
2009-04-16-TwitteringWithMathematica4
And even other users' timelines...
Take[TwitterUserTimeline["lancearmstrong"], 3]
{TwitterStatus[<lancearmstrong: The Armstrong's in the snow! Just went swimming at the aspen rec center.>], TwitterStatus[<lancearmstrong: http://twitpic.com/3ffo8>], TwitterStatus[<lancearmstrong: This is AMAZING!!!! http://tinyurl.com/d7d6ex>]}
Column[TwitterStatusUI/@%]
2009-04-16-TwitteringWithMathematica5
And the friends timeline...
Take[TwitterFriendsTimeline[session], 3]
{TwitterStatus[<cabel: Enjoying the absurdity of Remi Gaillard's real-life Pac-Man. http://tinyurl.com/dm9mor>], TwitterStatus[<danielpunkass: I suggest opt-in per tweet. I get a message from @twitshirt: "Lucky you! Somebody wants to make you famous!" Per-tweet licensing agreement.>], TwitterStatus[<danielpunkass: Feel bad for @twitshirt guys, but I agree it's a smarmy premise ©-wise. Friend in IRC suggested opt-in + @messages inviting opt-in. Solved.>]}
Column[TwitterStatusUI/@%]
2009-04-16-TwitteringWithMathematica6
And any replies to the user...
Take[TwitterReplies[session], 3]
{TwitterStatus[<spoonshake: @ragfield Ah, priceless Simpsons quotes.>], TwitterStatus[<gutzville: @ragfield I would just be happy if they put the curtis road exit in that has been open for more than a year>], TwitterStatus[<spoonshake: @ragfield Priceless headline.>]}
Column[TwitterStatusUI/@%]
2009-04-16-TwitteringWithMathematica7
And the public timeline...
Take[TwitterPublicTimeline[], 3]
{TwitterStatus[<lastp1acechamps: @mchowes yah my friend seriously lives right across the street from the newbury store and he couldn't get up for me.>], TwitterStatus[<exult: Some things alarm me more than other things>], TwitterStatus[<HambyDavid: Phone is dying. Need car charger>]}
Column[TwitterStatusUI/@ %]
2009-04-16-TwitteringWithMathematica8
Friends and Followers
Finally we can get a list of our friends and followers.
Take[friends = TwitterFriends[session], 5]
{TwitterUser[<bmeyaard>], TwitterUser[<quickKarl2>], TwitterUser[<_Becca_Knight>], TwitterUser[<bikefriday>], TwitterUser[<aimeekandrac>]}
Take[followers = TwitterFollowers[session], 5]
{TwitterUser[<bmeyaard>], TwitterUser[<quickKarl2>], TwitterUser[<NaturalComedy>], TwitterUser[<akw279>], TwitterUser[<Modells_Coupons>]}
And since this is Mathematica, we can do interesting data explorations.
friendIDs = TwitterUserID/@friends;
followerIDs = TwitterUserID/@followers;
union = Intersection[friendIDs, followerIDs];
55% of the people I'm following also follow me.
N[Length[union] / Length[friendIDs]]
0.55`
42% of the people who follow me are people whom I also follow.
N[Length[union] / Length[followerIDs]]
0.41509433962264153`
TwitterSessionClose[session]
Downloads
Download HTTP.m (required by Twitter.m).

Update: I have written a very similar entry for my company's blog.

Monday, April 6, 2009

Some useful bookmarklets

In case you don't know, bookmarklets are bookmarks which run JavaScript code rather than open a specific web page. Here are a collection of bookmarklets I use on a semi-regular basis to speed up searching of frequently used web sites. Bookmarklets can do other things, since they run arbitrary JavaScript code.

To install them click on the hyperlink and drag it to your bookmarks bar (or maybe right/control-click on the link and choose a menu item to add to bookmarks). They may not work in all browsers, but these all work in Safari.

You can use them in one of two ways. First, if you just choose the bookmark it will pop up a dialog asking for the query text, then search the corresponding site for the text you enter. Second, you can select a piece of text on the current web page, then when you choose the bookmarklet it will automatically search the corresponding site for the selected text.

Wikipedia

javascript:x=''+getSelection();if(!x.length){x=prompt('Wikipedia:','')}if(x&&x.length){location.href='http://en.wikipedia.org/w/wiki.phtml?search='+escape(x)}

Dictionary

javascript:x=''+getSelection();if(!x.length){x=prompt('Dictionary:','')}if(x&&x.length){location.href='http:/dictionary.reference.com/search?q='+escape(x)}

Thesaurus

javascript:x=''+getSelection();if(!x.length){x=prompt('Thesaurus:','')}if(x&&x.length){location.href='http:/thesaurus.reference.com/search?q='+escape(x)}

Google Images

javascript:x=''+getSelection();if(!x.length){x=prompt('Google%20Images:','')}if(x&&x.length){location.href='http://images.google.com/images?hl=en&q='+escape(x)}

Google News

javascript:x=''+getSelection();if(!x.length){x=prompt('Google%20News:','')}if(x&&x.length){location.href='http:/news.google.com/news?q='+escape(x)}

Google Maps

javascript:x=''+getSelection();if(!x.length){x=prompt('Google%20Maps:','')}if(x&&x.length){location.href='http://maps.google.com/?q='+escape(x)}

IMDB

javascript:x=''+getSelection();if(!x.length){x=prompt('IMDB:','')}if(x&&x.length){location.href='http:/imdb.com/Find?for='+escape(x)}

Amazon

javascript:x=''+getSelection();if(!x.length){x=prompt('Amazon:','')}if(x&&x.length){location.href='http://www.amazon.com/exec/obidos/external-search/?keyword='+escape(x)}

eBay

javascript:x=''+getSelection();if(!x.length){x=prompt('eBay:','')}if(x&&x.length){location.href='http://search.ebay.com/search/search.dll?query='+escape(x)}

Font outlines and 3D text

When importing PDF files Mathematica 6-7 converts glyphs to polygons. We can utilize this fact to get outlines of these glyphs which can be further processed as desired. Here I will show how to make arbitrary font/glyph combinations into 3D objects.
NOTE: this code depends on a specific behavior of Mathematica's PDF import feature. This code may break if future versions of Mathematica change the way glyphs from PDF files are imported.
As I mentioned previously, Mathematica's PDF import simulates polygons/paths with holes by connecting the different segments/contours with 0-width lines. There is a break in a contour where these 0-width lines occur. This first function finds the points which comprise the 0-width lines.
FindContourBreaks[pts_List] := Module[
    {i, lines, breaks = {}},
    lines = {pts[[#[[1]]]], pts[[#[[2]]]]}& /@
        Partition[RotateLeft[Flatten[{#, #}& /@
            Range[Length[pts]], 1]], 2];
    For[i = 1, i <= Length[lines], i++,
        If[MemberQ[lines, {lines[[i, 2]], lines[[i, 1]]}],
            AppendTo[breaks, i]
        ];
    ];
    breaks
];
This function returns a list of the individual polygon contours.
FindContours[pts_List] := Module[
    {breaks, ranges},
    breaks = FindContourBreaks[pts];
    ranges = Partition[RotateLeft[Join[{1, 1},
        Flatten[{#, # + 1}& /@ breaks]]], 2];
    ranges = Drop[ranges, - 1];
    DeleteCases[Take[pts, #]& /@ ranges, x_ /; Length[x] < 3]
];
Now that we have a list of the contours we can process them further. Here I will convert them from a 2D polygon to a object. The top of the object will be the 2D polygon living in 3D space. The bottom will be the reverse of the top (to get the lighting correct) offset a certain depth below the top. Finally, I will add a sequence of rectangles orthogonal to the top and bottom to form the sides of the object to close it in.
ThreeDContour[pts_List, depth_] := Module[
    {topPts, botPts, sideRects, sidePts, sideNormals},
    topPts = {#[[1]], #[[2]], 0}& /@ pts;
    botPts = (# + {0., 0., - depth})& /@ topPts;
    sideRects = Partition[RotateLeft[Flatten[{#, #}& /@
        Range[Length[topPts]], 1]], 2];
    sidePts = {
        topPts[[#[[1]]]], botPts[[#[[1]]]],
        botPts[[#[[2]]]], topPts[[#[[2]]]]
    }& /@ sideRects;
    Polygon /@ sidePts
];
ThreeDContours[pts_List, depth_] := Module[
    {contours},
    contours = FindContours[pts];
    ThreeDContour[#, depth]& /@ contours
];
ThreeDPolygon[Polygon[pts_List], depth_] := Module[
    {topPts, botPts, sidePolys},
    topPts = {#[[1]], #[[2]], 0}& /@ pts;
    botPts = Reverse[(# + {0., 0., - depth})& /@ topPts];
    sidePolys = ThreeDContours[pts, depth];
    {Polygon[topPts], Polygon[botPts], EdgeForm[], sidePolys}
];
This function converts a 2D graphics scene to a 3D graphics scene where all the 2D polygons are made into 3D objects with the given depth.
ThreeDGraphics[gfx_Graphics, depth_] :=
    gfx /. Polygon[pts_List] :> ThreeDPolygon[Polygon[pts], depth] /.
        (PlotRange -> _) :> PlotRange -> All /.
            Graphics[gfx2D___] :> Graphics3D[gfx2D];
These final two functions will take an arbitrary piece of text in an arbitrary font, export it to PDF, import the PDF back into Mathematica (to convert the glyphs to outlines), and make them 3D.
ThreeDText[str_String, family_String:"Times", depth_:10] :=
    ThreeDGraphics[TwoDText[str, family], depth];
TwoDText[str_String, family_String:"Times"] :=
    First[ImportString[ExportString[Cell[str, FontSize -> 100,
        FontFamily -> family], "PDF"], "PDF"]];
Finally, we can see the results.
ThreeDText["Hello\nWorld"]
Hellow world in 3D
We could also build an interface that allows us to choose which glyph in which font we want.
Manipulate[
    Show[
        ThreeDText[FromCharacterCode[character], font, depth],
        ImageSize->{Automatic, 300}
    ],
    {{font, "Times"},
        Map[First, FE`Evaluate[FEPrivate`GetPopupList["MenuListFonts"]]]},
    {{character, 65}, 33, 127, 1},
    {{depth, 10}, 0, 100}
]
Manipulate 3D text

Download Mathematica Notebook

Sunday, April 5, 2009

Horrible Visual Studio 2008 Standard Edition installer bug

I spend nearly all my time working on a Mac. Given the nature of cross-platform development I do sometimes require use of a Windows machine. Not too long ago I was upgrading to Visual Studio 2008 when I found a horrible bug in the installer.

Visual Studio 2008 Installer

I didn't want any Visual Basic or C# crap littering my machine. So I disabled those options, leaving only the C++ tools to be installed. Installation completed and I fired up the compiler only to get the following error message:

Compiling...
Project : error PRJ0003 : Error spawning 'cl.exe'.

Uh.

That's not supposed to happen.

Let's take a closer look:

C:\Program Files\Microsoft Visual Studio 9.0\VC\bin>dir
 Volume in drive C has no label.
 Volume Serial Number is 104F-0033

 Directory of C:\Program Files\Microsoft Visual Studio 9.0\VC\bin

04/05/2009  09:48 AM    <DIR>          .
04/05/2009  09:48 AM    <DIR>          ..
04/05/2009  09:46 AM    <DIR>          1033
04/05/2009  09:48 AM    <DIR>          amd64
11/08/2007  08:19 AM           165,376 atlprov.dll
11/08/2007  08:19 AM            77,312 bscmake.exe
11/08/2007  08:19 AM           670,192 c1.dll
11/08/2007  08:19 AM         2,334,200 c1xx.dll
11/08/2007  08:19 AM            33,784 cvtres.exe
11/08/2007  08:19 AM            17,920 dumpbin.exe
11/08/2007  08:19 AM            17,920 editbin.exe
11/08/2007  08:19 AM            17,912 lib.exe
11/08/2007  08:19 AM           790,008 link.exe
06/05/2006  06:02 PM               268 link.exe.config
11/08/2007  08:19 AM           358,384 ml.exe
11/08/2007  08:19 AM            94,200 nmake.exe
11/08/2007  08:19 AM            20,480 undname.exe
03/07/2007  03:44 PM                31 vcvars32.bat
04/05/2009  09:48 AM    <DIR>          x86_amd64
11/08/2007  08:19 AM            40,448 xdcmake.exe
06/05/2006  06:02 PM               268 xdcmake.exe.config
              16 File(s)      4,638,703 bytes
               5 Dir(s)  12,399,824,896 bytes free

So, no C++ compiler with C++ installation. Perfect.

Visual Studio 2008 Installer

On the suggestion of a coworker I also installed the C# tools. Low and behold the C# tools included the C++ compiler, and my problems went away:

C:\Program Files\Microsoft Visual Studio 9.0\VC\bin>dir
 Volume in drive C has no label.
 Volume Serial Number is 104F-0033

 Directory of C:\Program Files\Microsoft Visual Studio 9.0\VC\bin

04/05/2009  10:43 AM    <DIR>          .
04/05/2009  10:43 AM    <DIR>          ..
04/05/2009  09:46 AM    <DIR>          1033
04/05/2009  09:48 AM    <DIR>          amd64
11/08/2007  08:19 AM           165,376 atlprov.dll
11/08/2007  08:19 AM            77,312 bscmake.exe
11/08/2007  08:19 AM           670,192 c1.dll
11/08/2007  08:19 AM         2,334,200 c1xx.dll
11/08/2007  08:19 AM         2,356,720 c2.dll
11/08/2007  08:19 AM           125,936 cl.exe
06/05/2006  06:02 PM               268 cl.exe.config
11/08/2007  08:19 AM            33,784 cvtres.exe
11/08/2007  08:19 AM            17,920 dumpbin.exe
11/08/2007  08:19 AM            17,920 editbin.exe
11/08/2007  08:19 AM            17,912 lib.exe
11/08/2007  08:19 AM           790,008 link.exe
06/05/2006  06:02 PM               268 link.exe.config
11/08/2007  08:19 AM           358,384 ml.exe
11/08/2007  08:19 AM            94,200 nmake.exe
11/08/2007  08:19 AM            20,480 undname.exe
03/07/2007  03:44 PM                31 vcvars32.bat
04/05/2009  09:48 AM    <DIR>          x86_amd64
11/08/2007  08:19 AM            40,448 xdcmake.exe
06/05/2006  06:02 PM               268 xdcmake.exe.config
              19 File(s)      7,121,627 bytes
               5 Dir(s)  12,437,422,080 bytes free

I find it fairly shocking that this configuration was never tested before the bits left Microsoft.

Friday, April 3, 2009

Video with telemetry data overlay

At my cycling team's training camp last weekend I used my Flip video camera to record video of some of the hill descents. Several teammates thought it would be neet to see the video with telemetry data (such as speed) overlayed on top of it. Since I had my GPS recording at the time it was a straightforward matter to create such a video. Here's how I did it.
In preparation I exported my GPS data to a GPX file, which Mathematica can import. Next, I used QuickTime Player Pro to convert the movie into a sequence of PNG images, which Mathematica can import.
Set the working directory to this notebook's directory for convenience.
SetDirectory[NotebookDirectory[]];
Import the data from the GPX file.
xml = Import["Descent.gpx", "XML"];
Pull out all the "trkpt" elements.
trkpts = Cases[xml, XMLElement["trkpt", _, _], Infinity];
Length[trkpts]
204
First[trkpts]
XMLElement[trkpt, {lat->37.6150774, lon->-89.2330897}, {XMLElement[ele, {}, {222.6824951}], XMLElement[time, {}, {2009-03-29 16:09:18}]}]
Extract the latitude/longitude/elevation/time from each trackpoint.
latitudes = ToExpression["lat" /. #[[2]]]& /@ trkpts;
longitudes = ToExpression["lon" /. #[[2]]]& /@ trkpts;
elevations = Cases[trkpts, XMLElement[
    "ele", _, {e_String}] :> ToExpression[e], Infinity];
dates = Cases[trkpts, XMLElement[
    "time", _, {t_String}] :> DateList[t], Infinity];
pts = Transpose[{latitudes, longitudes, elevations}];
Compute the distance between each successive trackpoint.
distances = GeoDistance[
    GeoPosition[#[[1]], "WGS84Original"],
    GeoPosition[#[[2]], "WGS84Original"]
]& /@ Partition[pts, 2, 1];
Compute the time interval between each successive trackpoing.
times = First[DateDifference[#[[1]], #[[2]], "Second"]]& /@
    Partition[dates, 2, 1];
Speed = distance/time. Convert from Meter/Second to Mile/Hour.
<<Units`
speeds = (Convert[# Meter/Second, Mile/Hour] Hour/Mile)& /@
    distances/times;
Since there are more video frames than GPS trackpoints we're going to want to know what the speed is between trackpoints. To do this we'll create an InterpolatingFunction. At any time between two trackpoints we'll estimate the speed to be a linear interpolation between the speed at those two points.
absoluteTimes = Drop[AbsoluteTime /@ dates, 1];
interp = Interpolation[Transpose[{absoluteTimes, speeds}]];
For each frame of the video we will import the PNG file, compute the speed for that frame of video, draw the speed on top of the video, then export the new frame.
processFrame[i_Integer] := Module[
    {label, istr, img, g},
    label = ToString@NumberForm[
        interp[First[absoluteTimes]+i*1/29.97], {Infinity, 2}];
    label = StringJoin[label, " mph"];
    istr = IntegerString[i, 10, 4];
    img = Import[StringJoin["Descent", istr, ".png"]];
    g = Show[
        Rasterize[img],
        Graphics[Style[{
            Text[label, Scaled[{0.5, 0.1}], {0, 0}],
            Style[Text[label,
                Offset[{-4, 4}, Scaled[{0.5, 0.1}]], {0, 0}]
            , FontColor -> White]
        }, FontSize -> 72]]
    ];
    Export[StringJoin["DescentProcessed", istr, ".png"], g];
    g
];
processFrame[1]
DescentProcessed0001
Call the processFrame[] function for each frame (and wait a potentially long time).
With[{start = 1, stop = 9606}, Monitor[
    Do[processFrame[i], {i, start, stop, 1}],
    ProgressIndicator[Dynamic[(i - start)/(stop - start)]]
]]
Once all the frames have been exported we can recombine them back into a single movie using QuickTime Player Pro.

Download Mathematica Notebook

Thursday, April 2, 2009

Xcode project templates for MathLink applications

Setting up an Xcode project to build MathLink applications can be confusing and tedius. Therefore I have created Xcode project templates for MathLink applications.

Xcode project template installation

Download the templates here. Install the "MathLink" directory into "/Library/Application Support/Developer/Shared/Xcode/Project Templates/". After installation, when you create a new project in Xcode there will be a User Templates section that contains a MathLink subsection. Click on MathLink and find the two project templates.

Xcode project templates

The first project template is called C MathLink Program. Use it for the very simplest of applications where you want to implement a function (or a few functions) in C/C++ and call it directly from Mathematica. The project will create a single executable (.exe extension, yes, .exe, yes, on Mac).

Build the target and begin using it in Mathematica with the Install command.

Note: if Mathematica is installed in a different location than /Applications/Mathematica.app you will need to adjust the value of the user defined MATHEMATICA build setting in the target info window.

l = Install["~/Desktop/MyTest/build/Debug/MyTest.exe"]
LinkObject[/Users/schofield/Desktop/MyTest/build/Debug/MyTest.exe, 153, 9]
LinkPatterns[l]
{MyTest[arg1_Integer, arg2_Real, arg3_String]}
MyTest[23, 4.5, "67"]
23

The second project template is called C MathLink Package. Use it for more complicated applications that require both C/C++ code and supporting Mathematica code. The support code goes into init.m.