Showing posts with label R. Show all posts
Showing posts with label R. Show all posts

Wednesday, November 4, 2009

Fascinating Captain...

I recently learned that Snow Leopard includes a (public!) framework called IOSurface that allows for the sharing of basically OpenGL textures between processes. That neatly solves the problem with having your graphics device running in a separate process for the GUI. Previously, I've shipped bitmaps around between the GUI and the backing process (which is headless) but this could definitely be a performance improvement both in update speed and in memory. I'll have to look into it.

I suspect even RGL could be made to operate in this mode though you'd need some help from the package since it expects to run an event loop for handling mouse interaction with the scene graph.

Monday, September 7, 2009

Aw, nuts.

Upgraded to Snow Leopard and so it is time once again to make some changes to my personal R GUI--if for no other reason than to test cool stuff like Blocks and Grand Central Dispatch. It also looks like there's some interesting developments in Pasteboards and a few other under the hood places.

First off are Blocks. They look like function pointers but they definitely aren't. They're actually Objective-C classes that you can treat like functions. Very Smalltalk, complete with similar tricks to stuff their data onto the stack you explicitly move them to the heap (they're objects so you can copy them).

Unfortunately, they are not interchangeable with C function pointers so you can't just use them as callback functions. A shame really, since right now I have a libffi-based binding between the R callback functions (ReadConsole, WriteConsole, etc) to thunk to Objective-C methods. All hope is not lost, the R binding is not so complicated so writing a Block compatible version shouldn't be too hard.

Tuesday, March 25, 2008

Excellent

The feature freeze for R 2.7.0 is in place with an expected April 22nd release. I've noticed some of the locale stuff has changed recently (LC_CTYPE and the like) so I'll need to fix those warning messages. Right now I'm setting up the IKImageBrowserView-backed graphics device interface for the GUI, though the built-in quartz device has been serving me well so far. There are a couple of reasons for the view set up, which uses bitmaps to represent the graphics device on the backend. After the page is completed, the bitmap is converted to a more efficient representation (e.g. PNG) for temporary storage and the display list recorded into the device. We'll also continue to cheat and provide a Cover Flow mode (no, I don't care that it's a private API).

Another interesting feature is that, unlike the current Mac GUI, the graphics device is considered a View on the same Model (the interpreter). We'll see how it plays out, but that might help with some of the fancy footwork usually required when dealing with the NSDocumentController.

Tuesday, March 11, 2008

A little FFI goes a long way

As my two faithful readers have surely noted, one of my little projects is a Leopard-specific GUI for R that tries to leverage as many of the new features as possible. As it happens, one of those features is Garbage Collection, which I can't use because of the way the R framework is built. Perhaps I'll be able to use Simon's method for building self-contained R applications with the framework built in the appropriate way.

What I'd really like to talk about though is the fact that having PyObjC and whatever the Ruby bridge is called means that I can be positive that a copy of libffi is available on every Leopard system. This let me finally build something I've wanted for a long time: an elegant C<->Cocoa thunk for the R function pointers. To wit:

ptr_R_WriteConsoleEx = ffi_bind(self,@selector(writeConsole:length:type:),&ffi_type_void,
4,&ffi_type_pointer,&ffi_type_sint,&ffi_type_sint);


So, how do we do it? Well, first we need a little bit of context to keep a binding between a function pointer and a specific object/method combination. We also keep the types of arguments around as well as the return type. If we were doing a complete libffi bridge we would introspect this from the class at runtime rather than the static method I use here. In the case of R this would be overkill since the R function pointers are very much fixed entities. In any case, here's the little structure I use:

typedef struct {
id obj;
SEL sel;
IMP fn;
int nargs;
ffi_type *retval;
ffi_cif *cif;
ffi_type *types[0];
} ffi_call_struct;


Next, we need to write a little function to use as a closure and move the C function call to the ObjC function call. All libffi closure handlers look have the same function declaration:

void handler(ffi_cif *cif,void *retval,void *args[],void *user_data) {

First we build a place to hold our arguments

int i;
ffi_call_struct *call = (ffi_call_struct*)user_data;
void **values = (void**)malloc(sizeof(void*)*(call->nargs+2));

and simply transfer the arguments accross, making sure that the first two are the target object and target selector (Obj-C methods have two implicit arguments):

for(i=0;inargs;i++) { values[2+i] = args[i]; }
values[0] = &(call->obj);
values[1] = &(call->sel);

Then all we do is use ffi_call to make a call. Note that we use the NSAutoreleasePool method, which wouldn't be necessary if we could enable garbage collection:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ffi_call(call->cif,(void (*)(void))call->fn,retval,values);
[pool release];
free(values);
}


Next we need a little function to build an appropriate closure function to use as the R function pointer:

void *ffi_bind(id obj,SEL selector,ffi_type *retval,int nargs,...) {
int i;
ffi_cif *cif;
ffi_closure *closure;
va_list ap;


We use vargs so that we can define the argument lists inline. First, we allocate our context structure and transfer the argument types

//Allocate the structure and copy the call information into the structure.
ffi_call_struct *call = (ffi_call_struct*)malloc(sizeof(ffi_call_struct)+(nargs+2)*sizeof(ffi_type*));
call->obj = obj;
call->sel = selector;
call->fn = [obj methodForSelector:selector];
call->retval = retval;
call->nargs = nargs;

call->cif = (ffi_cif*)malloc(sizeof(ffi_cif));
cif = (ffi_cif*)malloc(sizeof(ffi_cif));

call->types[0] = &ffi_type_pointer;
call->types[1] = &ffi_type_pointer;
va_start(ap,nargs);
for(i=0;itypes[2+i] = va_arg(ap,ffi_type*);


Next we define the two ffi_cifs. The first one is the ffi_cif of the Obj-C method call and the second is the one for the function pointer we're trying to create. Finally, we create the closure and return it

ffi_prep_cif(call->cif,FFI_DEFAULT_ABI,nargs+2,call->retval,call->types); //The CIF for the ObjC Method
ffi_prep_cif(cif,FFI_DEFAULT_ABI,nargs,call->retval,&(call->types[2])); //The CIF for the function call
closure = (ffi_closure*)malloc(sizeof(ffi_closure));
ffi_prep_closure(closure,cif,handler,call);
return closure;
}


and that's pretty much all there is to it. There are two minor caveats, the first is that anything that employs method swizzling on this class is likely to fail since we cache the function pointer at bind time. Fortunately, swizzling is relatively rare these days so it generally doesn't happen. It may also technically be slower than the hand-coded method, but there are a couple of areas where we could probably speed up the thunking operation---the allocation of
values
in particular could be moved to the context object.

Friday, October 5, 2007

R 2.7.0 Quartz graphics device clipboard support

Tom Elliot recently posted a request to the R-SIG-Mac list looking for a way to programmatically put graphics onto the clipboard under OS X. Right now this is pretty hard, but I was inspired to patch R-devel's (the future 2.7.0) new Quartz device to allow for clipboard output.

After applying the patch, you can specify file="clipboard://" and when the device is closed (yes, you MUST close the graphics device to see output. It would probably be a good idea to write the file on a NewPage as well, come to think of it). Then you can wrap up a simple function to get copying of the current device to the clipboard (or you can simply open it directly).


copy.to.clipboard = function(dpi=300) { dev.copy(device=quartz,type="png",file="clipboard://",dpi=dpi);dev.close(); }


I've attached the patch for the brave souls willing to try it out:



Index: qdBitmap.c
===================================================================
--- qdBitmap.c (revision 43081)
+++ qdBitmap.c (working copy)
@@ -49,16 +49,45 @@
/* On 10.4+ we can employ the CGImageDestination API to create a
variety of different bitmap formats */
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
- CFURLRef path = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,(const UInt8*)qbd->path,strlen(qbd->path),FALSE);
- CFStringRef type = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->uti,strlen(qbd->uti),kCFStringEncodingUTF8,FALSE);
- CGImageDestinationRef dest = CGImageDestinationCreateWithURL(path,type,1,NULL);
- CGImageRef image = CGBitmapContextCreateImage(qbd->bitmap);
- CGImageDestinationAddImage(dest,image,NULL);
- CGImageDestinationFinalize(dest);
- CFRelease(image);
- CFRelease(dest);
- CFRelease(type);
+ CFStringRef pathString = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->path,strlen(qbd->path),kCFStringEncodingUTF8,FALSE);
+ CFURLRef path;
+ if(CFStringFind(pathString,CFSTR("://"),0).location != kCFNotFound) {
+ CFStringRef pathEscaped= CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,pathString,NULL,NULL,kCFStringEncodingUTF8);
+ path = CFURLCreateWithString(kCFAllocatorDefault,pathEscaped,NULL);
+ CFRelease(pathEscaped);
+ } else {
+ path = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,(const UInt8*)qbd->path,strlen(qbd->path),FALSE);
+ }
+ CFRelease(pathString);
+
+ CFStringRef scheme = CFURLCopyScheme(path);
+ CFStringRef type = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->uti,strlen(qbd->uti),kCFStringEncodingUTF8,FALSE);
+ CGImageRef image = CGBitmapContextCreateImage(qbd->bitmap);
+ if(CFStringCompare(scheme,CFSTR("file"),0) == 0) {
+ CGImageDestinationRef dest = CGImageDestinationCreateWithURL(path,type,1,NULL);
+ CGImageDestinationAddImage(dest,image,NULL);
+ CGImageDestinationFinalize(dest);
+ CFRelease(dest);
+ } else if(CFStringCompare(scheme,CFSTR("clipboard"),0) == 0) {
+ //Copy our image into data
+ CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault,0);
+ CGImageDestinationRef dest = CGImageDestinationCreateWithData(data,type,1,NULL);
+ CGImageDestinationAddImage(dest,image,NULL);
+ CGImageDestinationFinalize(dest);
+ CFRelease(dest);
+ PasteboardRef pb = NULL;
+ if(noErr == PasteboardCreate(kPasteboardClipboard,&pb)) {
+ PasteboardClear(pb);
+ PasteboardSyncFlags syncFlags = PasteboardSynchronize(pb);
+ PasteboardPutItemFlavor(pb,(PasteboardItemID)1,type,data,0);
+ }
+ CFRelease(data);
+ } else
+ warning("Not a supported scheme, no image data written.");
+ CFRelease(scheme);
+ CFRelease(type);
CFRelease(path);
+ CFRelease(image);
#endif
}
/* Free ourselves */

