//////////////////////////////////////////////////// // ImageJ macros for flagellar beat analysis // // Scripted by Bill Wickstead // v3.0 March 2007 //////////////////////////////////////////////////// // GLOBAL VARIABLES // // constants var pi=3.1415926536; // button-click variables var leftButton=16; var rightButton=4; var shift=1, ctrl=2, alt=8; var altClick=24, shiftClick=17; var pencilWidth=1, brushWidth=10; //////////////////////////////////////////////////// // TIME STAMPER VARIABLES // var TSpositionX="right", TSpositionY="bottom"; var TSmarginX=10, TSmarginY=10; var TSfontSize=12, TSstartTime=0, TSstep=5.208333; var TSsuffix=' ms', TSdecimals=1; var TSfont="SansSerif"; //////////////////////////////////////////////////// // ARROW THRU STACK VARIABLES // var arrowSize=12, arrowAngle=0; var arrowSelectSize=false, arrowSelectAngle=true; var arrowAllSlice=true, arrowDupStack=false; //////////////////////////////////////////////////// // SWIMMING ON THE SPOT VARIABLES // var sliceStep=3; var trackingPoints=2; var centreAlign=3; // centreAlign options [3=do both] var doTicks=false; // make cell displacement ruler var tickY=100; // position of ruler (below centre) var tickHeight=10; var tickSpace=266.7; // pixels var keepGraphs=false // keep fitting graphs for further use //////////////////////////////////////////////////// // CURVATURE VARIABLES // var curvCal=0; // um/pix value; 0=none var curvSmooth=2; // smoothing; 0=none var curvPlot=true; // create plots var curvPlotFix; // fixed y axis var curvYmin, curvYmax; var curvClearResults=true; //////////////////////////////////////////////////// // BEAT ANALYSIS VARIABLES // var beatStep=20; var beatSpeed=20; var markerSize=5; var sampleSpline=4; //////////////////////////////////////////////////// // MATHS FUNCTIONS // function int(x) { y=round( x - (x % 1) ); return y; } function roundUp(x) { y=int(x); if (x % y >0) { y++; } else if (x % y <0) { y--; } return y; } function sign(x) { if (x<0) { return -1; } else if (x>0) { return 1; } else { return 0; } } function magnitude(x) { // returns magnitude of x x=abs(x); if (x==0) { return 0; } else if (x<1) { y=int(-1+log(x)/log(10)); return y; } else { y=int(log(x)/log(10)); return y; } } function magScale(x) { // returns rounded up 1st sig.fig. of x y=roundUp( x/ pow(10, magnitude(x)) ); return y; } } macro '--- to use shortcuts hit alt+[key] ---' {} //////////////////////////////////////////////////// // DrawingTools // // This is a set of drawing tools similar to the pencil, paintbrush, eraser and paint bucket tools in NIH Image. // Pencil and paintbrush draw in the current foreground color, eraser draws in the current background color. // Paintbucket tool fills the selected area using the foreground color. // Alt key + pencil, paintbrush or paintbucket tool uses background color. // Double-click on the paintbucket or eye-dropper tool to set colors. // Double-click on the pencil, paintbrush or eraser tool to set the drawing width for that tool. // // Icons contributed by Tony Collins. macro "Unused Tool-1 - " {} // leave slot between text tool and magnifying glass unused macro "Unused Tool-2 - " {} // leave slot between dropper and pencil unused macro "Pencil Tool - C037L494fL4990L90b0Lc1c3L82a4Lb58bL7c4fDb4L5a5dL6b6cD7b" { getCursorLoc(x, y, z, flags); if (flags&alt!=0) setColorToBackgound(); draw(pencilWidth); } macro "Paintbrush Tool - C037F6036F3699CfffD71F4771D5eD7eD9e" { getCursorLoc(x, y, z, flags); if (flags&alt!=0) setColorToBackgound(); draw(brushWidth); } macro "Eraser Tool - C037R0aa4 P0a61f1aa0Pbef5f1" { setColorToBackgound(); draw(eraserWidth); } macro "Flood Fill Tool -C037B21P085373b75d0L4d1aL3135L4050L6166D57D77D68La5adLb6bcD09D94" { requires("1.34j"); setupUndo(); getCursorLoc(x, y, z, flags); if (flags&alt!=0) setColorToBackgound(); floodFill(x, y); } function draw(width) { requires("1.32g"); setupUndo(); getCursorLoc(x, y, z, flags); setLineWidth(width); moveTo(x,y); x2=-1; y2=-1; while (true) { getCursorLoc(x, y, z, flags); if (flags&leftButton==0) exit(); if (x!=x2 || y!=y2) lineTo(x,y); x2=x; y2 =y; wait(10); } } function setColorToBackgound() { savep = getPixel(0, 0); makeRectangle(0, 0, 1, 1); run("Clear"); background = getPixel(0, 0); run("Select None"); setPixel(0, 0, savep); setColor(background); } macro 'Extra tool options...' { choice=newArray("pencil", "paintbrush", "eraser", "floodfill"); Dialog.create("tool options"); Dialog.addChoice("Tool: ", choice); Dialog.show(); choice=Dialog.getChoice(); if (choice=="pencil") { pencilWidth = getNumber("Pencil Width (pixels):", pencilWidth); } else if (choice=="paintbrush") { brushWidth = getNumber("Brush Width (pixels):", brushWidth); } else if (choice=="eraser") { eraserWidth = getNumber("Eraser Width (pixels):", eraserWidth); } else if (choice=="floodfill") { requires("1.34j"); restorePreviousTool(); run("Color Picker..."); } } // Runs when the user double-clicks on the pencil tool icon // macro 'Pencil Tool Options...' { pencilWidth = getNumber("Pencil Width (pixels):", pencilWidth); } // Runs when the user double-clicks on the paint brush tool icon // macro 'Paintbrush Tool Options...' { brushWidth = getNumber("Brush Width (pixels):", brushWidth); } // Runs when the user double-clicks on the eraser tool icon // macro 'Eraser Tool Options...' { eraserWidth = getNumber("Eraser Width (pixels):", eraserWidth); } // Runs when the user double-clicks on the flood fill tool icon // macro 'Flood Fill Tool Options...' { requires("1.34j"); restorePreviousTool(); run("Color Picker..."); } //////////////////////////////////////////////////// // General macros tools // // Written by Bill Wickstead Jan 2006 macro ' --general--' {} macro 'Dispose All [d]' { if (nImages<1) { exit("no images open"); } if (nImages>1) ok= getBoolean("OK to dispose of " + nImages + ' images without saving?'); else ok= getBoolean('OK to dispose of image without saving?'); if (!ok) { exit (); } i=nImages; while (i>0) { selectImage(i); close; i--; } } macro 'Enhance constrast [h]' { run ("Enhance Contrast", "Saturated Pixels=0 Normalize"); } macro 'Apply LUT [a]' { run("Apply LUT"); } macro 'Capture points from spline [S]' { s=sampleSpline; Dialog.create("Spline options"); Dialog.addNumber("Sample every n-th points:", s); Dialog.addCheckbox("Clear results:", true); Dialog.show; s=Dialog.getNumber(); clearRes=Dialog.getCheckbox(); if (s<1 || s>50) { showMessage("Out of range"); exit; } if (clearRes) { run("Clear Results"); } NR=nResults; run("Fit Spline"); getSelectionCoordinates(x,y); for (n=0+NR; ngetWidth() || height>getHeight()) { message="Out of bounds\nPicture size: "+getWidth()+"x"+getHeight(); showMessage(message); } else { continue=0; } } left=(getWidth() - width)/2; top=(getHeight() - height)/2; makeRectangle(left, top, width, height); run("Crop"); } macro 'Draw centre line' { choice=newArray("black", "white"); Dialog.create("Centre line"); Dialog.addNumber("Line width:", 1); Dialog.addChoice ("line colour: ", choice); Dialog.show(); lw=Dialog.getNumber(); col=Dialog.getChoice(); if (col=="black") { setColor(0); } else if (col=="white") { setColor(255); } else { showMessage("out of bounds"); exit; } w=getWidth(); h=getHeight(); NS=nSlices; setLineWidth(lw); for (n=1; n<=NS; n++) { setSlice(n); drawLine(0,h/2,w-1,h/2); drawLine(w/2,0,w/2,h-1); } setSlice(1); } //////////////////////////////////////////////////// // Grayscale manipulation macros // // Written by Bill Wickstead April 2006 macro ' --levels--' {} macro 'Lighten / Darken...' { choice=newArray("lighten", "darken"); Dialog.create("Lighten / Darken"); Dialog.addChoice("Lighten / Darken", choice); Dialog.addNumber("All pixels by:", 0); Dialog.show(); choice=Dialog.getChoice(); AddVal=Dialog.getNumber(); if (choice=="lighten") { if (nSlices>1) { run("Add...", "stack value="+AddVal); } else { run("Add...", "value="+AddVal); } } else { if (nSlices>1) { run("Subtract...", "stack value="+AddVal); } else { run("Subtract...", "value="+AddVal); } } } macro 'Remove non-black' { dupl=true; tempArray=split(getTitle(), '.'); name=tempArray[0]+'_nonblack'; if (dupl) { run("Duplicate...", "title="+name+" duplicate"); } else { rename(name); } if (nSlices>1) { run("Multiply...", "stack value=255"); } else { run("Subtract...", "value=255"); } } //////////////////////////////////////////////////// // Stack manipulation macros // // Written by Bill Wickstead Jan 2006 function NoStackError() { if (nImages<1) { exit("image required"); } if (nSlices<1) { exit("stack required"); } } function NoSelectionError() { if (selectionType()==-1) { exit("selection required"); } } function PictureType() { info=getImageInfo(); index1=indexOf(info, 'Bits per pixel: '); index1 += 16; index2=indexOf(info, '(', index1); type=substring(info, index1, index2-1)+'-bit'; print(substring(info, index2+1, index2+4)); if (substring(info, index2+1, index2+4)=='RGB') { type='RGB'; } return type; } function sureSelect(ID, name) { // Makes sure selectImage() command goes to the correct image // selectImage() error appears to be a bug in versions 1.3* of ImageJ // function should become redundant selectImage(ID); checkID=getImageID(); checkname=getTitle(); if (ID != checkID || checkname != name) { exit ("Image select failure\nRequested: ID: "+ID+" named: "+name+"\nGot: ID: "+checkID+" named: "+checkname); } } macro ' --stacks--' {} macro 'Goto first slice [f]' { setSlice(1); } macro 'Goto last slice [l]' { setSlice(nSlices); } macro 'Goto slice... [g]' { NoStackError(); n=round(nSlices/2); continue=1; while (continue==1) { Dialog.create("Select slice"); Dialog.addMessage("Number of slices: "+nSlices); Dialog.addNumber("Goto slice:", n); Dialog.show(); n=Dialog.getNumber(); if (n<1 || n>nSlices()) { message="Out of bounds\nNumber of Slices: "+nSlices; showMessage(message); } else { continue=0; } } setSlice(n); } macro 'Slice to new window [n]' { run ("Select All"); run ("Copy"); name="slice"+getSliceNumber(); type=PictureType(); newImage(name, type, getWidth(), getHeight(), 1); run ("Paste"); } macro 'Sample slices...' { NoStackError(); NS=nSlices; continue=false; while (!continue) { Dialog.create("Sample slices"); Dialog.addNumber("Sample every n-th slice:", 5); Dialog.addNumber("Start slice", 1); Dialog.show(); sampleFreq=Dialog.getNumber(); startSlice=Dialog.getNumber(); if (sampleFreq<1 || sampleFreq>NS) { message="Out of bounds\nNumber of Slices: "+NS; showMessage(message); } else if (startSlice<1 || startSlice>NS) { message="Out of bounds\nNumber of Slices: "+NS; showMessage(message); } else { continue=true; } } tempArray=split(getTitle(), '.'); name=tempArray[0]+'_sampled'; run("Duplicate...", "title="+name+" duplicate"); setSlice(1); for (i=1; inSlices) { exit("Error: selected slices out of bounds"); } tempArray=split(getTitle(), '.'); name=tempArray[0]+'_trimmed'; if (dupl) { run("Duplicate...", "title="+name+" duplicate"); } else { rename(name); } i=nSlices; setSlice(i); while (i>lastSlice) { run("Delete Slice"); i--; } i=1; setSlice(i); while (inSlices) { exit("Error: split > nSlices"); } run("Select All"); tempArray=split(getTitle(), '.'); name1=tempArray[0]+'_part1'; name2=tempArray[0]+'_part2'; rename(name1); part1=getImageID(); run("Duplicate...", "title="+name2+" duplicate"); part2=getImageID(); for (i=1; i<=splitAt; i++) { setSlice(1); run("Delete Slice"); } selectImage(part1); for (i=nSlices; i>splitAt; i--) { setSlice(i); run("Delete Slice"); } selectImage(part1); setSlice(1); selectImage(part2); setSlice(1); } // End of macro macro 'Time stamper... [t]' { NoStackError(); Dialog.create("Time stamper"); Dialog.addNumber("Start time:",TSstartTime); Dialog.addNumber("Time between frames:",TSstep); tempArray=newArray("SansSerif", "Monospaced", "Serif"); choice=newArray(tempArray.length); choice[0]=TSfont; n=0; for (i=1; i0) { str=toString(int(time))+'.'; rem=time % int(time); for (n=1; n0) { i--; pointX=newArray(i); pointY=newArray(i); for (n=0; n0) { i--; pointX=newArray(i); pointY=newArray(i); for (n=0; nbigW) { bigW=testW; } if (testH>bigH) { bigH=testH; } } for (n=0; nmaxX) { maxX=delx; } if (dely>maxY) { maxY=dely; } } minX=-maxX; minY=-maxY; } else { for (n=0; nmaxX) { maxX=delx; } if (delymaxY) { maxY=dely; } } } run("Canvas Size...", "width="+w-minX+" height="+h-minY+" position=Bottom-Right"); run("Canvas Size...", "width="+w-minX+maxX+" height="+h-minY+maxY+" position=Top-Left"); // Move newW=getWidth(); newH=getHeight(); for (n=0; nw2) { w3=w1; } else { w3=w2; } if (h1>h2) { h3=h1; } else { h3=h2; } selectImage(ID1); run("Canvas Size...", "width="+w3+" height="+h3+" position=Center"); selectImage(ID2); run("Canvas Size...", "width="+w3+" height="+h3+" position=Center"); for (n=0; n<2; n++) { x1[n]+=(w3-w1)/2; y1[n]+=(h3-h1)/2; x2[n]+=(w3-w2)/2; y2[n]+=(h3-h2)/2; } theta1=atan2(y1[0]-y1[1], x1[1]-x1[0]); theta2=atan2(y2[0]-y2[1], x2[1]-x2[0]); angle=180*(theta2-theta1)/pi; bigW=abs(w3*cos(theta2-theta1))+abs(h3*sin(theta2-theta1)); bigH=abs(h3*cos(theta2-theta1))+abs(w3*sin(theta2-theta1)); for (n=0; n<2; n++) { x2[n]+=(bigW-w3)/2; y2[n]+=(bigH-h3)/2; } run("Canvas Size...", "width="+bigW+" height="+bigH+" position=Center"); // Rotate for (i=1; i<=NS2; i++) { setSlice(i); run("Select All"); run("Arbitrarily...", "slice angle="+angle+" interpolate"); } // Move newX= (x2[0]-bigW/2)*cos(theta2-theta1) + (bigH/2-y2[0])*sin(theta2-theta1) +bigW/2; newY= bigH/2 - (bigH/2-y2[0])*cos(theta2-theta1) + (x2[0]-bigW/2)*sin(theta2-theta1); delx=x1[0]-newX; dely=y1[0]-newY; for (i=1; i<=NS2; i++) { setSlice(i); run("Select All"); run("Cut"); makeRectangle(delx, dely, bigW, bigH); run("Paste"); } makeRectangle(0,0,w3,h3); run("Crop"); setSlice(1); } //////////////////////////////////////////////////// // Swimming on the spot macros // // Written by Bill Wickstead March 2006 macro ' --swimming on the spot--' {} macro 'Transform to stationary [T]' { // Gathers info on movement of 1 or 2 points thru a stack, fits to polynomial // and transforms to stationary swimming NoStackError(); NS=nSlices; width=getWidth(); height=getHeight(); type1=PictureType(); name1=getTitle(); ID1=getImageID(); // Capture points setTool(7); len=round(0.5+NS/sliceStep); if (NS % sliceStep == 0) { len--; } // proximal if (centreAlign==0) { showMessageWithCancel("Cell displacement calc:\nTrack single point thru stack"); } else { if (trackingPoints==1) { showMessageWithCancel("Track single point thru stack"); } else if (trackingPoints==2) { showMessageWithCancel("Track PROXIMAL point thru stack"); } else { exit("Error in trackingPoints value"); } } proxX=newArray(len); proxY=newArray(len); flagsOld=-1; i=0; while (i0) { i--; pointX=newArray(i); pointY=newArray(i); for (n=0; n0) { i--; pointX=newArray(i); pointY=newArray(i); for (n=0; n1) { delx=round(width/2 - x1); dely=round(height/2 - y1); setSlice(i); run("Select All"); run("Cut"); makeRectangle(delx, dely, newSize, newSize); run("Paste"); } // Rotate if (centreAlign!=2) { if (trackingPoints==1) { x2=0; for (n=0; n<=order_proxX; n++) { x2 += coeffs_proxX[n]*pow(i+1, n); } y2=0; for (n=0; n<=order_proxY; n++) { y2 += coeffs_proxY[n]*pow(i+1, n); } } else { x2=0; for (n=0; n<=order_distX; n++) { x2 += coeffs_distX[n]*pow(i, n); } y2=0; for (n=0; n<=order_distY; n++) { y2 += coeffs_distY[n]*pow(i, n); } } if (x2-x1==0) { if (y1-y2<0) { angle=90; } else { angle=270; } } else { angle=180*atan((y1-y2)/(x2-x1))/pi; if (x2-x1<0) { angle += 180; } } setSlice(i); run("Select All"); run("Arbitrarily...", "slice angle="+angle+" interpolate"); } } setSlice(1); } // end of cell displacement skip // CALCULATE CELL DISPLACEMENT // dist=0; for (i=1; i=0 && tickX[n]=0 && tickX[n] ends macro and outputs results"); setTool(7); setSlice(1); continue=1; count=0; flagsOld=-1; x1=newArray(NS); y1=newArray(NS); x2=newArray(NS); y2=newArray(NS); frameN=newArray(NS); while (continue==1 && count0) { if (theta[n]-2*piCount*pi>quad && theta[n-1]-2*piCount*pi<-quad) { piCount--; theta[n]+=2*piCount*pi; } else if (theta[n]-2*piCount*pi<-quad && theta[n-1]-2*piCount*pi>quad) { piCount++; theta[n]+=2*piCount*pi; } } if (curvCal==0) { dist[n]=cumulative; } else { dist[n]=cumulative*curvCal; } cumulative+=sqrt( (yy[n+1]-yy[n])*(yy[n+1]-yy[n]) + (xx[n+1]-xx[n])*(xx[n+1]-xx[n]) ); } if (curvCal==0) { dist[n]=cumulative; } else { dist[n]=cumulative*curvCal; } for (n=0; n=curvSmooth && len-2-n>=curvSmooth) { thetaAve=0; for (i=-curvSmooth; i<=curvSmooth; i++) { thetaAve+=curv[n+i]; } thetaSmooth[n]=thetaAve/(2*curvSmooth+1); } else { thetaSmooth[n]=curv[n]; } } for (n=0; ncurvmax) { curvmax=curv[n]; } else if (curv[n]0) { if (theta[n]-2*piCount*pi>quad && theta[n-1]-2*piCount*pi<-quad) { piCount--; theta[n]+=2*piCount*pi; } else if (theta[n]-2*piCount*pi<-quad && theta[n-1]-2*piCount*pi>quad) { piCount++; theta[n]+=2*piCount*pi; } } if (theta[n]>thetamax) { thetamax=theta[n]; } else if (theta[n]=curvSmooth && len-1-n>=curvSmooth) { thetaAve=0; for (i=-curvSmooth; i<=curvSmooth; i++) { thetaAve+=theta[n+i]; } thetaSmooth[n]=thetaAve/(2*curvSmooth+1); } else { thetaSmooth[n]=theta[n]; } } for (n=0; n50) { showMessage("Out of range"); exit; } } // THE END //