{Darin Nelson, Duke University Dept. of Neurobiology (dnelson@neuro.duke.edu)} {July, 1994} { NIH Image as presentation program: tips and tricks Our lab routinely needs to generate video tape recordings for use in animated presentations, but we haven't gotten around to buying a "real" presentation program, because so far NIH Image has proved flexible enough for our modest needs. I thought that someone might find it useful if I were to post some of the macros that I've been using to to bend it. Each presentation to be recorded requires writing a custom macro, which means that users have to have some expertise in macro programming, but once you've gotten a few little tricks under your belt, the process is pretty straightforward. I'm no expert on video production, so this won't be anything like an exhaustive treatise, but this should be enough to get the general idea across. I. What you need: A Macintosh with NTSC video signal output capability. There are some third party products which will take normal monitor signals and convert them to NTSC signals; the AV Macs have this capability built in. Lots of RAM. The more RAM you have, the bigger the stacks you can make videos from. You can work around low RAM, but it's not all that much fun. NIH Image. The Kolor 2.0 control panel by Russ Wetmore. This is available by anonymous FTP from various archives around the internet. The one I got it from is: mac.archive.umich.edu. This cdev is a free, unsupported little hack from Apple. A VCR. A monitor which accepts an NTSC signal (your everyday TV is fine). Some stacks for animation. It helps to have two monitors on your computer, but it's not critical. II. What you do: First, generate your images. I've included some macros which some users may find useful for this sort of thing. One of them goes through a stack and interpolates one frame after each existing frame by simple averaging. This helps to smooth out an animation series which otherwise might look jerky. Another group of macros helps combine images which may have entirely different LUTs. The basic idea is to shove one image or set of images down into values 0 -127, shove the other up into values 128-254, then paste one over the other with the "DoReplace" setting, so that 0 valued areas in the pastand are transparent to the values in the pastee. Generate a LUT which has been similarly treated, and you get the desired result, with the loss of one bit of resolution. That's all a bit convoluted if you just want to do one such paste operation, but is worth the effort if you want to do it 100 times in a row. I use this technique to generate stacks with an animated color data set superimposed on a differently colored background. Something to keep in mind when generating your image files: colors which look great on a computer screen sometimes look crummy on a TV screen. The worst offenders are bright colors, which tend to oversaturate. I imagine that an expert could tell you more specifically which colors to avoid, but I am not that expert. Once you've got all your pretty pictures generated, you need to write a macro which controls their animation. I've included an example animation macro in this text file. The most important part of an animation macro is getting the timing right. Make liberal use of for loops to set delay lengths both for controlling how long title frames show, and for controlling the speed of animations. If you test your animation out on a high resolution monitor before hooking up the NTSC output, keep in mind that a lower resolution monitor will refresh more quickly than a high resolution monitor, so your timing will get faster when you actually go to record. NIH Image isn't much on special effects, but for scientific presentations you probably don't want to overdo the gee-whiz stuff, anyway. I've included a set of macros which will allow you to generate passable fades, but even those are a little over the top. You should be careful with using the fading macros, because for some reason, NIH Image only allows the value 255 to be completely dark (RGB values 0,0,0). All the values from 1 to 254 have to have at least one of the RGB components > 0 (at least, that is my empirical determination). This matters because if you have black 255 in your image, then when it is completely faded there will still be a ghost of the non-black pixels displayed on your monitor. To get around this problem, use some pixel value besides 255 for displaying black in your images. Another little animation trick is to get blinking effects by switching back and forth between two nearly identical frames very quickly, with the blinking part of the image present in one frame, and absent in the other. If you've got a blazingly fast computer, you can get a partial transparency effect by VERY quickly switching between two frames in a stack. I generally include a completely black image the size of the screen among my images to be animated, because I've found that the PhotoMode macro command isn't entirely bug free (sorry for the embedded bug report, Wayne). Sometimes other windows (especially the Map and Info windows) peek through. I use the black image to hide other images and stacks, bringing them forward one at a time as needed. Another macro which is good to write is a "prep" macro, of which I also give an example. This is useful for putting all your windows in the proper order, in their proper places, and, if you like, fading them all to black to smooth out transitions during the animation. You can also position them all by hand, but that gets to be a pain after a while. Once all this is in place, and your VCR is all set to record your NTSC output, there's one more trick that you might want to use before you set you animation macro in motion. This is where the Kolor 2.0 control panel comes into play. It seems that PhotoMode has another bug: it doesn't hide title bars during animations. My workaround of choice has been to use Kolor 2.0 to change the color of the menu bar to that of the background color I'm using (almost always black). Also, as new windows are brought to the front during the course of an animation, the window is first drawn as a white rectangle by default, resulting in a distracting white flash before the image is displayed. Kolor 2.0 can set the color of this initial draw of the window to black, thus hiding it when windows are shifted around. I should warn that this little modification to the user interface makes it decidedly unfriendly (you can't SEE anything), so be prepared to do some mousing around in the dark. That exhausts my meager store of accumulated wisdom on this topic; I hope that someone finds it useful. Here are the macros I mentioned. I expect that a number of them include snippets of code from other people, but I've long since forgotten who. If it's you, let me know and I'll try to make reparations for omitting proper credit here. } macro 'Interpolate Frames'; {interpolates new frames between each frame of a stack by simple averaging. Useful for smoothing animations.} var i,w1,w2,w3,h1,h2,h3,d1,d2,d3:integer; begin SaveState; d1 := nSlices; ScaleMath(false); for i:=1 to d1 do begin ChooseSlice (i*2 - 1); SelectAll; Copy; AddSlice; ChooseSlice (i*2); Paste; SelectAll; MultiplyByConstant(0.5); SelectAll; Copy; if (i > 1) then begin ChooseSlice(i*2-2); Paste; Add; end; end; RestoreState; end; Macro '(-' macro 'Divide Stack by 2' {this one can be used when preparing to paste a stack with one LUT onto a background with a different LUT.} var i :integer begin for i := 1 to nSlices do begin selectslice (i); multiplybyconstant(0.5); end; end; MACRO 'Paste Stack onto Background' {This macro requires 2 and only 2 images to be open, one a stack and one a simple image. The simple image should be the second image opened. If images need to be divided and/or have offsets added to them, this should be done before running this macro. The pasting is done with REPLACE mode set, so that white space in the "pasteand" image is transparent, allowing the values of the "pastee" to show through.} VAR left,top,width,height,i,l2,t2,w2,h2:integer; Begin ChoosePic(1); CheckForStack; SelectAll; GetROI(left,top,width,height); left := GetNumber('Left coordinate of ROI',0); top := GetNumber('Top coordinate of ROI',0); ChoosePic(2); SelectAll; GetROI(l2,t2,w2,h2); SetNewSize(w2,h2); MakeNewStack('Pasted Stack'); SelectPic(1); for i:= 1 to nSlices do begin ChoosePic(2); SelectAll; Copy; ChoosePic(3); AddSlice; Paste; ChoosePic(1); ChooseSlice(1); SelectAll; Copy; DeleteSlice; ChoosePic(3); MakeRoi(left,top,width,height); Paste; SetOption; DoReplace; end; end; Macro '(-' {LUT animation-related macros} {these macros use hardwired pathnames, which have to be changed when you actually go to use them. They generate a series of LUTs which can be used to animate various fade-outs and fade-ins.} Macro 'Make LUT series with one constant value' {this fades all LUT values except one (134, in this case). You can easily change the preserved value by changing every occurance of 134 to something else. You'll need to change mylut to be set to the pathname of an NIH Image-generated lookup table.} var i:integer; mylut:string; begin mylut:='MyHD:myLUT'; SetCustom(256,3,0); SetImport('Custom'); Import(mylut); ScaleMath( false); for i:=1 to 8 do begin SelectAll; AddConstant(-32); MakeROI(134,0,1,1); AddConstant(255); KillROI; SetExport('Raw'); Export('seismic/cresylL-'&i*32); end; end; Macro 'Make LUT series dimming one value' {this sets every value in the LUT to the color (1,1,1) except 0 and 255 (of course) and some other value (in this case 134) which is faded out gradually. The usual comments on changing the single fading value and setting the LUT path name apply. } i:integer; mylut:string; begin mylut:='myHD:myLUT'; SetCustom(256,3,0); SetImport('Custom'); Import(mylut); ScaleMath( false); for i:=1 to 8 do begin SelectAll; AddConstant(-255); MakeROI(134,0,1,1); AddConstant(255-i*32); KillROI; SetExport('Raw'); Export('seismic/cresylM-'&i*32); end; end; Macro 'Make Dimmed LUT Series' {This creates a uniformly dimming series of LUTs for animation. You'll need to set the pathname of your favorite LUT.} var i:integer; mylut:string; begin mylut:='myHD:myLUT'; SetCustom(256,3,0); SetImport('Custom'); Import(mylut); ScaleMath( false); for i:=1 to 8 do begin AddConstant(-32); SetExport('Raw'); Export('seismic/cresyl-'&i*32); end; end; Macro 'Demo LUT Fades [D]' {this is just a little macro to demostrate LUT fades. If you use this technique in generating presentations, you'll need to use code something like this.} var i,j,k:integer; mylut:string; begin mylut:='SongBird:seismic/cresyl'; SetImport('Palette'); for i:=1 to 8 do Import(mylut&'L-'&i*32); for i:=1 to 8 do Import(mylut&'M-'&i*32); for i:=1 to 8 do Import(mylut&'-'&(9-i)*32); Import(mylut); end; Macro '(-' {here's an example of a prep macro and an animation macro, stripped down a little bit for simplicity of presentation. These files have to be custom designed for each presentation. I also include a couple of fading procedures like those outlined above.} PROCEDURE DIM var i,j,k:integer; mylut:string; begin mylut:='myHD:myLUT'; SetImport('Palette'); for i:=1 to 8 do Import(mylut&'-'&i*32); end; PROCEDURE BRIGHT var i,j,k:integer; mylut:string; begin mylut:='myHD:myLUT'; SetImport('Palette'); for i:=1 to 8 do Import(mylut&'-'&(9-i)*32); Import (mylut) end; Macro 'Ready Animations for Shooting [R]' {this macro takes care of some of the startup stuff. It doesn't load files, but it does get them in order. Very simple, so I haven't commented it out} begin SelectWindow(My First Stack'); SelectSlice(1); DIM; MoveWindow(166,67); SelectWindow('My Second Stack'); SelectSlice(1); DIM; MoveWindow(166,67); SelectWindow('My Title Page'); DIM; MoveWindow(12,67); SelectWindow('my big blank window'); MoveWindow(0,0); DIM; end; Macro 'Animate [A]' { this macro makes a movie of the sequences My First Stack, My Second Stack, and My Title Page. Requires also a file called 'my big blank window'. All four of these files must be preloaded. Also requires using the ColorIt control panel to set the title bars and default background color to black.} var i,j,k : integer begin { title page stuff} Photomode(true); SelectWindow('my big blank window'); DIMA for i:= 1 to 40000 do begin end; {a pause so I can hit the RECORD button} SelectWindow('My Title Page'); BRIGHT; for i:= 1 to 400000 do begin end; {a nice long pause} DIM; for i:= 1 to 20000 do begin end; {a little pause to avoid abrupt transitions} SelectWindow('my blank window'); {first animation} {the title page of this animation} SelectWindow('My First Stack'); {the title, we assume, is in the first image, which was set by the prep macro we already ran} BRIGHT; for i:= 1 to 100000 do begin end; DIM; for i:= 1 to 20000 do begin end; {now show the movie itself} {first 2x fast} SelectSlice(2); {or whatever the first slice of your animation is} for i:= 1 to 10000 do begin end; BRIGHT; for i:= 1 to 90000 do begin end; {show the first frame for a bit to give time for the audience to get oriented} for k := 1 to 2 do begin for j := 2 to 40 {last frame in animation} do begin selectslice(j); {no delay here, we go as fast as we can} end; for i:= 1 to 40000 do begin end; SelectSlice(2); for i:= 1 to 90000 do begin end; end; {now 1x slow} for j := 2 to 40 do begin selectslice(j); for i:= 1 to 8000 do begin end; {a little delay to slow things down to make the animation easier to follow} end; for i:= 1 to 40000 do begin end; DIM; SelectSlice(2); SelectWindow('my big blank window'); for i:= 1 to 70000 do begin end; {second animation} {the title page} SelectWindow('My Second Stack'); BRIGHT; {.... etc, just like before; I delete it here because this is just an example macro ...} DIM; SelectWindow('blank window'); for i:= 1 to 80000 do begin {time to stop the tape before the menu bar reappears} end; end;