Friday, September 21, 2007

Those of your tracking R-devel may have noticed...

...that there was recently a big change to the way the Quartz device works. Turns out, Simon and I had roughly the same idea about a Quartz 2D implementation of the Quartz device for reasons of speed and the combined result (I ran "patch" on my R-devel tree and then Simon did all of the hard work :-) ) is in there now. This means the RExecServer and other R GUIs can now all use the same drawing backend for a variety of sources. I believe, for people who don't want to use RExecServer, that Simon also put in a CGLayer-based Cocoa target that runs an event loop in much the same way as RExecServer to give you interactive graphics natively in v2.7 (I'm guessing, since the feature freeze has passed). It also means native access to all of the Quartz 2D bitmap generation goodness and a lot more speed for ALL of the graphics device implementations.

Simon also went through and added some other features as well, including support for non-square pixels and some scaling things that I never got working correctly.

So, things have been moving along even though it may appear that I dropped off the face of the planet for a while. I'm going to be updating RExecServer soon, which means a dependence on R-devel, to use the converged device and it's new API (which is also available to any GUI developer).

Friday, August 17, 2007

Cover Flow!



Yup. Figured it out. One of two ways to browse display plots. There is a more useful mode, using an iPhoto-like Browser that also lets you edit the list of plots. The arrow keys also let you move between plots.

