Guang Mei (gmei@hoh.mbl.edu Tel:(508)548-3705 ext 374) writes: >With NIH Image 1.52, it seems that I have difficulty when using "Save >Selection As..." to save part of the images to a new stack from a opened >stack. For example, when I have a stack (having 4 same-sized slices) open, >then choose some ROI on a slice and want to save the same ROIs on each >slice to a new stack. Currently, I have to use "Stack to Windows", save the >selection for each window, close the windows, open the newly saved files, >then " Windows to Stack" to get the wanted stack. Is there any other easier >way to do this? Here is the bare bones. More bells and whistles can be added. It doesn't actually save the stack, and if you run out of memory, then use deleteSlice after each copy and always select the first slice of the input stack. Probably there was already such a macro in stack macros... I didn't look, easier to just write a new one. macro 'copy stack selection'; var left,top,width,height, i, n, stk, nstk: integer; begin RequiresVersion(1.52); stk := pidNumber; getRoi(left,top,width,height); n := nSlices; if (n = 0) or (width = 0) then exit; setnewsize(width,height); makenewStack('selection'); {trouble if width is odd} nstk := pidNumber; for i := 1 to n do begin choosePic(stk); chooseSlice(i); copy; choosePic(nstk); if nSlices < i then addSlice; chooseSlice(i); paste; end; {after using choosePic/chooseSlice, be sure to do select at end} SelectPic(stk); SelectSlice(1); SelectPic(nstk); SelectSlice(1); end; -- Edward J. Huff huff@mcclb0.med.nyu.edu (212)998-8465 Keck Laboratory for Biomolecular Imaging NYU Chemistry Deptartment, 31 Washington Place, New York NY 10003 ************************************************ glenmac@carson.u.washington.edu Glen MacDonald writes 93-10-14 The macro below was written to be used with SEM prints set on a digitising tablet with the horizontal reference boundary aligned with the bottom of the tablet - the x-axis. Eventually, as in when someone actually needs it badly enough, I will write aversion that will allow measuring from the monitor. However, since aligning the sample to make its reference boundary is parallel with the x-axis of the SEM monitor is difficult, I will have to add a routine to allow drawing a line designating the refernce boundary of the sample as the x-axis, which will be non-parallel to the monitor's x-axis, and then shift all angle measurements accordingly. Then a corrected measurement of the distance from the midpoint of the angle to the arbitrary baseline. Angles macro {Macros written by Glen MacDonald, Hearing Development Laboratories RL-30, Univ. Washington, (206) 543-8360, glenmac@u.washington.edu. April 10, 1992} Macro 'Right Ear Angle, Position [R]'; {This measures width, angle, and distance from from the lower horizontal axis of objects on the tablet or monitor. Draw a line across the stereocilial tips of the hair cell bundle with the straight line tool, then press 'R'. Angles are 0 to180 and -0.1 to -179.9 degrees as drawn clockwise around a hypothetical circle, (left to right as you "face" the tallest row) with 0 implying that the tallest row of stereocilia are facing, and parallel to, the inferior edge and 180 implies that the tallest row is facing the superior edge, i.e, positive angles are 0 to pi and negative are pi to 2 pi. Y-axis is not inverted. An error message is returned if the bundle width is less than 1 micron. A submicron bundle usually means that your finger slipped while drawing the line and that measurement should be deleted.} var BundleWidth,BundleAngle,BundlePosition,SinA,Xm,Ym:real; dx,dy,LineWidth,x1,y1,x2,y2,width,height:integer; begin RequiresVersion(1.46); Procedure MeasureBundleOptions {Set to measure length,and angle} GetPicSize(width,height); SetMinorLabel('Position'); SetLineWidth(1); Measure; {get bundle width and angle} BundleWidth:=rLength[rCount]; {park this length here} DrawBoundary; {make the line visible} Procedure FindLineMidPoint; Procedure GetSinA; BundleAngle:=rAngle[rCount]; {park this angle here} if (SinA<0) then BundleAngle:=(rAngle[rCount]-180) else if ((SinA=0) and (dX>0)) then BundleAngle:=180 else BundleAngle:=rAngle[rCount]; MoveTo(Xm,Ym); {move to the midpoint} LineTo(Xm,height); {draw a line straight down} MakeLineRoi(Xm,Ym,Xm,height); SetCounter(rCount-1); {reset the counter so all values are on the same line} Measure; {get the distance from the edge to the hair cell bundle} killroi; BundlePosition:=rLength[rCount]; rLength[rCount]:=BundleWidth; rMinor[rCount]:=BundlePosition; rAngle[rCount]:=BundleAngle; UpdateResults; if BundleWidth<1 then begin PutMessage('This bundle width is less than 1 micron') SetCounter(rCount-1) end; end; Macro 'Left Ear Angle, Position [L]'; {This measures angle, and distance of LEFT EAR stereoacilia from the lower horizontal axis of objects on the tablet or monitor. Draw a line across the stereocilial tips of the hair cell bundle with the straight line tool, then press 'L'. Angles are 0 to 180, or -0 to -180 degrees as drawn clockwise around a hypothetical circle, (left to right as you "face" the tallest row) with 0 implying that the tallest row of stereocilia are facing, and parallel to, the inferior edge, 180 implies that the tallest row faces the superior edge, i.e, positive angles are 0 to pi and negative are pi to 2 pi. Y-axis is not inverted. An error message is returned if the bundle width is less than 1 micron. A submicron bundle usually means that your finger slipped while drawing the line and that measurement should be deleted.} var Xm,Ym,BundleWidth,BundleAngle,BundlePosition,SinA:real; LineWidth,dx,dy,x1,y1,x2,y2,width,height:integer; begin RequiresVersion(1.46); Procedure MeasureBundleOptions {Set to measure length,and angle} GetPicSize(width,height); SetMinorLabel('Position'); SetLineWidth(1); Measure; {get bundle width and angle} BundleWidth:=rLength[rCount]; {park this length here} DrawBoundary; {make the line visible} Procedure FindLineMidPoint; Procedure GetSinA; BundleAngle:=rAngle[rCount]; {park this angle here} if ((SinA=0) and (dX<0)) then BundleAngle:=0 else if((SinA<0) or ((SinA=0) and (dX>0))) then BundleAngle:=(180-rAngle[rCount]) else BundleAngle:=((rAngle[rCount])*(-1)); MoveTo(Xm,Ym); {move to the midpoint} LineTo(Xm,height); {draw a line straight down} MakeLineRoi(Xm,Ym,Xm,height); SetCounter(rCount-1); {reset the counter so all values are on the same line} Measure; {get the distance from the edge to the hair cell bundle} killroi; BundlePosition:=rLength[rCount]; rLength[rCount]:=BundleWidth; rMinor[rCount]:=BundlePosition; rAngle[rCount]:=BundleAngle; UpdateResults; if BundleWidth<1 then begin PutMessage('This bundle width is less than 1 micron') SetCounter(rCount-1) end; end; Procedure FindLineMidPoint; begin GetLine(x1,y1,x2,y2,LineWidth); {get x,y coords. of line ends} dx:=x1-x2; dy:=y1-y2; if dx>=0 then Xm:=x1-((x1-x2)/2) else Xm:=x1+((x2-x1)/2); if dy>=0 then Ym:=y1-((y1-y2)/2) else Ym:=y1+((y2-y1)/2); end; Procedure GetSinA; var Hypot:real; begin Hypot:=sqrt(sqr(dx)+sqr(dY)); {gives the hypotenuse of the angle} if (Hypot=0) then begin PutMessage('Repeat this measurement') SetCounter(rCount-1) exit end; if (Hypot<>0) then SinA:=dY/Hypot; end: Procedure MeasureBundleOptions; begin SetOptions('Minor,Length,Angle'); SetPrecision(2); end; Macro documentation 1 10/9/93 Stereocilia Angle/Position Macros Short cut keys: R, L Purpose: To measure the angle orientation of avian hair cell stereo cilia bundles relative to their distance from the inferior edge of the papilla. What it does: Measures the length of a line drawn along the tallest row of stereoecilia, its angle relative to the horizontal plane, and the distance from the midpoint of that line to the lower edge of the tablet or image. Each measurement leaves behind a line to mark its position. this makes it easier to prevent double measurements. Control: The user sets the scale and all lines must be drawn from left to right, as if one is facing the tallest row of stereocilia. Measurements may be made with prints aligned on the tablet, or with digital images aligned on the monitor. Errors: An error message occurs if the drawn line is less than 1 micron. The user clicks on the "OK" button and redraws the bundle. Any measurements associated with that line are automatically discarded. How it Works: There are two macros, one for the right ear [R] and one for the left ear [L]. Stereocilia normally orient themselves with the tallest row facing toward the apex of the papilla and at an angle to the inferior edge. The angles are measured 0 to 180¡ and -0.1¡ to -179.9¡. 0¡ indicates a tallest row facing, and parallel to the inferior edge while180¡ represents the tallest row facing, and parallel to the superior edge. Positive angles are given for tallest rows facing the apex. Negative ang les are given for tallest rows facing the base. To Use with digitizing tablet: 1. Load "Stereocilia Angle/Position" macro file [Special Menu or "Cmd 9"]; 2. Choose the Line Tool (middle of the right column in the Tool Palette, diagonal line icon); 3. Create a new blank image [File Menu, "New"] or "Cmd N"; 4. Set the scale - a. align the micrograph with its nearest edge aligned against the X-axis of the tablet, and its upper corners taped down to immobilize it- A clear plastic ruler taped to the tablet is useful for setting the X-axis while helping to hold down the micrograph. b. draw the scale bar, holding down the Shift key simultaneously with the cursor button - this will force a straight line along the scale bar; c. get the Set Scale dialog box, double click on the Line Tool, or use Analyze Menu; d. click on Micrometers, then enter the micron length of the scale bar in the Actual Distance box, set the Magnification to 1.0 and click "OK"; 5. re-align the micrograph such that the inferior edge of the papilla is aligned with the X-axis of the tablet (sliding the print margin under the ruler) and tape down its upper corners to immobilize it; 6. Note whether this is a left ear or a right ear, find a stereocilia bundle and determine if the tallest row may be clearly seen; 7. Draw a line over the tips of the tallest row of stereocilia, parallel to the row, drawing from left to right as if you were facing the tallest row with the "staircase" of shorter stereocilia behind the tallest row; 8. Press "R" or "L" as appropriate for the sample; 9. Draw the next row, press the appropriate letter and repeat for the micrograph. Stereocilia orientation may be measured from images on the monitor by aligning the inferior edge of the microscope image with the bottom edge of the screen. Sample Data File Length Position Angle 1 32.98 219 14.09 2 29.07 200 4.32 3 33.38 207.5 8.62 4 38.18 221.5 45.06 5 37.7 222 21.72 6 27.73 222 25.09 7 27.02 231.5 140.53 pk ******************************************************* Wayne Rasband writes 93-10-12 >Could someone give me a hint on how to create a pseudo-color LUT for NIH-Image >given that a specified DN will have a specified color in R, G and B. One way to do this is to write a macro that assigns RGB colors to specified gray values. Here is an example macro that allows you to specify a gray value and the RGB color you want that value to be displayed in. Not that this macro modifies only one of the 256 LUT entries. Also note that LUT entries 0 and 256 can not be modified. They are always mapped to white and black respectively.=20 To run this macro, Copy it to the Clipboard, Open a new text window in Image, Paste, Load Macros, and then select 'Change One LUT Entry=8A' from the Special menu. --wayne macro 'Change One LUT Entry=8A'; var dn:integer; begin dn:=3DGetNumber('Gray Value(1-254):',128); RedLut[dn]:=3DGetNumber('Red(0-255):',255); GreenLut[dn]:=3DGetNumber('Green(0-255):',0); BlueLut[dn]:=3DGetNumber('Blue(0-255):',0); UpdateLUT; end; ********************************************************************************** Glen Macdonald writes 93-07-01 What is the best way to computer the circularity of a cell? Traditionally, our lab has always used the ratio of minor to major axes. However the Measurements Macro file that Wayne includes with Image contains the following formula in a cirularity macro. It was contributed by someone who is nolonger around. Any comments? Given the first 2 factors, perhaps this equation is related to sphericity. rUser1[rCount]:=4*3.14159265*(rArea[rCount]/sqr(rLength[rCount])); Thanks. -Glen ****************************************** This formula derives from the ratio of the area of a circle to the square of the perimerter: A/P^2 = Pi R^2/(4 Pi R^2) = 1/(4 Pi) or 4 Pi A/P^2 = 1 (this ratio is 1 for a circle) This ratio can be fairly small for complicated objects. For example, we found that partially-healed circular wounds in eye tissue can have ratios of 1/5 or 1/7 or less. Kent Reuber, Computational Specialist Biology Department, Brandeis University reuber@hydra.rose.brandeis.edu ******************************************************************************* Coordinated Pan and Zoom 7/16/93 10:36 AM Wayne- Is there a mode for Image that allows me to zoom multiple windows simultaneously and then pan (with the hand tool) several images simulataneously? We use Image to compare the results of different processing algorithms, and this would allow us to roam over several images at once. By the way... I have tried to use the macro language for signal processing, but the automatic scaling of things like subtract and convolve messes me up. I wish I had a few function calls like these: A function for precision gain and offset y = (x + offset1)*gain + offset2 A function for adding two images y = x1 + x2 + offset A function for multiplying two images y = (x1 + offset1)*(x2 + offset2) + offset3 A function for convolution that doesn't decide what the offset and tap scale factor should be: y = conv(x, 'taps_filename') + offset I've tried to implement certain signal processing algorithms (like an NTSC-to-RGB decoder), but I can't seem to get around the automatic scale and offset properties of subtract, multiply and convolve. Am I missing something? Norm Hurst (Mr. MPEG) David Sarnoff Reseach Center ********************************** There is a poorly documented synchronized scrolling feature in Image. To try it out, open two or more windows(they must be the same size), tile them using Tile Windows, zoom in on one of them, then pan one of the images with the option key down. Notice how all the other images zoom and pan to the same relative location. >I have tried to use the macro language for signal processing, but the automatic >scaling of things like subtract and convolve messes me up. I am considering adding a macro function(and corresponding menu command) something like the following: ImageArithmetic('op',' pic1', 'pic2', gain, offset) 'op'('Add', 'Sub', 'Mult', etc.) is the operation to be performed, 'pic1' and 'pic2' are the two images to be operated on. Pixel values resulting from the operation would by multiplied by the gain and then the offset would be added. For convolution, I'm thinking of allowing keywords to be added to the first line of the kernel file to control how scaling is done. --wayne ************************************************************* >Much deleted > is that the values used for thresholding seem to >be somewhat subjective. we have (for our first trial) about 15 images, and >its tough to find good levels other than just saying "that looks good", and >what looks good on frame 1 may not on frame 10. Actually, the routine in Image which does thresholding (or AutoThreshold in macro) are not arbitrary. They are based upon an algorithm from an IEEE journal by Ridler and Calvard (IEEE Transactions on Systems Man and Cybernetics, August 1978). It is basically a histogram based iterative testing technique. I have about two or three other thresholding macros written. Because every persons image is so differing it is difficult to find a thresholding technique that works for everyone. Perhaps the best technique you can employ is simply to apply a linear function between your desired output and the threshold. You can easily develop this in a macro. Your linear function will be created by you by knowing where a set of images should be thresholded. From then on, the same function can be applied to all the rest of your images. Here is some sample code that does this: PROCEDURE HistoThresh; VAR Minimum,Upper,index:integer; Match:real; TotalPixCount,PixCount:integer; BEGIN {Find the total number of pixels} SetOptions('Area, Std. Dev., Mean, Min/Max, Mode'); ResetCounter; RestoreCircle; Measure; Count := rCount; Minimum := rMin[count]; TotalPixCount := rArea[count]; SDeviation := rStdDev[count]; PixCount :=0; {here is a typical linear fucntion} MyScalefactor := 0.57588 + Sdeviation*0.40056; Match := 1.0 - (Sdeviation/100)*MyScaleFactor; {Loop till set percent of histogram population is reached} ShowHistogram; index :=Minimum; repeat PixCount := PixCount + Histogram[index]; PopPercent := PixCount/TotalPixCount; index :=index+1; until (PopPercent >= Match) Threshold := index-1; SetThreshold(Threshold); END; You can also threshold based on the standard of deviation like this: PROCEDURE StdDevThresh; VAR Count, Threshold:integer; Minimum,Upper:integer; StdDev,TheMean,Difference,MultFactor:real BEGIN MultFactor := GetNumber('Enter the number of Std Deviations past the mean to threshold',1.0); ResetCounter; SetOptions('Std. Dev., Mean, Min/Max'); Measure; Count := rCount; StdDev := rStdDev[Count]; TheMean := rMean[count]; Minimum := rMin[count]; Difference := TheMean-Minimum; Upper := Round(TheMean+ Difference); SetDensitySlice(Minimum,upper); ResetCounter; Measure; Count := rCount; StdDev := rStdDev[Count]; Threshold := TheMean+ round(MultFactor*StdDev); SetThreshold(Threshold); Showmessage('Threshold level =',Threshold); END; Mark Vivino NIH/DCRT mvivino@helix.nih.gov *************************************************************************** Wayne Rasband writes 93-05-25 >Does anyone know of a simple method (macro or otherwise) of dumping a >table of actual vs. calibrated grey levels (with actual levels taking >every value from 0 to 255)? I have enclosed a macro(it's from the file "Measurement Macros") that generates a table of actual vs. calibrated values. --wayne macro 'Display Calibration Table'; { Stores 0-255(all possible gray values) in the User1 column and the 256 corresponding calibrated values in the User2 column. Max Measurements must be set to 256 or greater. Use the Export command to export the calibration table to a text file. The two columns will be identical if the image is not calibrated. } var i:integer; v:real; begin RequiresVersion(1.44); SetCounter(256); SetUser1Label('value'); SetUser2Label('cvalue'); for i:=0 to 255 do begin rUser1[i+1]:=i; rUser2[i+1]:=cvalue(i); end; ShowResults; end; ************************************************************************* Norm Hurst writes 93-09-27 : Finding Texture 9/27/93 10:53 AM Mary Beth Seasholtz and NIH-ers- Here is a quick and dirty macro to find textured areas. Be sure to put the file '3x3 HPF' into you NIH-Image folder (or create your own HPF suited to your problem). The '3x3 HPF' file I used is -1 -2 -1 -2 12 -2 -1 -2 -1 It is called once with "Convolve('3x3 HPF');" in the Find Texture [T] macro. There are a couple of parameters you may want to change: In Core(8,127), the first parameter specifies the width of a coring function. The intent of this is to remove small changes that are probably noise. Increasing the '8' removes more noise (and some of your real data as well!). Setting it to zero disables it, but in that case you would just comment it out. I used "Smooth" to spread the data; you might want to try your own low-pass filter with Convolve('my_filter'). Or try a second, cascaded Smooth. If you have any questions, let me know. Let me know if this helps. -Norm Hurst David Sarnoff Research Center 609-734-2925 -------------------------------------------------------------------------- { Requires the file '3x3 HPF' that contains: -1 -2 -1 -2 12 -2 -1 -2 -1 or make your own filter (taps must sum to zero) } procedure Core(w,c:integer) { APPLIES A LUT LIKE THIS: | / | / | / -------c--w----- / | / | / | GOOD FOR REMOVING NOISE FROM HIGH-PASS FILTERED IMAGES } var i,y:integer; begin for i:=1 to 254 do begin y := (255-i)-c; if abs(y) < w then y := 0 else begin if y>0 then y:=y-w else y:=y+w end; RedLUT[i]:=y+c; GreenLUT[i]:= y+c; BlueLUT[i]:=y+c; end; UpdateLUT; ApplyLUT; end; procedure AbsoluteValue(c:integer) { APPLIES A LUT LIKE THIS: /|\ / | \ / | \ ---/---c---\--- / | \ / | \ / | \ } var i,y:integer; begin for i:= 1 to 254 do begin y:= 2*abs(i-c); RedLUT[i]:=y; GreenLUT[i]:= y; BlueLUT[i]:=y; end; UpdateLUT; ApplyLUT; end; macro 'Find Texture [T]' begin Convolve('3x3 HPF'); {or make your own HPF!} Core(8,127); {remove noise from flat areas - increase 1st param. to remove more noise} AbsoluteValue(127); {make all texture one polarity} Smooth; {or Convolve('low pass filter of your choice')} AutoThreshold; end; ************************************************************************* Wayne Rasband writes 93-07-07 >global macro vars? 7/7/93 > 9:21 AM >>>> BTW are global macro variables documented? > >Does this mean I can remember numbers and strings between macro invokations? > >What's the syntax? Global variables are listed at the beginning of the macro file. I have attached an example macro file that uses global variables. --wayne ----------------------------------------- var gvar1,gvar2,gvar3:integer; gstring:string; macro 'Macro 1 [1]'; begin gvar1:=GetNumber('gvar1:',gvar1) end; macro 'Macro 2 [2]'; begin gvar1:=GetNumber('gvar1:',gvar1) end; *************************************************************** >Hi fellow Image users! > I have a question regarding the macro language and the Surface Plot function >in Image 1.50b63. I was wondering if there was a macro hook for the Surface >Plot function (I'm trying to automate the creation of a stack of surface >plots). If anyone has any ideas or suggestions, I'd be really appreciative >for any help. Thanks! Here is a macro that creates a surface plot movie from a stack. It requires V1.50b70, which is available by ftp from zippy.nimh.nih.gov. --wayne macro 'Make Surface Plot Movie '; var i,OldStack,NewStack:integer; N,PlotWidth,PlotHeight:integer; ScaleFactor:real; OneToOne:boolean; begin RequiresVersion(1.50); CheckForStack; SaveState; OldStack:=PicNumber; N:=nSlices; PlotWidth:=GetNumber('Surface Plot Width:',300); PlotHeight:=GetNumber('Surface Plot Width:',300); SetNewSize(PlotWidth,PlotHeight); MakeNewStack('Stack'); NewStack:=PicNumber; SelectPic(OldStack); for i:= 1 to N do begin SelectSlice(1); SurfacePlot; SelectAll; Copy; Dispose; SelectPic(NewStack); if i<>1 then AddSlice; Paste; SelectPic(OldStack); DeleteSlice; end; Dispose; {OldStack} RestoreState; end; *********************************************************************** Wayne Rasband writes 93-09-21 >I am writing a macro to import Tracor Northern/Noran X-ray Maps and Images into >NIH-Image. I can extract the images OK, but am unsure of what to do with the >LUTs that are stored in the original file. Ican read them into image but I >want to apply them to the images I have converted and then save the images as >TIFF files with the LUTs embedded. Has anyone done this with Confocal images >or some other system? Any help would be appreciated. I have looke at a few of >the I/O macors but havent found one that reads LUTs yet. You should be able to Import the LUT as an image and then copy it to Image's LUT. Here is an example macro that shows how to do it. --wayne macro 'Import LUT=8A'; {Imports a 256 x 3 x 8-bit look-up table located 'offset' bytes from the beginning of a file. Use an offset of 32 for LUTs created using Image's Save As command and an offset of 0 for Exported LUTs.} } =20 var offset,i:integer; begin offset:=3D0; {Use 32 for LUTs created using Save As} SetImport('8-bit');=20 SetCustom(256,3,offset); Import(''); {Read LUT as an image} for i:=3D0 to 255 do begin RedLut[i]:=3DGetPixel(i,0); GreenLut[i]:=3DGetPixel(i,1); BlueLut[i]:=3DGetPixel(i,2); end; UpdateLUT; end; *************************************************************** Mark Vinino writes 93-09-21 >LUTs that are stored in the original file. Ican read them into image but I >want to apply them to the images I have converted I have looke at a few of >the I/O macors but havent found one that reads LUTs yet. You don't need a macro. Simply go to Import and say look up table in the dialog. If your file is simply byte data containing nothing but a LUT, then you can import it directly. For example run this macro, export the result as raw image data using the export command, then import the file as a LUT with the import command. I'm not totally sure what you want but running this macro make help you get started. macro 'Create LUT [E]'; var i,Max,Subtraction,Min,Addition:integer; v:real; begin RequiresVersion(1.45); SetNewSize(256,3); MakeNewWindow('LUT values in bytes'); {Make black black} PutPixel(0,255,0); PutPixel(1,255,0); PutPixel(2,255,0); {Make white white} PutPixel(0,0,255); PutPixel(1,0,255); PutPixel(2,0,255); Max := 255; Min := 0; {Red} Subtraction := 0; for i:=1 to 127 do begin PutPixel(i,0,Max-Subtraction); Subtraction := subtraction -2; end; for i:=128 to 254 do begin PutPixel(i,0,0); end; {Green} Subtraction := 0; for i:=1 to 127 do begin PutPixel(i,1,Max-Subtraction); Subtraction := subtraction -2; end; Addition := 0; for i:=128 to 254 do begin PutPixel(i,1,Min+Addition); Addition := Addition +2; end; {Blue} for i:=1 to 127 do begin PutPixel(i,2,0); end; Addition := 0; for i:=128 to 254 do begin PutPixel(i,2,Min+Addition); Addition := Addition +2; end; MakeROI(0,0,256,3); end; Mark Vivino NIH/DCRT mvivino@helix.nih.gov ******************************************************************************** Edward Huff writes 93-05-28 You might find these macros useful (not exhaustively tested, but I don't think they contain any bugs :-). macro 'Make stack same size as front image'; var width, height: integer; name: string; begin SaveState; GetPicSize(width, height); SetNewSize(width, height); name := GetString('New Stack Name', 'stack'); MakeNewStack(name); RestoreState; end; macro 'Move front image to end of newest compatible stack'; var stkn, picn, width, height, w2, h2: integer; compat: Boolean; begin picn := PicNumber; if nSlices <> 0 then begin beep; exit; end; GetPicSize(width, height); stkn := nPics + 1 compat := false; while not compat do begin stkn := stkn - 1; if stkn = 0 then begin beep; exit; end; ChoosePic(stkn); GetPicSize(w2, h2); if width = w2 then if height = h2 then if nSlices <> 0 then if stkn <> picn then compat := true; end; ChoosePic(picn); SelectAll; Copy; ChoosePic(stkn); ChooseSlice(nSlices); AddSlice; Paste; ChoosePic(picn); Dispose; if picn < stkn then stkn := stkn - 1; SelectPic(stkn); SelectSlice(sliceNumber); end; -- Edward J. Huff huff@mcclb0.med.nyu.edu (212)998-8465 Keck Laboratory for Biomolecular Imaging NYU Chemistry Deptartment, 31 Washington Place, New York NY 10003 ************************************************************************ Image has only 8 bits per pixel, ever. To display two colors, you have to go to 4 bits for each color. Instead of scaling the two to 7 bits each, (0 to 127 or 128 to 255), you must scale them both to 0 to 15. Then multiply one by 16 and add the two images. Then you need a lut, which is easily made using the lut arrays (named red[i] etc I think), but remember you can't change white and black. for i := 1 to 255 do begin red[i] := i div 16; green[i] := i mod 16; blue[i] := 0; end; Not tested, getting it to work is an exercise for the reader... Please post a working solution. -- Edward J. Huff huff@mcclb0.med.nyu.edu (212)998-8465 Keck Laboratory for Biomolecular Imaging NYU Chemistry Deptartment, 31 Washington Place, New York NY 10003 ************************************************************************* Wayne Rasband writes 93-06-24 Here are two macros for displaying two images as a red and green composite. The first creates a 24-bit image(with the blue slice blank) and converts it to 8-bit composite color using the RGB to 8-bit Color command. The second scales both image to 0-16, multiplies the second by 16, combines the two by ORing them, and then generates a custom LUT to display the composite image. --wayne macro 'RGB Color Merge'; { Merges a "red" image and a "green" image to create a composite color image by creating a temporary 24-bit image and converted to 8-bits. } var i,w1,w2,h1,h2,rgb:integer; begin SaveState; if nPics<>2 then begin PutMessage('This macro operates on exactly two images.'); exit; end; SelectPic(1); GetPicSize(w1,h1); SelectPic(2); GetPicSize(w2,h2); if (w1<>w2) or (h1<>h2) then begin PutMessage('The two images must have the same width and height.'); exit; end; SetNewSize(w1,h2); SetBackground(255); MakeNewStack('RGB'); AddSlice; AddSlice; rgb:=PicNumber; SelectPic(1); SelectAll; Copy; SelectPic(rgb); SelectSlice(1); Paste; Invert; SelectPic(2); SelectAll; Copy; SelectPic(rgb); SelectSlice(2); Paste; Invert; RGBToIndexed('Custom'); SelectPic(rgb); Dispose; RestoreState; end; macro 'LUT Color Merge'; { Merges a "red" image and a "green" image to create a composite color image. Both images are scaled to 0-15, the second is multiplied by 16 and the two images are ORed and displayed using a custom red-green LUT. } var i,w1,w2,h1,h2,merged:integer; begin SaveState; if nPics<>2 then begin PutMessage('This macro operates on exactly two images.'); exit; end; SelectPic(1); GetPicSize(w1,h1); SelectPic(2); GetPicSize(w2,h2); if (w1<>w2) or (h1<>h2) then begin PutMessage('The two images must have the same width and height.'); exit; end; SetNewSize(w1,h2); MakeNewWindow('Merged'); merged:=PicNumber; SelectPic(1); SelectAll; Copy; SelectPic(merged); Paste; SelectAll; MultiplyByConstant(1/16); ChangeValues(0,0,1); ChangeValues(16,16,15); SelectPic(2); SelectAll; Duplicate('Temp'); MultiplyByConstant(1/16); ChangeValues(16,16,15); MultiplyByConstant(16); ChangeValues(0,0,1); SelectAll; Copy; SelectPic(merged); Paste; DoOr; for i:=0 to 255 do begin RedLut[i]:=(i mod 16)*16; GreenLut[i]:=(i div 16)*16; BlueLut[i]:=0; end; UpdateLut; SelectPic(nPics); Dispose; {Temp} RestoreState; end; ***************************************************************************** Edward Huff writes 93-04-06 To do operations on all frames of a stack, load the "stacks" macro set from the Macros folder. If necessary, add a new macro (and send it to Wayne or the list). procedure CheckForStack; begin if nPics=0 then begin PutMessage('This macro requires a stack.'); exit; end; if nSlices=0 then begin PutMessage('This window is not a stack.'); exit; end; end; macro 'Sharpen'; var i:integer; begin CheckForStack; for i:= 1 to nSlices do begin SelectSlice(i); SetOption; Smooth; SetOption; Sharpen; end; end; If you load the above text, you can sharpen all slices of a stack. If you change the macro name to 'Sharpen [A]' then pressing the A key will sharpen all slices of a stack. Change to 'Sharpen [F1]' to use the F1 function key on extended keyboards. For the other problem, try Options menu Propagate Density Calibration. Load macros is command 9 or on Special menu. ********************************************************************** Wayne Rasband writes 93-03-18 >1. I wrote a macro which opens a LUT then prompts for importing file. I found >out that in this case I have to put the color palette file in the same folder >as where the application NIH Image sits. Is there any way to re-direct the >path in my macro so that it could open a color palette sitting in the "Palette" >folder? You can use paths with the Open macro command. For example, Open('HD400:Images:My Image') Open('HD400:Color Tables:My Color Table') Open('HD400:Outlines:My Outline') You can have a default path by taking advantage of the Open Command's ability to automatically concatenate strings. For example, RequiresVersion(1.48);} {V1.48 added support for string variables} path:=' HD400:My Stuff:'; Open(path,'My Image'); Open(path,'My Color Table'); Open(path,'My Outline'); --wayne ************************************************************************ >Date: Tue, 10 Aug 93 15:52:11 PDT >From: karl%pluto.dnet@loni.ucla.edu >To: "hammer@wana.pbrc.Hawaii.Edu"%GATE.dnet@loni.ucla.edu >Subject: Re: Posting on Image list > >To: Anyone interested in my grain counting macros > >I am pleased that some people are interested in the grain counting >macros that I wrote. As I don't do grain counting myself, they have not >been tested in 'real world' image analysis, and I have had limited >feedback to help me refine them. I welcome feedback for debugging and >finding missing essential features, BUT I will be on vacation until Aug. >28 and so cannot respond immediately.The macros are relatively simple >and straightforward, so feel free to edit them. There are four macros >that ASSIST in grain counting, but it is far from automatic. > >You must set the Max Measurements to 1000 in the Analyze Options dialog >box, and can make a maximum of 999 measurements. Use Set Scale to get >areas and densities in real units. > >GRAINS MUST BE DISTINGUISHABLE BY GRAY SCALE ALONE! Image enhancement >and editing is to be done beforehand. > >Fix LUT - changes black(255) and white(0) pixels to very dark gray(254) >and very light gray(1), so that all pixels are within the LUT. > >Calibrate Area per Grain - initializes a few things, and expects that >you have selected several 'typical' grains for an average calibration >with a selection tool. It asks for the number of grains selected, >calculates and stores the calibration. > >Count Grains in Area - expects that you have selected your area of >interest with a selection tool, and previously calibrated the grains. >Grays out and numbers your area, and records area, number of grains, and >density (grains/unit area) in the results table. > >Verify Calibration - Works like 'Calibrate Area per Grain' but does not >change anything. Simply reports previous calibration and verification. >If the discepency is too large you must re-'Calibrate Area per Grain'. >THIS RESETS THE RESULTS TABLE SO >>>!!SAVE IT FIRST!!<<< > > >Have fun, and I hope their useful. >Reply to: (after Aug. 28) >karl@loni.ucla.edu >regardless where this message appears to come from. > >--------MACRO FILE FOLLOWS---------------------------------------------- >{Grain Counting Macros } >{written by Karl Beykirch 1993} > >macro 'Fix LUT [F5]'; >begin > ChangeValues(255,255,254); > ChangeValues(0,0,1); >end; > >macro 'Calibrate Area per Grain [F6]' >var > pix,part,i,foo,width:integer; >begin > RequiresVersion(1.49); > GetRoi(foo,foo,width,foo); > if width=0 then begin > PutMessage('You have not selected an area!!'); > exit; > end; > SetOptions('Area, User1, User2'); > SetUser1Label('Grains'); > SetUser2Label('Density'); > SetPrecision(4); > SetForegroundColor(255); > SetBackgroundColor(0); > Copy; > KillRoi; > MakeNewWindow('Temp'); > Paste; > SetDensitySlice(255,255); > Measure; > GetResults(pix,foo,foo,foo,foo); > part:=GetNumber('Enter Number of Grains Selected:',1); > Dispose; > ResetCounter; > rUser2[1000]:=pix/part; > SetNewSize(275,50); > MakeNewWindow('Grain Size Calibrated'); > MoveTo(10,25); > Write('Grain size calibrated as ',rUser2[1000]:5:2,' pixels / grain >.'); > SetNewSize(640,480); >end; > >macro 'Count Grains in Area [F7]' >var > pixpart:real; > area,pix,i,foo,left,top,width,height:integer; >begin > if rUser2[1000]=0 then begin > PutMessage('Grain size is NOT calibrated!!'); > exit; > end; > GetRoi(left,top,width,height); > if width=0 then begin > PutMessage('You have not selected an area!!'); > exit; > end; > SetBackgroundColor(0); > Copy; > i:=rCount; > SetForegroundColor(128); > Fill; > SetForegroundColor(255); > MoveTo(left+(width/2),top+(height/2)); > Write(i+1); > KillRoi; > MakeNewWindow('Temp'); > Paste; > SetDensitySlice(255,255); > Measure; > Dispose; > rUser1[i+1]:=rArea[i+1]/rUser2[1000]; > SetCounter(i); > MakeNewWindow('Temp'); > Paste; > SetThreshold(1); > Measure; > GetResults(pix,foo,foo,foo,foo); > Dispose; > rUser2[i+1]:=rUser1[i+1]/pix; >end; > >macro 'Verify Calibration [F8]' >var > verif:real > pix,part,i,foo,width:integer; >begin > GetRoi(foo,foo,width,foo); > if width=0 then begin > PutMessage('You have not selected an area!!'); > exit; > end; > if rUser2[1000]=0 then begin > PutMessage('Grain size is NOT calibrated!!'); > exit; > end; > SetForegroundColor(255); > SetBackgroundColor(0); > Copy; > KillRoi; > MakeNewWindow('Temp'); > Paste; > SetDensitySlice(255,255); > i:=rCount; > Measure; > GetResults(pix,foo,foo,foo,foo); > SetCounter(i); > part:=GetNumber('Enter Number of Grains Selected:',1); > Dispose; > verif:=pix/part; > SetNewSize(275,50); > MakeNewWindow('Grain Size Verification'); > MoveTo(10,15); > Writeln('Grain size calibrated as ',rUser2[1000]:5:2,' pixels / grain >.'); > Writeln('Verification calibrated as ',verif:5:2,' pixels / grain .'); > SetNewSize(640,480); >end; > > ************************************************************************** >I am new at macros, and can't seem to get this simple macro to work. It is >supposed to density slice a group of open images. The same macro works to >threshold all the open images, replacing SetDensitySlice() with >SetThreshold(). The SetDensitySlice() command works OK for a single image. > What am I doing wrong? > >macro 'density slice all [F10]' >Var > i:integer >Begin > for i:=1 to nPics do begin > SelectPic(i) > SetDensitySlice(83,216) > end >end This macro doesn't work because, unlike thresholds, Image doesn't store the density slice with the image. In other words, there is only one density slice, but there is a threshold for each image. Also, unlike thresholds, density slicing is turned off when you switch windows. You can turn it back on, however, by double clicking on the LUT tool. The differences between density slicing and thresholding should become clearer if you play with the following macros. macro 'Density Slice All' Var i:integer Begin for i:=1 to nPics do begin SelectPic(i); SetDensitySlice(i*50+50,i*50+100); end end; macro 'Threshold All' Var i:integer Begin for i:=1 to nPics do begin SelectPic(i); SetThreshold(i*50+50); end end; --- wayne ---- ************************* Glen Macdonald wrote 93-06-23 I ran into the same problem and overcame it after discovering that Image retains the previous density slice setting between images. at least when sequentially capturing and measuring images or manually selecting image windows. To use the macro below, I set the density slice based on an initial image, then outline a cell, or let it take the whole window, and run the macro for each roi. It turns off density slicing, measures total area, then turns density slicing back on and measures the area of pixels in the slice interval. It doesn't matter if you have density slicing on or off when running the macro. Hope this helps. -Glen macro 'Area Fraction of Density Slice [M]'; {Measures Whole cell and density sliced area, stores them into the Area and Major Axis columns and puts the decimal fraction of area in the density into the Minor Axis column.} var i,n:integer; CellArea:real; {temp variable to hold results between Measurements} AreaFrxn:real; {this variable defines the fraction of area in the density slice} begin SetOptions('Area,Major,Minor'); SetPrecision(3); SetMajorLabel('Slice Area'); SetMinorLabel('Frxn Area'); SetDensitySlice(0,0);{turn density slice off for total cell outline area} measure;{put total cell area into rArea[rCount]} CellArea:=rArea[rCount];{save this area value as the variable CellArea} SetDensitySlice(255,255); {regain density slice} SetCounter(rCount-1);{set counter back because doing two measurements for single cell} measure; {put this density sliced area into rArea[rCount]} rMajor[rCount ]:=rArea[rCount ]; {put the density sliced area in rMajor column} rMinor[rCount]:=rArea[rCount]/CellArea; rArea[rCount ]:=CellArea;{move the total area to correct location} updateResults; {write everything to the Measurement box} SetDensitySlice(0,0); {return to greyscaleto allow accurate encircling next cell} DrawBoundary;{leave a permanent outline of areas measured, but deletes pixels in the line from repeated measurements, changing subsequent values} end; ************************************************************* Here is an example macro that outputs a 30 hz TTL pulse train on pin 1 of the Scion utility port. --wayne 93-09-20 macro 'Generate Pulse Train' {Outputs a 30Hz pulse train on pin 1(Data Output bit 3)} {of the Scion LG-3's utility connector.} var NextTicks,inc:integer; begin inc:=1; {1/60 sec.} SetCursor('watch'); NextTicks:=TickCount+inc; repeat scion[4]:=BitOr(scion[4],8); repeat until TickCount>=NextTicks; NextTicks:=NextTicks+inc; scion[4]:=BitAnd(scion[4],7); repeat until TickCount>=NextTicks; NextTicks:=NextTicks+inc; until button; end; ***************************************************************************** >Mostly we use Sort Palette to select objects based on color . The RGB to HSV >command is very cool and very useful, however, it does not work if we only >have the composite image and not its RGB pieces. Why can't you convert the composite color image to RGB and then use the RGB to HSV command? >Also, we miss the Change Values command.... Here is a macro the does more or less what the old Change Values command did. macro 'Change Values'; var v1,v2:integer; begin v1:=GetNumber('Change pixels with this value:',255); v2:=GetNumber('to this value:',254); ChangeValues(v1,v1,v2); end; --wayne 93-07-22 **************************************************************** > However, if I try to use the 'Import BioRad MRC 600 Z Series...' on a >single image or a z-series, I get the following error alert: > > "This does not seem to be a BioRad Z Series Image File." > >Am I right to set the width to 768, the height to 512 and the offset to 76, >when prompted by the 'edit' button in the 'Import BioRad MRC 600 Z >Series...' dialog box? No, that is the problem. Leave the settings alone. > >Any advice would be helpful at this point. Most of our data is in the form >of z-series files, so right now, I can't do much with it. Is there a >document somewhere in 'pub/nih-image' on zippy.nimh.nih.gov that explains >how to use the 'Import BioRad MRC 600 Z Series...' macro? I couldn't find >one. Using Image 1.50, choose File/Open and open the "Input/Output Macros" file in the Macros folder. Then choose Edit/Find and enter "does not seem" into the dialog box. You will find this text: RequiresVersion(1.50); width:=512; height:=1; offset:=0; SetImport('8-bit'); SetCustom(width,height,offset); Import(''); {Read header} width:=GetPixel(0,0)+GetPixel(1,0)*256; height:=GetPixel(2,0)+GetPixel(3,0)*256; nImages:=GetPixel(4,0)+GetPixel(5,0)*256; Dispose; hdrsize:= 76; picsize:=width*height; if (width<128) or (width>2048) or (height<128) or (height>2048) or (nImages<1) or (nImages>256) then begin PutMessage('This does not seem to be a Biorad MRC 600 Z Series file.'); As you can see, the macro is figuring out the proper width, height, and offset. The Import with a null string argument is the source of the dialog box. You are supposed to select the file, but NOT to change the custom import parameters. The reason it failed is that you entered a nonzero offset. This caused image to skip over the bytes at the beginning of the file which contain the actual width, height, and number of images. Image then tried to use the first pixel values from your image as the width, etc. -- Edward J. Huff huff@mcclb0.med.nyu.edu (212)998-8465 Keck Laboratory for Biomolecular Imaging NYU Chemistry Deptartment, 31 Washington Place, New York NY 10003 ****************************************************************************** Norm Hurst writes 93-10-04 Variance macro 10/4/93 11:24 AM >>>Anyone have a macro that calculates a variance image? I assume you want the "instantaneous" variance, the variance of a pixel in its neighborhood. var(i) = (x(i) - avg(x))^2/(N-1) where avg(x) is the average of values in a neighborhood (say 5x5) around a pixel x(i), and N is the number of pixels in the neighborhood (25). Let's disregard the /(N-1) operation for now -- it's merely a scaling operation. x(i) - avg(x) can be found by convolving with an appropriate filter. Here is the filter for the 5x5 case: -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 24 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 The squaring operation can be done with a parabolic LUT. This LUT can include the scaling operation for those who need calibrated results (modify the argument to "Square" to be other than 1.0 to scale the LUT). The macro "Find Variance [V]" is below. --------------------- { Requires the file '5x5 mean diff' to contain: -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 24 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 This is an impulse filter (all zeros with a 1 in the middle) minus a 5x5 average (5x5 1's divided by 25), then scaled so the smallest tap is 1 (i.e. times 25). } procedure Square(scale:real) { APPLIES A PARABOLIC LUT} var i,y:integer; begin for i:= 1 to 254 do begin y:= (i-127)*(i-127)*scale/64.25; if y > 255 then y:=255; RedLUT[i]:=y; GreenLUT[i]:= y; BlueLUT[i]:=y; end; UpdateLUT; ApplyLUT; end; macro 'Find Variance [V]' begin Convolve('5x5 mean diff'); {impulse minus 5x5 average} Square(1.0); {Adjust argument to scale the LUT} end; **************** Here is a version of Norm Hurst's variance macro that has the convolution filter inline. To get inline filtering to work right, I had to add width and height arguments to the NewTextWindow command and to fix the Dispose command so it works with text windows. These fixes are in V1.53b9 on zippy.nimh.nih.gov. --wayne procedure Square(scale:real) { Applies a parabolic LUT} var i,y:integer; begin for i:= 1 to 254 do begin y:= (i-127)*(i-127)*scale/64.25; if y > 255 then y:=255; RedLUT[i]:=y; GreenLUT[i]:= y; BlueLUT[i]:=y; end; UpdateLUT; ApplyLUT; end; procedure ImpulseFilter; {This is an impulse filter (all zeros with a 1 in the middle) minus a 5x5 average (5x5 1's divided by 25), then scaled so the smallest tap is 1 (i.e. times 25).} begin RequiresVersion(1.53); NewTextWindow('5x5 mean diff',150,140); writeln('-1 -1 -1 -1 -1'); writeln('-1 -1 -1 -1 -1'); writeln('-1 -1 24 -1 -1'); writeln('-1 -1 -1 -1 -1'); writeln('-1 -1 -1 -1 -1'); Convolve(''); Dispose; end; macro 'Find Variance [V]' begin ImpulseFilter; {impulse minus 5x5 average} Square(1.0); {Adjust argument to scale the LUT} end; ************************************************************************* Here is a sample macro using WaitForTrigger. It is part of a macro package which allows the user to click over objects to be counted, leave a colored marker behind, bin the counts into a variable. Counting for each category is terminated by clicking above or to the left of the image. At the end of counting (there may be many categories of objects) another macro fills the appropriate array with the total count for this category, in the Count1 variable, and resets the counter. The results are recorded with a single line for each sample. Each line conatains sample id., parameters, and counts in categories. macro 'Count First Label [1]'; begin getMouse(x,y); Count1:=0; SetForegroundColor(1); repeat while (x>0) and (y>0) do begin WaitforTrigger; getMouse(x,y); Measure; Count1:=Count1+1; MakeOvalRoi(x,y,8,8); Fill; end; if (x<0) or (y<0) then do begin count1:=count1-1; UpdateResults; SetCounter(clickCount); beep exit; end; A WaitForButton or a Wait(Button) would by pass the need for the capture trigger to serve double duty. As far as using Apple calls, sure I suppose I can figger out how to get them if I have to, but my question was simply why does the WaitForTrigger behave differently now. > Glen Macdonald writes: > > >I have been using WatiForTrigger in macros that need to act upon a mouse > >click. > >Thle Button macro command hasn't worked in quite the way I needed. While > >the macros work fine with 1.49b5 on a system with a frame grabber card, > >most of my development is on a Mac without a capture card (our Image > > > It's difficult to understand what exactly you want from the call. If you > are not going to be doing image capture, what exactly do you get from > making the call? > If the button call does not do the job, apple has ways to detect everything > including mouse-ups, mouse-downs, or whatever. See the apple button calls > as well as waitnextevent and getnextevent. You could use these in a > usercode call from a macro to detect something. > > Mark > > ******************* Edward J. Huff writes 93-04-08 It appears that when there is no framegrabber, the "WaitForTrigger;" macro call should be replaced with "repeat until button;" to avoid the alert which was added recently. Here is the Button macro function (macros1.p): ButtonC: begin ExecuteFunction := ord(Button); FlushEvents(EveryEvent, 0); end; Here is the wait for trigger macro command (macros1.p): TriggerC: WaitForTrigger; Here is the WaitForTrigger Pascal procedure (camera.p): procedure WaitForTrigger; begin StopDigitizing; ShowWatch; if FrameGrabber = QuickCapture then begin ControlReg^ := BitAnd($82, 255); {Wait for external trigger and capture one frame} repeat until (ControlReg^ >= 0) or Button; {Wait for it to complete} end else begin PutMessage('External triggering requires a QuickCapture card.'); macro := false; end; end; In version 1.45, the function was: procedure WaitForTrigger; begin StopDigitizing; ShowWatch; if FrameGrabber = QuickCapture then begin ControlReg^ := BitAnd($82, 255); {Wait for external trigger and capture one frame} repeat until (ControlReg^ >= 0) or Button; {Wait for it to complete } end else repeat until Button; end; Thus, in a macro, replace WaitForTrigger; with repeat until button; The only difference is that with this form, there is a flushevents call, and also whatever else is done in the Macro processor, e.g. a checkevents call. ********************* Couldn't you use the following procedure instead of WaitForTrigger? procedure WaitForButton; begin repeat until button; repeat until not button; end; The second repeat loop prevents "auto-repeating" if the mouse button is held down too long. Without it, WaitForButton and WaitForTrigger should behave the same way. --wayne 93-04-15 ******************************************************************* Edward J. Huff writes 93-08-31 Use Image 1.44Markup on zippy.nimh.nih.gov in /pub/nih-image/contrib. Here is a sample macro that used the autooutline macro command which I put into that version: procedure putLengths; var aoy: integer; v, n, l, a: integer; begin IncludeInteriorHoles(false); WandAutoMeasure(false); SelectPic(MarkupPicNumber('mark')); KillRoi; ChangeValues(255, 255, 254); ResetCounter; measure; for v := 1 to 6 do begin TextPut(txb, txo, ' ', histogram[v]); rUser1[v + 20] := histogram[v]; end; for v := 1 to 6 do begin n := 0; l := 0; if rUser1[v + 20] <> 0 then begin ChangeValues(v, v, 255); aoy := 0; SetThreshold(255); while AutoOutline(aoy) do begin ChangeValues(255, 255, v); SetCounter(v - 1); Measure; l := l + rLength[v]; n := n + 1; SetThreshold(255); end; SetThreshold(-1); end; rLength[v] := l; rUser1[v] := n; end; KillRoi; SetThreshold(-1); for v := 1 to 6 do begin TextPut(txb, txo, ' ', rLength[v] * 500); end; for v := 1 to 6 do begin TextPut(txb, txo, ' ', rUser1[v]); end; end; This macro found all regions marked with one of the 6 "extra" colors (pixel values 1 to 6), selected all contiguous pixels using the wand tool macro, and measured it. It then erased that group and looked for another, and added the results up for noncontiguous pixels. ************************************************************************** The restriction quoted in the Macro appendix of the 1.51 manual (the last one I looked in) states: ---- Restriction The else clause in IF THEN ELSE statements must be either a simple statement or a BEGIN...END statement. For example, IF odd(i) THEN i:=1+1 ELSE IF odd(j) THEN j:=j+1 should be replaced by IF odd(i) THEN i:=1+1 ELSE BEGIN IF odd(j) THEN j:=j+1 END ---- This is not enough, as the following sample (among many possible) illustrates: macro 'while if then begin'; var i: integer; begin i := getnumber('1-> msg 1 2 3, 0-> msg 3',0); while i = 1 do if true then begin putmessage('got here 1'); putmessage('got here 2'); i := 0; end; putmessage('got here 3'); end; When i is zero, the output is "got here 2" and the macro exits at the end before the "got here 3". The while statement finds no "begin" after the do and then skips to the next semicolon, which is wrong. -- Edward J. Huff huff@mcclb0.med.nyu.edu (212)998-8465 Keck Laboratory for Biomolecular Imaging NYU Chemistry Department, 31 Washington Place, New York NY 10003 *********** >macro 'while if then begin'; >var > i: integer; >begin > i := getnumber('1-> msg 1 2 3, 0-> msg 3',0); > while i = 1 do > if true then begin > putmessage('got here 1'); > putmessage('got here 2'); > i := 0; > end; > putmessage('got here 3'); >end; > > >When i is zero, the output is "got here 2" and the macro exits at the end >before the "got here 3". The while statement finds no "begin" after the >do and then skips to the next semicolon, which is wrong. The 1.53b40 beta now on Zippy runs this macro correctly. I put in Ed Huff's bug fixes from ImageMarkup 1.44, which correct most problems with nested IF, WHILE, REPEAT and FOR statements. However, as a result of these fixes, the interpreter now requires semicolons between statements and no longer ignores some misplaced characters. --wayne ************************************************************************************** >We are using image with a dt2255 board. One of our main objectives is to >monitor in real time (at least as fast as possible ~3-10 fps) the spatial >profile of our laser beam. The 2-d live capture is great, but we would like >to have live profiles also shown. Cursor crosshairs would be used to pick >a row and column to show the profile. Has anyone done this already? If >anyone is familiar with the Spiricon beam profilers, we want to duplicate >some of these features. Any help or comments would be greatly appreciated. Here is a macro that does continuous 1-d profile plots. Unfortunately, it's not very fast. To get 3-10 fps you will probably need to write a custom UserCode routine in User.p. "Inside Image" has some information on how to go about doing this. --wayne macro 'Dynamic 1-D Plot'; { Displays a dynamic 1-d plot of a line in the image while the image is being captured. You most first create a line selection in Camera window. The macro works best if you first to a Plot Profile and move the Plot window so it doesn't cover the Camera window. You may have to shrink the Camera window. Hold down the mouse button to terminate. } var x1,y1,x2,y2,LineWidth:integer; begin GetLine(x1,y1,x2,y2,LineWidth); if x1=-1 then begin PutMessage('Create a straight line selection in the Camera window'); exit; end; SetPlotScale(0,255); repeat Capture; if button then exit; MakeLineRoi(x1,y1,x2,y2); PlotProfile; until button; end; ****************************************************************************** >Imagers, > > >I have binary images that consists of a large number of small objects. I >would like to replace each object with one pixel. It would be nice if it >was at the x-y center of gravity, but it isn't necessary. Can NIH-Image >do this? Does this macro do what you want to do? Or is separation of touching objects also a part of the problem? macro 'mark centre'; var i:integer; begin Duplicate('Centre'); SetScale(0,'pixels'); AnalyzeParticles; SelectAll; Clear; For i:=1 to rCount do putPixel(rX[i],rY[i],255); end; __________________________________________________________________________ Jan Lasse Eilertsen Tlf:(work) +47 7 593983 Dept. of Inorganic Chemistry Tlf:(priv) +47 7 561429 The Norwegian Institute of Techology Sem Saelands vei 12 N-7034 Trondheim E-mail: eilerts@kjemi.unit.no __________________________________________________________________________ ********************************************************* >Hi >Does anyone know how to use tools like the "magnifying glass" or "the >Scrolling tool" without exiting af macro? >I have a macro that detects several kordinats bye the press of the mouse. I >would like to be able to use the magnifying glass while specifying each >point. I hae tried using Exit in the macro, but it makes it inposible to >return to the macro at the point where I droped out. >What Can I Do.............. Try using a key press instead of a mouse press. I have included a set of macros that use key presses to score two types of cells. --wayne 93-11-26 -------------------------------------------- {Use this set of macros to manually scoring two types of cells. Counts are kept in two global variables and displayed in the Values window. } var UCount, LCount:integer; {Global variables, initially zero} procedure ShowCellCounts; begin ShowMessage('U. Count: ', UCount:1,'\', 'L. Count: ',LCount:1,'\' 'Total : ',UCount+LCount:1); end; macro 'Reset Cell Counts'; begin UCount:=0; LCount:=0; ShowCellCounts; end; procedure MarkCell(c:string); {Draws a character in the current foreground color.} var x,y:integer; begin SetFont('Monaco'); SetFontSize(9); SetText('Right Justified'); GetMouse(x,y); MoveTo(x+4,y); Write(c); end; macro 'Score Unlabeled Cell [1]'; begin UCount:=UCount+1; MarkCell('*'); ShowCellCounts; end; macro 'Score Labeled Cell [2]'; begin LCount:=LCount+1; MarkCell('+'); ShowCellCounts; end; ******************************************************************** With the current version of NIH Image, you can save a text file created by a macro from within that macro. I do not remember the version for which this became possible, but: Activate the named text window, then issue a Save; command. The first time the file is saved, you will get a dialog box with the name of the text window as the suggested filename in the current path. Subsequent saves of that file go unprompted (same as other "save" behavior). I use this to create reports from a series of files. I tacked a section of macro code to the end of this message which I use to create the above-mentioned report. It contains globally- and locally-defined variables for names. Bill ========================== Bill Heeschen / Analytical Sciences - Materials Characterization 1897-D Building / The Dow Chemical Company Midland, MI 48667 U.S.A. phone: (517)636-4005 fax: (517)636-5453 Email: waheeschen@dow.com ========================== If (ReportFlag = 0) then begin GetTime(year,month,day,hour,minute,second,dayofweek); ReportWindow:=concat('Report_',year:4:0,'_',month:2:0,'_',day:2:0,'_',hour:2:0,' _',minute:2:0); PutMessage('Reports being written to ',ReportWindow,'. Do not close this window until quitting from NIH Image.'); NewTextWindow(ReportWindow); Writeln('Data from images analyzed starting ',year:4:0,'/',month:2:0,'/',day:2:0,' at ',hour:2:0,':',minute:2:0); ReportFlag:=1; end; SelectWindow(ReportWindow); Writeln('Summary for ',CurrName); Writeln('Threshold limits ',LowThresh:3:0,'-',UpThresh:3:0); Writeln('Hole Area Fraction: ',AreaFrac:5:2,' %'); Writeln('Total Holes Counted: ',n:4:0,' in ',Area:7:0,' mm^2'); Writeln('D10 diameter: ',D10Diam:5:3,' mm'); Writeln('D32 diameter: ',D32Diam:5:3,' mm'); Writeln(''); Save; ResetCounter; ************************************************************************ Here is an example of a macro that saves text to a file. Starting with v1.54b72, the SaveAs command also works for saving text. --wayne macro 'Save Results to Text File'; {This is an example of how to save results to a text file.} begin Measure; NewTextWindow('My Results'); writeln('Area=',rArea[rCount]:1:3); writeln('Mean=',rMean[rCount]:1:3); Save; end; ************************************************************* >I also have large images to work on (12000*1000 pixels) and find not being able >to scroll along my image frustrating at times. If anyone comes up with a >solution let me know. Auto-scrolling is on my list of things to do. In the meantime, you can use the enclosed macros to make rectangular selections larger than the window. >Another problem I have encountered is when measuring large objects in my >images. I get a message "cannot measure greater than 4096 pixels". > >Is there anyway of increasing this limit or getting around it in some way? >Also what causes the limit? Is it an inherent limitation of the programe or >programe language? NIH Image uses line buffers that are 4096 pixels long. You could try increasing the size of these buffers, but you would probably start getting compiler errors complaining about exceeding the 32K limit for local variables and/or records. --wayne 93-12-13 macro 'Define Upper Left [1]'; var x1,y1,x2,y2,LineWidth:integer; begin GetLine(x1,y1,x2,y2,LineWidth); if x1<0 then begin PutMessage('Click with line selection tool to define upper left corner of ROI.'); exit; end; RoiLeft:=x1+(x2-x1)/2; RoiTop:=y1+(y2-y1)/2; end; macro 'Define Lower Right and Create ROI [2]'; var x1,y1,x2,y2,LineWidth:integer; begin GetLine(x1,y1,x2,y2,LineWidth); if x1<0 then begin PutMessage('Click with line selection tool to define lower right corner of ROI.'); exit; end; RoiRight:=x1+(x2-x1)/2; RoiBottom:=y1+(y2-y1)/2; if (RoiLeft=RoiRight) and (RoiTop=RoiBottom) then begin PutMessage('Upper left and bottom right are the same.'); exit; end; MakeRoi(RoiLeft,RoiTop,RoiRight-RoiLeft,RoiBottom-RoiTop) end; ************************************************ I have included a macro that finds the spatial scale. There should not be any loss of precision. I will consider adding a "GetScale" macro command to provide a simpler way to get the scale and also a way to get the unit of measurement. --wayne 93-11-17 macro 'Compute Spatial Scale'; var scale:real; begin MakeLineRoi(0,0,100,0); Measure; KillRoi; Scale:=100/rLength[rCount]; if scale=1 then PutMessage('Image is not spatially calibrated') else PutMessage('Scale=',scale:1:4,' pixels/unit'); end; ******************************************************************** 93-12-10 >I have a set of operations that I use on a large number of files, and for >which I've written a macro. I'd like to create a text file listing the >names of all of the image files on which the macro should operate, but I >don't know how to have NIH-Image get the filenames (one per line) from >the text file. The macros section of the manual discusses string operations, >but I haven't figured out how to accomplish the above using any of the >examples or macros that I've ftp'd. Can anyone point to a solution? > >Thanks... > >Bill Christens-Barry One way is to put all of the file names into the macro file, like this: procedure lookup(n: integer); begin if n = 1 then name := 'file name 1'; if n = 2 then name := 'file name 2'; ... etc... end; macro 'use a file'; var name: string; i: integer; begin i := GetNumber('file number',1); Lookup(i); Open(name); end; Otherwise, this will require addition of a user string function macro extension. Adding such functions is reasonably easy if you have Think Pascal 4.0.1 and get the UMX version of image 1.52 which is on zippy.nimh.nih.gov in /pub/nih-image/contrib. Ok, today I will upload a newer version, which has a load macros command, a neat pair of functions that get around the "global variables reset to zero on load macros" problem (setMemo('name',value) and getMemo('name')), the 16 bit unsigned add and subtract image routines now put 2-4=0 instead of 2-4=65533, and a few other changes like some gray scale morphology routines.... Instructions for adding user macro extensions are (I think) pretty complete and are found in UMacrodef.p. I just added an "import onto window" macro which permits me to display a movie of 16 bit 640x480 images including conversion from 16 to 8 bits before display at a reasonable rate of several frames per second (It was fast enough and I didn't measure the rate) on a Quadra 950. Most of the delay before was caused by the repeated creation and deletion of a new window for the 16 bit image which looks like junk anyway and flashed in front of the display window. I will merge this with Image 1.53 sometime after source becomes available... -- Edward J. Huff huff@mcclb0.med.nyu.edu (212)998-8465 Keck Laboratory for Biomolecular Imaging NYU Chemistry Department, 31 Washington Place, New York NY 10003 ************************************************************** A future version of NIH Image will have a GetScale macro command. In the meantime you can compute the scale using the method used by the enclosed macro. --wayne 93-12-14 macro 'Compute Spatial Scale'; var scale:real; begin MakeLineRoi(0,0,100,0); Measure; KillRoi; Scale:=100/rLength[rCount]; if scale=1 then PutMessage('Image is not spatially calibrated') else PutMessage('Scale=',scale:1:4,' pixels/unit'); end; ************************************************************** Mark Vinino writes 93-11-11 >data - how do you line up the sections before you do the reconstruction? There is a simple macro for manual alignment of data in the User-macro folder on zippy. The macro is limited to manual translational alignment. It is difficult to do rotational alignment. Macro is seen below. {Macros for performing simple visual translational image registration} {Modified 3/1/93} var ImageToRegister, RegistrationImage:integer; OrigTop,OrigLeft,OrigWidth,OrigHeight,stage:integer; macro 'Define Image to Register [1]'; begin RequiresVersion(1.48); If npics <> 2 then begin Putmessage('You must have exactly two images open. One to register to, the other to register.'); exit; end; GetROI(OrigLeft,OrigTop,OrigWidth,OrigHeight); if OrigWidth=0 then begin PutMessage('Use Select All or create an ROI on the image you want registered.'); exit; end; ImageToRegister := PicNumber; ShowPasteControl; Copy; NextWindow; RegistrationImage:=PicNumber; Showmessage('Run the second macro.'); RestoreRoi; Stage:=1; end; macro 'Register [2]'; begin if stage<>1 then begin PutMessage('Use the Define Image to Register macro first.'); exit; end; Showmessage('Move the ROI to the visual registration point and run the third macro.'); Paste; SetOption; DoXOr; stage:=2; end; macro 'Create registered image [3]'; var MovedTop,MovedLeft,MovedWidth,MovedHeight:integer; OrigMovedTop,OrigMovedLeft,OrigMovedWidth,OrigMovedHeight:integer; NewTop,NewLeft,NewWidth,NewHeight:integer; width, height:integer; BEGIN if stage<>2 then begin PutMessage('Use the Define and Register macros first.'); exit; end; Undo; GetPicSize(width, height); SelectPic(RegistrationImage); RestoreRoi; GetROI(Movedleft,Movedtop,Movedwidth,Movedheight); if MovedTop < 0 then begin OrigMovedTop := OrigTop-Movedtop; NewTop := 0; OrigMovedHeight := OrigHeight + MovedTop; NewHeight := OrigHeight + MovedTop; end else begin OrigMovedTop := Origtop; NewTop := MovedTop; OrigMovedHeight := MovedHeight; NewHeight := MovedHeight; end; if Movedleft < 0 then begin OrigMovedLeft :=OrigLeft-Movedleft; NewLeft := 0; end else begin OrigMovedLeft := OrigLeft; NewLeft := MovedLeft; end; if (Movedleft + MovedWidth) > width then begin OrigMovedwidth := width-Movedleft; NewWidth:= width-Movedleft; end else begin OrigMovedWidth := MovedWidth; NewWidth:= MovedWidth; end; SelectPic(ImageToRegister); KillROI; MakeROI(OrigMovedLeft,OrigMovedTop,OrigMovedwidth,OrigMovedheight); Copy; SetNewSize(width,height); MakeNewWindow('Registered'); MakeROI(Newleft,Newtop,Newwidth,Newheight); Paste; Showmessage('The ROI portion of your original image has been copied to the registered image.'); END; ****************************************************************** Concerning my message of Sun Nov 14 17:47:53 1993, Glen Prusky writes: > I have tried to use this macro and it does not seem to work. The two macro > headers show up under the special menu correctly, but the macro always > crashes. Also, I presume that under AverageFrames; I can define the > parameters. eg. number of frames to be averaged or integrated. When I have > tried this it does not seem to do it right. I have gone over the macros and > I can't seem to figure out what the problem is. Can anyone help? Hi Glen, as the author of the original message it's probably my job to answer. I refer you to Wayne Rasband's replies to my message, dated Fri 5 Nov 1993 12:59:09 and 14:15:16. (Edited versions appear below.) Briefly: in response to my note, Wayne added a cool new PasteLive command in v1.53b42, so you'll need to download a version after this. Try the new example macros that he gives; I've been using my own slightly customized version but can vouch that things generally work great. You should indeed be able to set parameters in the Average dialog under Special. Don't know exactly what your crashes were caused by, but it's probably the wrong version. Feel free to rite me directly if you have more problems... Chi-Bin Chien chien@jeeves =====Edited messages follow======== Date: Fri, 5 Nov 1993 12:59:09 +0000 From: wayne@helix.nih.gov(Wayne Rasband) Subject: Re: Sweet wishes (Wayne) I have included a new version of "Live Paste Averaged" (now called "Paste Averaged") that is more convenient to use. Use it with the new "Live Paste" macro that is also included. These macros require the 1.53b41 beta on Zippy. Here is the new procedure: 1) Take the averaged picture. 2) Select an out-of-focus ROI in the averaged picture. 3) Run "Live Paste" (type L). 4) Focus the ROI. 5) Run "Paste Averaged" (type A). 6) Repeat steps 2 through 5. --wayne macro 'Live Paste [L]'; begin LivePaste; end; macro 'Paste Averaged [A]'; { Captures an averaged or integrated selection into a window other than the Camera window. Use in conjunction with "Live Paste". Useful for making montages of different focal planes of fluorescent specimens. } var x,y,width,height,pid:integer; begin GetRoi(x,y,width,height); {Needs to be first} {PutMessage(x,y,width,height);} RequiresVersion(1.53); if WindowTitle='Camera' then begin PutMessage('The active window cannot be "Camera".'); exit; end; if width=0 then begin PutMessage('Rectangular selection required.'); exit; end; pid:=PidNumber; SelectWindow('Camera'); MakeRoi(x,y,width,height); AverageFrames; Copy; SelectPic(pid); MakeRoi(x,y,width,height); Paste; end; ------------------------------ Date: Fri, 5 Nov 1993 14:15:16 +0000 From: wayne@helix.nih.gov(Wayne Rasband) Subject: PasteLive macro command The new "LivePaste" macro command in V1.53b41 has become "PasteLive" in V1.53b42. --wayne ======End of Edited Messages======= Technical note: at the time, GetROI didn't work properly after if it was after RequiresVersion(); this bug was fixed several beta versions later. I have tried the "Live Paste Averaged" macro that Wayne posted, and like it a lot. This will be a really nice feature for us, since we often make montages of different focal planes of a fluorescent specimen. The macro "Setup Live Paste", at the end of this message, is a nice companion. (For convenience, "Live Paste Averaged" is repeated there.) The procedure for making montages is as follows: 0) Open the Paste Control panel 1) Take the first averaged picture. 2) Select an out-of-focus ROI in the averaged picture. 3) Run "Setup Live Paste" 4) manually Paste, then click on Live Paste to get the live video ROI 5) Focus the ROI 6) Run "Live Paste Averaged" to fill in with averaged video. 7) Repeat steps 2 through 6 until you have the desired montage. In the spirit of "Wayne gives 'em an inch, and they ask for a foot": A) Is it possible to turn on Live Paste from a macro? As far as I could tell from the manual, it isn't yet. Such a feature could be used at the end of "Setup Live Paste", to replace the keystroke and mouse click of step 4, making the whole process a little faster. B) Is it possible to get a ChooseWindow() equivalent of SelectWindow? As they are now, the macros spend time and eyestrain flashing back and forth to the Camera window. ChooseWindow('Camera') would prevent this. Also, thanks a lot Wayne for the WindowTitle function! Chi-Bin Chien chien@jeeves.ucsd.edu ============ macro 'Setup Live Paste [S]'; { Sets up the current rectangular ROI in preparation for Live Paste Averaged. } var x,y,width,height,pid:integer; begin RequiresVersion(1.53); if WindowTitle='Camera' then begin PutMessage('The active window cannot be "Camera".'); exit; end; GetRoi(x,y,width,height); if width=0 then begin PutMessage('Rectangular selection required.'); exit; end; pid:=PidNumber; SelectWindow('Camera'); MakeRoi(x,y,width,height); Copy; SelectPic(pid); RestoreROI; end; macro 'Live Paste Averaged [L]'; { Captures an averaged or integrated selection into a window other than the Camera window. Use in conjunction with "Live Paste". } var x,y,width,height,pid:integer; begin RequiresVersion(1.53); if WindowTitle='Camera' then begin PutMessage('The active window cannot be "Camera".'); exit; end; GetRoi(x,y,width,height); if width=0 then RestoreRoi; GetRoi(x,y,width,height); if width=0 then begin PutMessage('Rectangular selection required.'); exit; end; pid:=PidNumber; SelectWindow('Camera'); RestoreRoi; AverageFrames; Copy; SelectPic(pid); Paste; end; ********************************************************************************* I have a User.p which defines a number of 16 bit and real arithmetic usermacro routines, and conversions between formats, using "illegible" images i.e. 16 bit images which display twice as wide as the "actual" image, real images display four times as wide. You aren't supposed to look at these images. (Actually, mostly the real versions are implemented, but making a set of 16 bit versions should not take long...) The same principle could be used for 8 bit arithemetic routines with no automatic scaling or with explicit scaling, called from macros. The routines specify pic numbers of input and output images. These will be made available one of these days in contrib on zippy. Merging this code with a new version of image is quite easy, since nearly all of the changes are in user.p. Typical Macro file: var numToHex: integer; CnvRealTo8, Cnv8ToReal, AddReal, AddRealCons, MpyReal, MpyRealCons: integer; SubReal, SubRealCons, DivReal, DivRealCons: integer; MinReal, MaxReal, SmlReal, SmlRealCons, LrgReal, LrgRealCons: integer; KernelParams, SmoothReal: integer; Pic8, PicRA, PicRB, PicROut: integer; procedure LookupUserCodes; begin numToHex := UserFunc(0, 0, 0, 0, 'NumToHex'); CnvRealTo8 := UserFunc(0, 0, 0, 0, 'CnvRealTo8'); Cnv8ToReal := UserFunc(0, 0, 0, 0, 'Cnv8ToReal'); AddReal := UserFunc(0, 0, 0, 0, 'AddReal'); AddRealCons := UserFunc(0, 0, 0, 0, 'AddRealCons'); MpyReal := UserFunc(0, 0, 0, 0, 'MpyReal'); [...etc...] end; macro 'Multiply Out := A * B'; begin UserCode(MpyReal, PicROut, PicRA, PicRB); SelectPic(PicROut); end; macro 'Convert Out to 8'; begin UserCode(CnvRealTo8, Pic8, PicROut); SelectPic(Pic8); end; macro 'Add Out := A + B'; begin UserCode(AddReal, PicROut, PicRA, PicRB); SelectPic(PicROut); end; [...etc... The whole file will be available on zippy one of these days...] These macros suffer from the changing picNumber disease. Perhaps my revised version will also include a "picOrdinal" function which returns picNumbers that will not change and can be used interchangably with picNumber values... Here are the changes required to modules other than user.p. Mostly, this implements the changes necessary to permit the above illustrated uses of UserFunc and UserCode, and also permits the Pascal code in User.p to generate macro error messages. Globals.p: Add UserFuncC, UserStrC to CommandType. Image.p: Uncomment InitUser Init.p: StringFunctions := [GetStringC, GetSerialC, ChrC, ConcatC, UserStrC]; Macros1.p: procedure DoUserCode (var StrResult: Str255; var Result: Extended); forward; function DoStringFunction: str255; var str: str255; discard: extended; begin case MacroCommand of GetStringC: DoStringFunction := DoGetString; ChrC: DoStringFunction := DoChr; GetSerialC: DoStringFunction := GetSerial; ConcatC: begin GetArguments(str); DoStringFunction := str; end; UserStrC: begin DoUserCode(str, discard); DoStringFunction := str; end; otherwise MacroError('"GetString", "GetSerial", "chr", "concat", or "UserStr" expected'); end; end; function ExecuteUserFunction: extended; var Discard: str255; Result: extended; begin DoUserCode(Discard, Result); ExecuteUserFunction := Result; end; function ExecuteFunction: extended; begin case MacroCommand of {...} UserFuncC: ExecuteFunction := ExecuteUserFunction; end; {case} end; procedure DoUserCode (var StrResult: Str255; var Result: Extended); {invoked for UserCode(code[,param1[,param2[,param3[,strarg]]]])} {also invoked for x := UserFunc(code[,param1[,param2[,param3[,strarg]]]])} {also invoked for str := UserStr(code[,param1[,param2[,param3[,strarg]]]])} {Contributed by Mark Vivino} var WhichCode: integer; Param1, Param2, Param3: extended; ErrorStr: Str255; begin Result := 0; StrResult := ''; GetLeftParen; WhichCode := GetInteger; Param1 := 0; Param2 := 0; Param3 := 0; GetToken; if token = comma then begin Param1 := GetExpression; GetToken; if token = comma then begin Param2 := GetExpression; GetToken; if token = comma then begin Param3 := GetExpression; GetToken; if token = comma then begin StrResult := GetString; GetToken; end; end; end; end; if token <> RightParen then MacroError(RightParenExpected); if Token <> DoneT then begin ErrorStr := ''; UserMacroCode(WhichCode, Param1, Param2, Param3, StrResult, ErrorStr, Result); if ErrorStr <> '' then MacroError(ErrorStr); end; end; procedure DoUserCodeDiscard; var DiscardResult: extended; DiscardStr: str255; begin DoUserCode(DiscardStr, DiscardResult); end; procedure ExecuteCommand; {...} UserCodeC: DoUserCodeDiscard; end; macros2.p procedure InitSymbolTable; with SymbolTable[263] do begin symbol := 'userfunc '; tType := FunctionT; cType := UserFuncC; end; with SymbolTable[264] do begin symbol := 'userstr '; tType := FunctionT; cType := UserStrC; end; nSymbols := 264; User.p: procedure UserMacroCode (CodeNumber: integer; Param1, Param2, Param3: extended; var StrResult, ErrorStr: Str255; var result: Extended); implementation const NoCodeMessage = 'Requires user written Think Pascal routine. '; type RealPtr = ^Real; packedUnsigned = packed record u: 0..255 end; packedUnsignedInt = packed record u: 0..65535 end; pup = ^packedUnsigned; puip = ^packedUnsignedInt; [...] {Variables used for scaling 16 bit to 8 bit } gScaleMin, gScaleMax: LongInt; gScaleXlate: Handle; procedure InitUser; begin {instead of commenting out the InitUser call in Image.p} {comment out the menu calls if you don't want to add the user menu.} {UserMenuH := GetMenu(UserMenu);} {InsertMenu(UserMenuH, 0);} {DrawMenuBar;} {Additional user initialization code goes here.} gScaleXlate := nil; gScaleMin := 0; gScaleMax := 0; end; [...etc...] -- Edward J. Huff huff@mcclb0.med.nyu.edu (212)998-8465 Keck Laboratory for Biomolecular Imaging NYU Chemistry Deptartment, 31 Washington Place, New York NY 10003 ******************************************************************* I have included a macro that can import a Photoshop 2.0 or 2.5 uncompressed 24-bit TIFF file into NIH Image. >Also, when is the ability to convert RBG to HSI being added?This would be >great, I have been using Photoshop to do both this and my color analysis, >but would prefer using Image when I get things worked out... V1.51 has the ability to convert RGB to HSV. --wayne 93-08-06 procedure ImportPhotoshop(offset:integer); { Imports a 24-bit, uncompressed Photoshop TIFF file into a 3-slice stack. } var width,height,temp,stack,i,j:integer; begin SetImport('8-bit; Invert'); width:=GetNumber('Width:',512); height:=GetNumber('Height:',512); SetCustom(3*width,height,offset,1); Import(''); temp:=PicNumber; SetNewSize(width,height); MakeNewStack('RGB'); AddSlice; AddSlice; stack:=PicNumber; for i:=0 to width-1 do begin for j:=1 to 3 do begin ChoosePic(temp); GetColumn(3*i+(j-1),0,height); ChoosePic(stack); ChooseSlice(j); PutColumn(i,0,height); end; end; SelectSlice(1); SelectPic(temp); Dispose; end; macro 'Import Photoshop 2.0 24-bit TIFF'; begin ImportPhotoshop(196); end; macro 'Import Photoshop 2.5 24-bit TIFF'; begin ImportPhotoshop(598); end; ************************************************ Norm Hurst writes 93-07-28 Getting more than 8 bits 7/28/93 2:04 PM Here is a way to get more than 8 bits out of an 8-bit image. Magic? Well, there's no free lunch. You have to trade off resolution to get it. And, to stay within Image's 8-bit number system, you have to give up some of the range. So this trick only works if: 1. you cannot get your light-range of interest to fill the 8-bit range of Image, 2. your gray-scale resolution is noise-limited (the noise acts as a dither signal), and 3. you are oversampled at least 2:1 (nothing of interest is less than 2-pixels wide). Still interested? Set the LUT slope to double what it is (see macro below), centered around the gray scale of interest. This will saturate other parts of the picture into black or white. Now "Apply LUT". The range of interest will contain only even or odd numbers (not both kinds). Now convolve with these taps: 0 0 0 1 2 1 0 0 0 This will filter out some noise (and blur the image). You should get a mix of even and odd values. You've gained an extra bit (on average). If your image is predominantly vertical stripes, turn the convolution sideways using these taps: 0 1 0 0 2 0 0 1 0 This will prevent filtering in the horizontal direction, preserving your stripes' resolution. If you want to get 2 extra bits, quadruple the LUT slope and convolve with this filter: 1 2 1 2 4 2 1 2 1 Note: You do not need to *exactly* double the range, but doing so results in a repeatable process. However, your best bet is to solve one of the three problems mentioned above! -Norm Hurst David Sarnoff Research Center ----------------------------- P.S.: Here's a macro for doubling the LUT around its current mean: macro 'Double LUT [D]'; var i,y:integer; mean :real; begin mean := 0; for i:=1 to 254 do begin mean := Mean + RedLUT[i]/254.0; end; for i:=1 to 254 do begin y := 2*(RedLUT[i]-mean) +mean; if y > 254 then y := 254; if y < 1 then y := 1; RedLUT[i]:=y; end; mean := 0; for i:=1 to 254 do begin mean := mean + GreenLUT[i]/254.0; end; for i:=1 to 254 do begin y := 2*(GreenLUT[i]-mean) +mean; if y > 254 then y := 254; if y < 1 then y := 1; GreenLUT[i]:=y; end; mean := 0; for i:=1 to 254 do begin mean := mean + BlueLUT[i]/254.0; end; for i:=1 to 254 do begin y := 2*(BlueLUT[i]-mean) +mean; if y > 254 then y := 254; if y < 1 then y := 1; BlueLUT[i]:=y; end; UpdateLUT; end; ********************************************************************* Here is a version Barr-Kum's macro for recording a sequence of images on a NEC PC VCR that uses the new Photomode command in V1.50. I had to add the SetCursor command to get the cursor to reappear. --wayne ----------------------------------------------------------- macro 'Mac To NEC PC VCR'; var linefeed,return,r,c,p,s,t,crlf,rc, ps,st,file:string; i:integer; begin r:=chr(82); c:=chr(67); p:=chr(80); s:=chr(83); t:=chr(84); linefeed:=chr(10); return:=chr(13); crlf:=concat(return,linefeed); rc:=concat(r,c,crlf); ps:=concat(p,s,crlf) st:=concat(s,t,crlf) RequiresVersion(1.50); OpenSerial('1200 baud,no parity,eight data,one stop'); for i:=1 to 275 do begin SetImport('TIFF'); Import('Temp', i:3) Photomode(true); PutSerial(rc); Wait(4); PutSerial(st); Wait(3); Photomode(false); SetCursor('Arrow'); close; Beep; end end; ************************************************************** Sorry for the delay in re-posting this message. Hope it helps. Barr-Kum --------------------------------------------- Sometime back I posted a query requesting help on recording a single image at a time onto a VCR. I did recieve replies which are appended at the end. I did manage to make a movie sequence by recording each image in this manner. This method bypasses the memory constraint when using animations on the computer. Equipment used was 1) Mac II cx 2) Syscom Mediator Scan convertor 3) Nec PC VCR (serial port controllable) Software NIH Image 1.49 Multiple images of contour plots were made on a workstation saved in Tiff format and telneted onto the Mac. Caution if you can generate PICT files use them as they use up less space. Out of the 600 images generated there was space for only 250 on a disk with 80 MB free space. The VCR was hooked up to the video output via scan convertor. Another line connecting VCR serial port and Mac serial was used to control VCR actions from the Mac. A macro written in NIH image was then used to crontrol the action of the VCR. Each image w called up. The menu bar etc were visible in the movie. Wayne Rasband has said that he will consider having a Macro call for PHOTOMODE. This should solve that problem. For some reason if a Pause command was sent to the VCR insted of a STOP the syncronisation got mixed up. Recording would occur when the image was being closed etc. Ive given the Macro used below. The general outline of the macro is as follows 1) Call up a image file 2) Send message to VCR via serial port to start recording 3) Time a sec (or any other) 4) Send message to VCR to stop recording 5) close file, loop What is needed is to have photomode between 1 and 2, where the whole Image fills the screen. I had to do this thru a macro as i was sending 250 images to the VCR. However f you are sending only a couple you could load the image, go into photomode and have a macro that will can be activated by command keys to send message to VCR to start recording and stop after fixed time. Hope this helps Sereno Barr-Kumarakulasinghe Marine Sciences Research Center SUNY @ Stony Brook NY 11794-5000 e-mail: sbarrkum@csserv1.ic.sunysb.edu sbk111@msrc.sunysb.edu ------------------------------------------------------------------ macro 'FileToVCR'; var linefeed,return,r,c,p,s,t,crlf,rc, ps,st,file:string; i:integer; begin r:=chr(82); c:=chr(67); p:=chr(80); s:=chr(83); t:=chr(84); linefeed:=chr(10); return:=chr(13); crlf:=concat(return,linefeed); rc:=concat(r,c,crlf); ps:=concat(p,s,crlf) st:=concat(s,t,crlf) RequiresVersion(1.48); OpenSerial('1200 baud,no parity,eight data,one stop'); for i:=1 to 275 do begin SetImport('TIFF'); Import('Temt', i:3) {photo Mode should be included here when available} PutSerial(rc); Wait(4); PutSerial(st); Wait(3);} close; Beep; end end; ******************************************************************** This macro sounds like it does most of what you want, that is if I understand what you want. macro 'Show RGB Values [S]'; var x,y,v,savex,savey:integer; begin repeat savex:=x; savey:=y; GetMouse(x,y); if (x<>savex) or (y<>savey) then begin v:=GetPixel(x,y); ShowMessage('loc=',x:1,', ',y:1, '\value=',v:1, '\RGB=',RedLUT[v]:1,', ',GreenLUT[v]:1,', ',BlueLUT[v]:1); wait(.5); end; until button; end; Mark Vivino NIH/DCRT mvivino@helix.nih.gov ***************************************************** >I have a question about Image (currently using v1.49), concerning >interrogating/putting image colors from a macro. I know how to get and put >pixel values (which indicate colormap entries), but is there a way to >interrogate the current colormap? I want to be able to do processing based on >the current *color*, not just the colormap index. Is there a way to do either >of the following? > > o Retrieve the RGB value of the colormap entry indexed by the pixel at > location X,Y; or Here is a macro that does just that. macro 'Get RGB Values'; var x,y,r,g,b,v:integer; begin x:=GetNumber('X:',x); y:=GetNumber('Y:',x); v:=GetPixel(x,y); R:=RedLut[v]; G:=GreenLut[v]; B:=BlueLut[v]; PutMessage('RGB=',R,',',G,',',B); end; > > o Directly retrieve the RGB value of colormap entry N. This is very similar. macro 'Get RGB Values'; var r,g,b,v:integer; begin v:=GetNumber('Color Map Entry:',v); R:=RedLut[v]; G:=GreenLut[v]; B:=BlueLut[v]; PutMessage('RGB=',R,',',G,',',B); end; >Of course, the inverse operations, putting RGBs *into* the colormap, are also >needed. There are many example macros in the file "LUT Macros" that does this. >And then, for the *real* frosting on the cake, is there a pair of >functions to convert between the red/green/blue color model and the hue/ >saturation/luminosity model? There is currently no macro macros to do this, but V1.50 has a menu command to convert an RGB image to HSV. --wayne ******************************************************************* The MakeSpectrum routine in Image(enclosed) cannot easily be converted to a macro. One problem is that HSVColor is a record and the macro languages does not support records or user defined arrays. Another problem is that HSV2RGB(a Mac toolbox routine) cannot be called from a macro. You could work around these problems be using the arrays built into the macro language, such as RedLUT, GreenLUT, BlueLUT, rUser1 and rUser2, and by writing your own version of HSV to RGB. Pascal code for doing HSV to RGB conversion can be found in the book "Fundamentals of Interactive Computer Graphics" by Foley and Van Dam. --wayne procedure MakeSpectrum; {Generates the "Spectrum" color table.} const Sat = -1; Val = -1; var i: integer; color: HSVColor; begin with info^ do begin for i := 0 to 255 do begin color.hue := i * 256; color.saturation := sat; color.value := val; HSV2RGB(color, ctable[i].rgb); end; LutMode := ColorLut; IdentityFunction := false; SetupPseudocolor; end; end; *********************************************************** >I would like to use Image to process several files during the night. >I understood that this is possible and wonder if one of you could give >me an example how it can be done. I would like to do the following things: >-background correction (e.g. 2d ball) >-sharpening >-thresholding ( is it possible to do this automatically, e.g. with >Isodata threshold method?) >-skeletonizing the binary picture >-counting the number of pixels in the skeletons (would it also be >possible to discard skeletons smaller than e.g. 5 pixels?. Something like the enclosed macro should do the job. --wayne macro 'Process Files '; var n,nFiles,i,PixelCount:integer; begin nFiles:=GetNumber('Number of Files:',1); SetUser1Label('Count'); for n:=1 to nFiles do begin Open(n:3); {Names must be in the form '001','002, etc.} SubtractBackground('2D Rolling Ball',25); Sharpen; AutoThreshold; ApplyLUT; Skeletonize; SetParticleSize(5,999999); AnalyzeParticles; PixelCount:=0; for I:=1 to rCount do PixelCount:=PixelCount+rArea[i]; Dispose; rUser1[n]:=PixelCount; {Save count in user1} end; SetCounter(n); ShowResults; end; ****************************************************** Here is a macro that changes the LUT to make the values near 128 fairly visible. Play around with it to get better results, this was written on the (incorrect) assumption that brightness = r+g+b. {j is i xor 255 and also white is 255,255,255 not 0,0,0} {The brightness of each pixel is not quite right, there is a better way to get } {different colors with same brightness...} macro 'Nearly Gray LUT for making XOR line selector visible'; var i,j,d: integer; begin while (d < 1) or (d > 63) do d := GetNumber('Amount of color',20); for i := d*2 to 127 do begin j := 255 - i; RedLUT[i] := j + d; GreenLUT[i] := j + d; BlueLUT[i] := j - d*2; RedLUT[j] := i - d*2; GreenLUT[j] := i + d; BlueLUT[j] := i + d; end; UpdateLUT; end; -- Edward J. Huff huff@mcclb0.med.nyu.edu (212)998-8465 Keck Laboratory for Biomolecular Imaging NYU Chemistry Deptartment, 31 Washington Place, New York NY 10003 *******************************************