{ Fluorescence Macros These macros were written for a system comprising a Scion LG-3 framegrabber in a Mac IIfx, a Uniblitz shutter with the T132 controller from Vincent Associates, and a Dage SIT camera mounted on a Nikon Optiphot-2 upright microscope. They provide acquisition of full or partial frames; easy live montaging; automatic stamping with scale bar, date, and time; full shutter control from the keyboard, including automatic shutter control from acquisition macros; background subtraction; and rudimentary timelapse capabilities. ==== Customization ===== To match other configurations, you must: 1) Change the Set Background macro so that it can find the Gauss(15x15) kernel (part of the NIH-Image distribution set). 2) Adjust the Set Mag macro to get correct scale bars 3) Change the shutter control macros if you are using a different shutter and/or controller 4) Remove the video sync code from acquisition macros if you are not using the Scion LG-3; 5) Replace the Video Rate Capture commands if you are not using the Scion framegrabber, with its built-in memory. ====== Written by ====== Chi-Bin Chien, Dept. of Biology, UC San Diego Comments and bug reports to: chien@jeeves.ucsd.edu (I can't promise any support for these macros, but will do my best to respond.) Version 1.1, last modified 6/16/94 Requires Image version 1.53 } { ==== List of Macros ==== Add Slice [+] adds slice Delete Slice [-] deletes slice Goto [G] go to desired slice Set Mag [M] set mag before acquiring Store Bkgd [F12] acquire a background frame Bkgd on-off toggle background subtract Timestamp [F11] toggle timestamp Autoshutter toggle Autoshutter mode Open Shutter [O] open shutter Close Shutter[C] close shutter Live [L] go to live video Average [ ] take averaged picture Live and Average are fancy context-sensitive macros: ¥ in the Camera window, Live starts capturing and Average adds to a stack ¥ in a non-Camera window with a ROI selected, Live starts a Live paste, and Average pastes in an averaged picture for the ROI ¥ in a non-Camera window with no ROI, Live switches to the Camera window, and starts capturing Flash to Stack [F14] takes flash picture, adds to stack Flash to Disk [F13] takes flash picture, saves to disk Slow Flash [F9] opens shutter 100 ms before averaging Timelapse take a timed series of flashes, to disk AddBlankSlice [B] adds a black frame to the current stack, with date and scale bar Invert [I] inverts the window, or all slices in the stack Sub bkgd [S] subtracts background (in clipboard) from all slices Print All [P] print all slices of all windows } { Global variables } var mag, barlength: real; { objective magnification, size of scale bar to use (µm) } camerapid: integer; { PID of camera window } bgpid: integer; { background window } bgsub: boolean; { whether to subtract background } tsoff: boolean; { whether to omit timestamp from captured images } asoff: boolean; { whether the AutoShutter feature is off } shopen: boolean; { whether we think the shutter is open } tlperiod: real; { timelapse period } { Macros start here } macro 'Fluorescence Macros'; begin end; macro '(-'; begin end; macro 'Add Slice [+]'; begin AddSlice end; macro 'Delete Slice [-]'; begin DeleteSlice end; { Error(s) issues the error message S and terminates the macro. } procedure Error(s:string); begin PutMessage(s); exit; end; { CheckForStack and CheckForSelection are utility routines. } procedure CheckForStack; begin if nSlices=0 then Error('This window is not a stack'); end; procedure CheckForSelection; var x1,y1,x2,y2,lw,l,t,w,h:integer; begin GetRoi(l,t,w,h); GetLine(x1,y1,x2,y2,lw); if (w=0) and (x1<0) then Error('Please make a rectangular selection.'); end; { GetSliceRange is a procedure that gets the implied parameters first and last, then does range checking to make sure this is a valid range of slices. GetSliceSequence gets the implied parameters first, last, and step, doing range checking to make sure it's a valid sequence of slices. } procedure GetSliceRange(smart:boolean); var default:integer; begin first:=GetNumber('First slice:',1); if (first<1) or (first>nSlices) then Error('Not a valid slice number.'); default:=nSlices; if smart and (first=1) and (SliceNumber > 1) then default:=SliceNumber; last:=GetNumber('Last slice:',default); if (lastnSlices) then Error('Not a valid range of slices.'); end; procedure GetSliceSequence(smart:boolean); begin GetSliceRange(smart); step:=GetNumber('Step:', 1); if (step<1) then Error('Step must be 1 or greater.'); end; macro 'Goto [G]'; var s: integer; begin CheckForStack; s:=GetNumber('Slice to go to:', 1); SelectSlice(s); end; procedure SetMag; var m :real; begin if (mag>0) then m:=mag else m:=10.; m:=GetNumber('Magnification of objective:', m); if (m<4.) or (m>100.) then Error('Magnifications must be between 4x and 100x.'); mag:=m; if (nPics>0) then SetScale(mag*.061, 'µm'); { e.g., 6.10 pixels/µm at 100x } { Set barlength. Default is 1000 µm/mag, rounded to the nearest 5 µm; special cases for 16x, 60x. } barlength:=5*round(200./mag); if (mag=16.) then barlength:=50.; if (mag=60.) then barlength:=20.; if (mag>=6) and (mag<6.31) then barlength:=200.; ShowMessage('scale bar=', barlength, 'µm'); end; macro 'Set Mag [M]'; { set mag, barlength } begin SetMag; end; macro 'Store Bkgd [F12]'; { make a new window called 'Background', to be used for background subtractions } begin if (camerapid<>0) then SelectPic(camerapid) else begin StartCapturing; camerapid := PidNumber; end; AverageFrames; SelectAll; Copy; if (bgpid<>0) then SelectPic(bgpid) else begin { no background window yet } SetNewSize(640,480); MakeNewWindow('Background'); bgpid:=PidNumber; end; Paste; Invert; Convolve('video fx:NIH Image:Kernels:Gauss(15x15)');} bgsub:=true; end; macro 'Bkgd on-off'; begin if bgsub then begin ShowMessage('Bkgd subtract off.'); bgsub:=false; exit; end; SelectWindow('Background'); bgpid:=PidNumber; ShowMessage('Bkgd subtract on.'); bgsub:=true; end; macro 'Timestamp [F11]'; { toggle timestamp } begin tsoff:=not tsoff; if tsoff then ShowMessage('Timestamp off.') else ShowMessage('Timestamp on.'); beep; end; macro 'Autoshutter'; begin asoff := not asoff; if asoff then ShowMessage('Autoshutter off.') else ShowMessage('Autoshutter on.'); end; procedure OpenShutter; begin OpenSerial('300 baud'); PutSerial('@'); shopen:=true; end; procedure CloseShutter; begin OpenSerial('300 baud'); PutSerial('A'); shopen:=false; end; macro 'Open Shutter [O]'; begin OpenShutter; end; macro 'Close Shutter [C]'; begin CloseShutter; end; { LabelFrame puts up a scale bar, date, and timestamp if TS is true. year, month, day, hour, minute, second are passed implicitly. (l,t) is upper-left corner of ROI. } procedure LabelFrame(l,t,:integer; ts:boolean); var bar:integer; begin if (mag=0.) then SetMag else SetScale(mag*.061, 'µm'); { SetScale() insures that all frames are spatially calibrated } bar := mag*.061*barlength; SetLineWidth(2); MoveTo(l,t); LineTo(l+bar,t); SetForegroundColor(0); SetFont('Monaco'); SetFontSize(9); MoveTo(l+17,t+7); Writeln(barlength:2,' µm'); if (ts) then begin MoveTo(l,t+18); Writeln(month:2,'/',day:2,'/',year-1900); Write(hour:2,':'); if (minute <10) then Write('0',minute:1,':') else Write(minute:2,':'); if (second<10) then Write('0',second:1) else Write(second:2); end; end; { If background subtraction is turned on, BkgdSub will subtract the background from the current ROI in the current picture. It uses (l,t,w,h) to know where to get the background from. } procedure BkgdSub; var pid:integer; begin pid:=PidNumber; KillROI; if bgsub then begin ChoosePic(bgpid); if (w=0) then SelectAll else MakeROI(l,t,w,h); Copy; SelectPic(pid); RestoreROI; Paste; Add; end; end; { Live and Average are fancy context-sensitive macros: ¥ in the Camera window, Live starts capturing and Average adds to a stack ¥ in another window with a ROI selected, Live starts a Live paste, and Average pastes in an averaged picture for the ROI ¥ in another window with no ROI, Live switches to the Camera window, and starts capturing } macro 'Live [L]'; var l,t,w,h:integer; begin GetROI(l,t,w,h); RequiresVersion(1.53); if not asoff then OpenShutter; if (camerapid=0) or (WindowTitle='Camera') or (w=0) then begin StartCapturing; camerapid := PidNumber; exit; end; { If we get to here, we're in a non-Camera window with a ROI; start Live Paste} PasteLive; end; macro 'Average [ ]'; { assumes that Live has been called already } var l,t,w,h,destpid,p:integer; year,month,day,hour,minute,second,DoW:integer; camera:boolean; begin GetROI(l,t,w,h); RequiresVersion(1.53); if not asoff and not shopen then begin OpenShutter; Wait(.1); { Compensate for SIT's lag time } end; destpid:=PidNumber; camera:=(WindowTitle='Camera'); if camera then begin { try to find the first stack } p:=0; repeat p:=p+1; Choosepic(p); until (nSlices>0) or (p>=nPics); if (nSlices>0) then destpid:=p; { else leave destpid pointing at Camera, later code will start a new stack } Choosepic(camerapid); end; if not camera then begin ChoosePic(camerapid); if (w>0) then MakeROI(l,t,w,h); end; AverageFrames; if (w=0) then SelectAll; Copy; GetTime(year,month,day,hour,minute,second,DoW); if not asoff then CloseShutter; SelectPic(destpid); if (w>0) and not camera then begin { we were in PasteLive mode } MakeRoi(l,t,w,h); Paste; BkgdSub; exit; end; if nSlices>0 then begin SelectSlice(nSlices); AddSlice; end; if nSlices=0 then begin if (camera and (w>0)) then SetNewSize(w,h) else SetNewSize(640,480); MakeNewStack(month:2,'/',day:2,'/',year-1900:2,' ',hour:2,'.',minute:2,'.',second:2); end; Paste; BkgdSub; LabelFrame(0,0,not tsoff); if (camera and (w>0)) then SelectPic(camerapid); end; { Flash just flashes and puts the averaged picture in the Camera window. } macro 'Flash [F1]'; begin if (WindowTitle<>'Camera') then Error('Flash only works in the Camera window.'); OpenSerial('300 baud'); StopCapturing; { in case we're live } { wait for even frame of the video signal, then flash and grab } repeat until ((BitAnd(Scion[3],48)=0)); PutSerial('B'); AverageFrames('Video Rate Capture',2); end; { Flash to Stack flashes once and puts the averaged picture in the first stack that it finds. If there's no stack, it creates one. If you frame a ROI in the camera window, only the ROI contents will be saved in the stack... } macro 'Flash to Stack [F14]'; var l,t,w,h,destpid,p:integer; year,month,day,hour,minute,second,DoW:integer; camera:boolean; begin GetROI(l,t,w,h); RequiresVersion(1.53); destpid:=PidNumber; camera:=(WindowTitle='Camera'); if (camerapid=0) then begin Capture; camerapid := PidNumber; if (destpid=0) then destpid:=camerapid else ChoosePic(destpid); end; if camera then begin { try to find the first stack } p:=0; repeat p:=p+1; ChoosePic(p); until (nSlices>0) or (p>=nPics); if (nSlices>0) then destpid:=p; { else leave destpid pointing at Camera, later code will start a new stack } Choosepic(camerapid); end; if not camera then begin ChoosePic(camerapid); if (w>0) then MakeROI(l,t,w,h) else SelectAll; end; OpenSerial('300 baud'); StopCapturing; { in case we're live } { wait for even frame of the video signal, then flash and grab } repeat until ((BitAnd(Scion[3],48)=0)); PutSerial('B'); AverageFrames('Video Rate Capture',2); GetTime(year,month,day,hour,minute,second,DoW); if (w=0) then SelectAll; Copy; SelectPic(destpid); if nSlices>0 then begin SelectSlice(nSlices); AddSlice; end; if nSlices=0 then begin if (camera and (w>0)) then SetNewSize(w,h) else SetNewSize(640,480); MakeNewStack(month:2,'/',day:2,'/',year-1900:2,' ',hour:2,'.',minute:2,'.',second:2); end; Paste; BkgdSub; LabelFrame(0,0,not tsoff); if (camera and (w>0)) then SelectPic(camerapid); end; { Flash to Disk flashes once and saves the averaged picture to disk. You must be in the camera window; if a ROI is selected, only the ROI will be saved. } macro 'Flash to Disk [F13]'; var l,t,w,h:integer; year,month,day,hour,minute,second,DoW:integer; begin if (WindowTitle<>'Camera') then Error('Must be in the Camera window!'); GetROI(l,t,w,h); RequiresVersion(1.53); OpenSerial('300 baud'); StopCapturing; { in case we're live } { wait for even frame of the video signal, then flash and grab } repeat until ((BitAnd(Scion[3],48)=0)); PutSerial('B'); AverageFrames('Video Rate Capture',2); GetTime(year,month,day,hour,minute,second,DoW); if (w=0) then SelectAll else MakeROI(l,t,w,h); BkgdSub; LabelFrame(l,t,not tsoff); MakeROI(l,t,w,h); SaveAs(month:2,'/',day:2,'/',year-1900:2,' ',hour:2,'.', minute:2,'.', second:2); if (w>0) then begin SetLineWidth(1); DrawBoundary; end; end; { Slow Flash opens the shutter, waits 100 ms, then takes an averaged picture which is added to the first stack that it finds. If there's no stack, it creates one. If you frame a ROI in the camera window, only the ROI contents will be saved in the stack... } macro 'Slow Flash [F9]'; var l,t,w,h,destpid,p:integer; year,month,day,hour,minute,second,DoW:integer; camera:boolean; begin GetROI(l,t,w,h); RequiresVersion(1.53); destpid:=PidNumber; camera:=(WindowTitle='Camera'); if (camerapid=0) then begin Capture; camerapid := PidNumber; if (destpid=0) then destpid:=camerapid else ChoosePic(destpid); end; if camera then begin { try to find the first stack } p:=0; repeat p:=p+1; ChoosePic(p); until (nSlices>0) or (p>=nPics); if (nSlices>0) then destpid:=p; { else leave destpid pointing at Camera, later code will start a new stack } Choosepic(camerapid); end; if not camera then begin ChoosePic(camerapid); if (w>0) then MakeROI(l,t,w,h) else SelectAll; end; OpenSerial('300 baud'); StopCapturing; { in case we're live } { wait for even frame of the video signal, then flash and grab } repeat until ((BitAnd(Scion[3],48)=0)); PutSerial('@'); Wait(0.1); AverageFrames('Video Rate Capture',2); GetTime(year,month,day,hour,minute,second,DoW); CloseShutter; if (w=0) then SelectAll; Copy; SelectPic(destpid); if nSlices>0 then begin SelectSlice(nSlices); AddSlice; end; if nSlices=0 then begin if (camera and (w>0)) then SetNewSize(w,h) else SetNewSize(640,480); MakeNewStack(month:2,'/',day:2,'/',year-1900:2,' ',hour:2,'.',minute:2,'.',second:2); end; Paste; BkgdSub; LabelFrame(0,0,not tsoff); if (camera and (w>0)) then SelectPic(camerapid); end; macro 'Set Timelapse'; begin if (tlperiod=0) then tlperiod:=60; tlperiod:=GetNumber('Seconds between pictures:',tlperiod); end; { Timelapse starts taking a timelapse series of pictures, every tlperiod seconds (use Set Timelapse to set this period). Use command-period to abort the series. Note: the timing loop doesn't handle the transition over midnight. } macro 'Timelapse [F15]'; var l,t,w,h:integer; year,month,day,hour,minute,second,DoW:integer; time,next,last:real; begin if (WindowTitle<>'Camera') then Error('Must be in the Camera window!'); GetROI(l,t,w,h); if (w=0) then SelectAll; KillROI; RestoreROI; { save ROI for future RestoreROIs } RequiresVersion(1.53); OpenSerial('300 baud'); StopCapturing; { in case we're live } if (tlperiod=0) then tlperiod:=60; { start at the next multiple of tlperiod } GetTime(year,month,day,hour,minute,second,DoW); time:=second+60*(minute+60*hour); if (tlperiod<60) then next:=time+(tlperiod-(second mod tlperiod)) else next:=time+(60-second); last:=time; while true do begin while (timelast) then begin ShowMessage(hour:2,':',minute:2,':',second:2,'\',next-time); last:=time; end; end; { wait for even frame of the video signal, then flash and grab } repeat until ((BitAnd(Scion[3],48)=0)); PutSerial('B'); AverageFrames('Video Rate Capture',2); GetTime(year,month,day,hour,minute,second,DoW); RestoreROI; BkgdSub; LabelFrame(l,t,not tsoff); RestoreROI; SaveAs(month:2,'/',day:2,'/',year-1900:2,' ',hour:2,'.', minute:2,'.', second:2); if (w>0) then begin SetLineWidth(1); DrawBoundary; end; RestoreROI; next:=next+tlperiod; end; end; macro 'AddBlankSlice [B]'; var year,month,day,h,mm,s,DoW,bar:integer; begin SetBackgroundColor(255); AddSlice; GetTime(year,month,day,h,mm,s,DoW); LabelFrame(0,0,false); end; macro 'Invert [I]'; var i,s:integer; begin if (nSlices = 0) then Invert else begin s:=SliceNumber; for i:=1 to nSlices do begin ChooseSlice(i); Invert; end; SelectSlice(s); end; end; macro 'Sub bkgd [S]'; var i,s:integer; begin PutMessage('The background frame should be in the clipboard, already smoothed and inverted.'); if (nSlices=0) then begin Paste; Add; exit; end; s:=SliceNumber; for i:=1 to nSlices do begin ChooseSlice(i); Paste; Add; end; SelectSlice(s); end; { PrintROI does the work of printing the ROI (or the whole frame, if no ROI is set). If the ROI is too big to print on a LaserWriter at 86% (i.e., wider than ~640 pixels or higher than ~840), it is printed as overlapping panels. } procedure PrintROI; var ROIset: boolean; left,top,width,height,right,bottom,l,t,w,h:integer; begin GetRoi(left,top,width,height); if (width>0) then ROIset:=true else begin ROIset:=false; left:=0; top:=0; GetPicSize(width,height); end; if (width<=640) and (height<=840) then Print else begin right:=left+width-1; bottom:=top+height-1; l:=left; repeat t:=top; repeat if l+640>right then w:=right-l+1 else w:= 640; if t+800>bottom then h:= bottom-t+1 else h:=840; MakeRoi(l,t,w,h); Print; t:=t+800; until (t>bottom-40); l:=l+600; until (l>right-40); end; if not ROIset then KillRoi else MakeRoi(left,top,width,height); end; macro 'Print All [P]'; var i,j: integer; begin PutMessage('Remember to set 86% magnification in Page Setup.'); for i:=1 to nPics do begin SelectPic(i); if nSlices=0 then PrintROI; if nSlices>0 then begin for j:=1 to nSlices do begin SelectSlice(j); PrintROI; end; end; end; end;