Thursday, August 16, 2007

iWork '08 Spelunking.

Fiddling around with iWork '08 a bit and Numbers support is trivial to add to the R GUI. Basically, it exports a tab delimited version as text, much like Excel, though it uses the more modern pasteboard type (where Excel exports a much less useful string). We can detect that it is numbers from the metadata and "native" pasteboard types and throw up the same dialog as for the Excel pasting. Yay!

I'm thinking, for symmetry, that I should let you right click on a data frame in the workspace and copy as a tab delimited type for pasting into Excel/Numbers as well.

Saturday, August 4, 2007

RExecServer can load help

It's not the best implementation in the world---I still think that should take place at the R level not in the GUI implementation---but on the plane to Seattle I gave RExecServer the ability to load help via the pager instead of just complaining.

Friday, August 3, 2007

Using RExecServer from ESS

In the long term I hope that most of this can be configured by an Installer of some sort, but for those wanting to use RExecServer with ESS, here's how my setup is currently configured:

When I use Emacs at all (which is very rarely these days, I mostly use TextMate) I use Aquamacs, which ships with ESS installed. Hopefully the instructions won't be all that different for people using Carbon Emacs and what have you (and if they are, perhaps people would be so kind as to post their changes). I'm also not a super-sophisticated ESS user so there might be better ways of doing the ESS side of things.

First, though, we need to get ourselves easy access to RExecServer from the command line. I like to do this via a symbolic link in /usr/local/bin (/usr/bin would also work). You'd think that we could just symlink RExecServer.app/Contents/MacOS/RExecServer and be done with it, but the way OS X starts applications is... uh... strange and doing that will cause all sorts of problems because the bundle loader won't be able to find any resources and promptly crash out. It's not pretty. What we need to do is actually set a special environment variable called CFProcessPath to point to the application bundle rather than /usr/local or whatever.

Fortunately, I made this into a little script so you won't have to do it yourself. It lives in the Resources folder of the application bundle. So, from Terminal we would do something like:


