/*Perspective_Stack_View.txt author: Norbert Vischer date: 12-apr-2013 17:02 url: http://simon.bio.uva.nl/objectj/examples/ - Creates animated perspective stack view - To be used for publications or presentations - Single view can be saved with transparent background - Color addition is illustrated if Mix Colors is checked From the current (hyper-)stack, a perspective animation is created If stack is a hyperstack, all channels of current slice and frame setting are used. Otherwise, all slices of stack are used (max: 10). */ var shrinkY = 0.4, //slice is vertically compressed down to factor 0.4 shrinkX = 0.8, //backside is 0.8 * frontside overlap = -0.2, // fraction of slice to be obscured by next higher slice (use neg. values for non-overlap spacing) border = 1,//line width in pixels margin = 50, //extra background added to each side animationFrames = 30, // totalHeight, trapezHeight, offsetY, maxChn, animationID, tilesID, origTitle, back2 = 0,//fixed colors for composite mode front2 = 0x0404040,//fixed colors for composite mode pattern="", startAt, perspectiveFlag = true, ; macro "Create demo stack [1]"{//demo hyperstack with circles //run("Close All"); saveSettings; //colors run("Hyperstack...", "title=DemoStack type=8-bit display=Color width=300 height=300 channels=3 slices=2 frames=2"); setForegroundColor(255, 255, 255); run("Line Width...", "line=30"); Stack.setPosition(1,2,2); makeOval(30, 30, 240, 240); run("Draw", "slice"); Stack.setPosition(2,2,2); run("Draw", "slice"); Stack.setPosition(3,2,2); run("Draw", "slice"); run("Select None"); run("Mean...", "radius=15 stack"); run("Add Noise", "stack"); Stack.setPosition(1,2,2); setForegroundColor(0x0bbffaa); setBackgroundColor(0x0005566); } macro "Create Perspective Animation [2]"{ requires("1.47o"); run("Options...", "iterations=1 count=1 edm=Overwrite"); origTitle = getTitle; if(is("composite") || is("hyperstack")){ Stack.setDisplayMode("color"); Stack.getDimensions(dummy, dummy, channels, slices, frames); maxChn = channels; } else maxChn = nSlices; maxChn = minOf(maxChn, 9); pattern = substring("123456789", 0, maxChn); html = "http://simon.bio.uva.nl/objectj/examples/PerspectiveStackView/PerspectiveStackView.htm"; Dialog.create("Perspective Settings"); msg = "Border is drawn with foreground color"; msg += "\nBackground is filled with background color.\n"; Dialog.addMessage(msg); Dialog.addString("Channel Sequence:", pattern); Dialog.addNumber("Border Width:", border); Dialog.addNumber("Animation Frames:", animationFrames); solid = true; Dialog.addCheckbox("Color mixing during overlap", !solid); Dialog.addCheckbox("Perspective", true); Dialog.addChoice("Expand:", newArray("From Bottom", "From Center", "From Top"), "From Center"); Dialog.addHelp(html); Dialog.show(); tmpPattern = Dialog.getString; tmpPattern = replace(tmpPattern, " ", ""); checkPattern(tmpPattern, pattern); pattern = tmpPattern; border = Dialog.getNumber(); if (border < 0 || border > 10) border = 0; animationFrames = Dialog.getNumber(); solid = !Dialog.getCheckbox(); perspectiveFlag = Dialog.getCheckbox(); startAt = Dialog.getChoice(); roiManager("reset"); saveSettings; if (!solid){ setForegroundColor(front2); setBackgroundColor(back2); } createTiles();//single roi in roimanager plus on mask tilesID = getImageID; roiManager("select", 0); getSelectionBounds(xhook, y, ww, trapezHeight); offsetY = round((1- overlap) * trapezHeight); maxChn= lengthOf(pattern); totalHeight = trapezHeight + (maxChn - 1) * offsetY + 1;//excl margins newTitle = stripExtension(origTitle); newTitle +="-Animation"; newImage(newTitle, "RGB White", ww, totalHeight, animationFrames); animationID = getImageID; run("Select All"); run("Clear", "stack"); setBatchMode(true); for (phase = 1; phase <= animationFrames; phase++){ createPhase(phase, pattern); run("Select None"); } setBatchMode(false); selectImage(animationID); ww = getWidth + 2* margin; hh = getHeight + 2* margin; selectImage(animationID); run("Canvas Size...", "width=&ww height=&hh position=Center"); restoreSettings; close("Tmp*"); selectImage(animationID); run("Out [-]"); run("In [+]");//avoid stripes on Mac Stack.setFrameRate(15); wait(750); for (slc = 1; slc <= nSlices; slc++){ setSlice(slc); wait(50); } setOption("Changes", false); } //create a perspective view at a time point given by "phase" function createPhase(phase, pattern){ //top = totalHeight - trapezHeight -1; baseLine = getBaseLine(phase); for (digit =maxChn; digit >= 1; digit--){ slc=parseInt(substring(pattern, digit-1, digit)); selectImage(tilesID); roiManager("select", 0); setSelectionLocation(xhook, (slc - 1) * trapezHeight ); run("Copy"); selectImage(animationID); setSlice(phase); roiManager("select", 0); setSelectionLocation(0, baseLine - trapezHeight); setSlice(phase); if (!solid) setPasteMode("Add"); else setPasteMode("Copy"); run("Paste"); setPasteMode("Copy"); dy = offsetY * (phase - 1)/(maxOf(animationFrames -1, 1)); baseLine -= dy; } } //calc the bottom line of lowest trapezium function getBaseLine(phase){ relRange = (phase - 1)/(maxOf(animationFrames -1, 1));//0..1 absRange = (maxChn-1) * offsetY * relRange;//distance lower to upper baseline thisHeight = absRange+ trapezHeight; if (startAt == "From Top") baseLine = (totalHeight - trapezHeight) * relRange + trapezHeight; if (startAt == "From Center") baseLine = totalHeight /2 + trapezHeight/2 + absRange/2; if (startAt == "From Bottom") baseLine = totalHeight -1; return baseLine; } macro "Save View with Transparent Background [3]"{ if (!isOpen(animationID)) exit("First Re-create Animation"); if (animationID != getImageID) showMessageWithCancel("Bring active animation to front?"); selectImage(animationID); title= stripExtension(origTitle); title += "-transp.png"; phase = getSliceNumber; makeRectangle(margin, margin, getWidth-2*margin, getHeight-2*margin); run("Duplicate...", "title=" + title); run("Make Composite"); Stack.setDisplayMode("color"); run("Arrange Channels...", "new=1233"); setSlice(4); run("Grays"); setMetadata("label", "alpha"); run("Select All"); changeValues(0, 1e9, 0); top = totalHeight - trapezHeight -1; baseLine = getBaseLine(phase); firstBaseLine = baseLine; xhook = 1; for (chn =maxChn; chn >= 1; chn--){ roiManager("select", 0); setSlice(4);//alpha setSelectionLocation(0, baseLine - trapezHeight); changeValues(0, 1e9, 255); oldBaseLine = baseLine; baseLine -= offsetY * (phase-1)/(maxOf(animationFrames -1, 1)); } top = oldBaseLine - trapezHeight; makeRectangle(0, top, getWidth, firstBaseLine - top); run("Crop"); saveAs("PNG", ""); selectImage(animationID); run("Select None"); } macro "Save Animation as AVI [4]"{ selectImage(animationID); run("AVI... ", "compression=JPEG frame=15"); } //creates RGB montage with zero spacing //and a mask imgage for polygon roi function createTiles(){ getDimensions(width, height, channels, slices, frames); Stack.getPosition(channel, slice, frame); run("Duplicate...", "title=Tmp duplicate channels=1-" + channels + " slices=" + slice + " frames="+ frame); run("RGB Color"); maxChn = nSlices; while (maxChn > 10 && maxChn <2 && maxChn > nSlices) maxChn = getNumber("Max. 10 slices to display", 2); shrinkY2 = shrinkY; if (!perspectiveFlag) shrinkY2 = 1; run("Scale...", "x=1.0 y="+ shrinkY2 + " z=1.0 interpolation=Bilinear average process create title=Tmp_PerspectiveStack"); squashedID = getImageID; trapezHeight = getHeight; selectImage(squashedID); hh = getHeight; ww = getWidth; topWidth = shrinkX * ww; tleft = round(ww/2 -topWidth/2) + 1; tright = round(ww/2 + topWidth/2) - 1; wwMin2 = ww - 2; xx = newArray(tleft, tright , wwMin2, 1); yy = newArray(0, 0, hh, hh); makeSelection("polygon", xx, yy); if (!perspectiveFlag){ makeRectangle(1, 0, getWidth-1, getHeight); } run("Create Mask"); rename("Tmp" + random); run("Create Selection"); roiManager("reset"); roiManager("Add"); selectImage(squashedID); if (perspectiveFlag){ for (chn =1; chn <= maxChn; chn++){ setSlice(chn); for (y = 0; y < hh - 1; y++){ factor = y/hh; thisWidth = topWidth + factor * (ww - topWidth); thisWidth = thisWidth - thisWidth%2; makeRectangle(0, y, ww, 1); run("Scale...", "x=- y=- width="+thisWidth+" height=1 interpolation=Bilinear fill"); } } } roiManager("select", 0); run("Clear Outside", "stack"); if (border > 0){ run("Enlarge...", "enlarge=-"+border + " pixel");//shrink roiManager("add"); arr = newArray(0, 1); roiManager("select", arr); roiManager("XOR"); run("Fill", "stack"); } run("Select None"); run("Make Montage...", "columns=1 rows=&maxChn scale=1"); rename("Tmp_Tiled"); } //sequence pattern must be correct function checkPattern(tmpPattern, pattern){ for (jj = 0; jj < lengthOf(tmpPattern); jj++){ if (indexOf(pattern, substring(tmpPattern, jj, jj+1)) <0) exit("Sequence must contain a combination of digits '" + pattern +"\'"); } } function stripExtension(title){ if (endsWith(title, ".tiff")) return substring(title, 0, lengthOf(title) - 5); if (endsWith(title, ".tif")) return substring(title, 0, lengthOf(title) - 4); if (endsWith(title, ".jpg")) return substring(title, 0, lengthOf(title) - 4); if (endsWith(title, ".png")) return substring(title, 0, lengthOf(title) - 4); return title; }