$ cd /usr/local/bin
$ sudo ln -s /Applications/RExecServer.app/Contents/Resources/RExecServer.sh R-exec


if you have put RExecServer.app somewhere other than Applications, use that path instead of "/Applications." That's pretty much all there is to it. Of course, "/usr/local/bin" should be in your path


$ R-exec

R version 2.6.0 Under development (unstable) (2007-07-14 r42234)
Copyright (C) 2007 The R Foundation for Statistical Computing
ISBN 3-900051-07-0

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

>


You should be able to use R-exec from Terminal just like normal R. For ESS, I have a line in my .emacs file:

(require 'ess-site)
(setq inferior-R-program-name "R-exec")


that starts my RExecServer version of R instead of the normal version. Once upon a time, I think I read that ESS is smart enough to detect multiple installed versions of R with specially named symlinks, but I don't know how to get it to work (if an ESS expert knows how to do this, please chime in).

In any case, that's pretty much all there is to it. Hopefully that helps, but if you have questions please email me or (better yet so others can see) leave a comment.

Thursday, August 2, 2007

Oh, yeah.

I did figure out one thing in an idle moment on vacation last week. How to paste from Excel to the R console. Thought y'all might like something like that. I should probably do another featurecast soon to show some of the things that have been fleshed out a bit. These include...


  • Multi-level hinting. If you have "plot(foo()," the hinting mechanism will show you "plot," rather than nothing

  • Drag-n-drop from the workspace to the console gets a summary() on those objects

  • Excel (or Excel-like things I suppose. Anything that posts VALU or NSTabularPboardType) pasting

  • Device recording, with the option to deactivate as well as clear plot listing

  • Clear the console

  • Copying text from the console optionally does so in a "source()-able" manner. i.e. prompts and output are removed.

  • Keybindings for popular things like Command-= to give you "<-"

  • TextMate-style brace handling. i.e. select some text and type "(" (or ",`,',{,[,Command-[) and it will put the appropriate bracket around the selected text.

  • An editor. :-)

  • Preferences :-)



Any requests?

I'm sure there's other stuff I've forgotten at this point, but I'm still unburying myself otherwise and getting ready to head to BioC'07 in a couple of days.

Sunday, July 15, 2007

RExecServer git repository update

The master branch of the git repository is now up-to-date with the leopard branch, minus any leopard specific code (of which is there none at present). R-devel is required to get things to compile. The GUI code isn't available yet, I'd like to get things to a truly usable stage first. I haven't had as much time this week, other things have taken priority, but some minor stuff was added such as the beginnings of the preference panes (and the infrastructure to support them) as well as the start of the action menus. I also changed the graphics device plot selector to take advantage of available space. Perhaps I'll post a screencast of that soon.

In the meantime, people who aren't using OS X, but want to consider a similar GUI type of set up might want to look at the Omegahat CORBA stuff, which did something very similar a long time ago. The page hasn't even been updated since before OS X 10.0 was released. :-)

Tuesday, July 10, 2007

Just a couple minor updates

Doing some other things today so only a couple of minor updates watching The Tour this evening:




  • Removed the mouse-sensitive pages menu. This seems to be pretty disruptive without putting some extra delay into the transition, but the delay makes it too slow. Instead, I'm now using the toolbar lozenge to show and hide. I think I'll also hook it up to middle-click.

  • Implemented some display transformers for the Workspace. There's a (localized) "n objects" on the bottom now. The class and size information for objects is now shown (could probably be prettier!) as well.
  • Updated: Oh, yeah. You can also clear a console with Cmd-L now (the current prompt is restored). I saw that feature request on R-SIG-Mac a couple of weeks ago and it was something like 5 lines of code so I tossed it in.

Monday, July 9, 2007

R GUI Screencast

Some of the new GUI features are hard to get from screenshots so I decided to make a little screencast to show off a couple of the new features.



Saturday, July 7, 2007

Starting to think about help...

You know what would be awesome? If the HTML manpages generated by R were actually microformat-enabled HTML. I was just thinking about how it would be nice if you could identify example code and whatnot from the eventual help page. I wonder how scary that code is? IIRC the HTML generated by R's manpage generator was pretty much HTML3 (so not even easy to style) so it could be pretty scary. It may even be one of the Perl bits.

Okay, I need to get some sleep now.

Friday, July 6, 2007

Making Progress on the Console



Making some progress on the GUI's Console implementation. You can see that we now indicate WHICH server we're talking to at the moment. I've added an attached "workspace" inspector in the form of a source list such as the ones you see in Mail and iTunes. This holds information about the objects for inspection or dragging and dropping between servers. The thin black line is a splitter bar and is user selectable (double clicking collapses it entirely).

Both sides now have a status bar (of course I made sure the shading matched the action button... why do you ask?) with an action menu. As well, the hint is back and is now being powered from a new object inspection infrastructure in RExecServer (I have some other code to work on, but I will hopefully mind a few minutes to finish things and do a git push). We're now parsing out the function information rather than capturing string output so I think we can do completion in the style of Xcode or TextMate where, say, lm completion would result in:


lm(<#formula>,<#data>,<#subset>,<#weights>,<#na.action>)


I think Ctrl-/ should advance and that TAB (perhaps, I'm open to suggestion) should complete the editing. In other words, imagine we had...


lm(X ~ Y,mydata,<#subset>,<#weights>,<#na.action>)


then TAB would clean the other three arguments and jump us out of the function. You can't see it, but the console is actually doing a lot of invisible markup that we can use to detect the completion regions. We're presently also using text attributes to track input, prompt, error and output regions. This means we can easily cut and paste text WITHOUT prompts (which has been a major feature requests) and without ambiguity. We can also save a console script as a sourceable .R file for later use (sort of like dribble) saving the previous output as comments or not as all.

Wednesday, July 4, 2007

Ha Ha! Success!

After several hours of missing one very important line of code in RExecServer, which was preventing it from operating in purely vended mode---it was a delegate problem and the TerminalDelegate would replace it---I've gotten RExecServer executing and communicating with an R GUI.





The GUI is Leopard specific, but I can offer some details so far:

  • We use a normal NSDocument with no modifications to NSDocumentController or NSApplication or any of the questionable things I had to do for the pre-RExecServer GUI implementation

  • Devices are considered to be views of the document so devices and the console are explicitly intertwined now. In much the same way that the main Interface Builder window and the associated UI views are linked. Hopefully this means an end to the infamous "hanging" windows.

  • Scripts won't be, but we'll need a UI for specifying their target console...

Tuesday, July 3, 2007

Apparently I'm on a tear...

Rob Goedman gets the Official Tester Award for RExecServer, reporting several issues with the graphics subsystem and my inability to appropriately use version control systems. There are a bunch of changes in the git repository:

  • All the files you need are actually there

  • Clipping rects are restored on subsequent updates so some things are working better

  • Terminal support has been refactored into a pseudo-GUI to help make sure I have coverage on the things I'll need for a true front end. Its probably fractionally slower than a true Terminal version of R, but it has some more flexibility

  • Device windows close when devices go away. A good thing too because they'd crash if you resized them after the device disappeared.



I've been playing with making the RGUI_Type not be AQUA, which restores all of the help file functionality, but causes an annoying (and untrue) complaint from quartz() among other things like trying to use X11 for select.list(). I wish there was a way to selectively deactivate things like that in R instead of the blanket situation we have now. There are a number of places where we have


if(.Platform$GUI == "windows" | .Platform$GUI == "AQUA") ... else ...


that are just irritating. Personally, I think we should be doing dispatch (S3, S4, I don't care) where .Platform$GUI becomes an _object_ so that I can define functions for my particular GUI and where .default is the stuff on the RHS of the else.

Monday, July 2, 2007

RExecServer object vending

The RExecServer started getting basic object vending today. To test it out I implemented some simple object copying using Distributed Objects. This is checked into the public git repository, but will require R-devel to run because of changes to function export under R-devel. Behold!

First we start ourselves a couple of copies of R. Then,


> x = 1:100
> .Call("RES_CopyObject","x","R Execution Server 2")
NULL
> .Call("RES_ServerName")
[1] "R Execution Server 1"
>


Afterwards, we can take a look at the other execution server:

> ls()
[1] "x"
> .Call("RES_ServerName")
[1] "R Execution Server 2"
> x
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
[19] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
[37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
[55] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
[73] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
[91] 91 92 93 94 95 96 97 98 99 100
>


Ta Da! There's no big trick to it--we're just using the standard serialization routines to read and write NSData objects on the Cocoa side and then using the usual NSDistantObject routines to actually transmit the data. There's no real error checking at the moment, but that will come.

Also, with the help of some scripts from Rob Goedman I think I've tracked down the last of the clipping and state stacking errors in this latest checkin.

RExecServer available as a git repository

The RExecServer source is now available as a git repository. Once I figure out what exactly they mean I will be "mobbing" it to allow for patch submission. The binary hasn't been updated, but the source fixes a few reported problems with the pager() and some graphics issues (clipping mostly). It also adds the ability to specify the usual command-line options except for the gui related ones (for obvious reasons).