/* "StartupMacros" The macros and macro tools in this file ("StartupMacros.txt") are automatically installed in the Plugins>Macros sub-menu and in the tool bar when ImageJ starts up. About the drawing tools: This is a set of drawing tools similar to the pencil, paintbrush, eraser and flood fill (paint bucket) tools in NIH Image. The pencil and paintbrush draw in the current foreground color and the eraser draws in the current background color. The flood fill tool fills the selected area using the foreground color. Hold down the alt key to have the pencil and paintbrush draw using the background color or to have the flood fill tool fill using the background color. Set the foreground and background colors by double-clicking on the flood fill tool or on the eye dropper tool. Double-click on the pencil, paintbrush or eraser tool to set the drawing width for that tool. Original icons contributed by Tony Collins. ASC icons by Peter J. Lee. Shortcuts assigned here: "Analyze Particles [F2]" "Copy Image Scale [F3]" "ASC Set Scale [F4]", "Fancy Scale Bar [F5]" "ASC ROI Color Coder and Summary [F6]" "Add Additional Geometries to Table [F7]", "Threshold by Selection or ROIs [F8]" "Roi_Manager_Add_and_Outline_in_Overlay [F9]" "Remove Overlay [F10]" "Anim or Transp GIF PNG Seq or comp TIFF [F11]" "Set Selection or Trim [F12]" Available shortcuts slots: [n1] , [n2] etc. : numerical keypad entries [&1], [&2] etc. : numerical keyboard or top row of the keyboard (requires ImageJ v1.53a) Could leave these for personal shortcut toolset ('Option 2'): https://imagej.net/learn/keyboard-shortcuts */ startupV = "v260424"; call("ij.Prefs.set", "asc.startupMacros.version", startupV); /* Global variables */ var pencilWidth = 1, eraserWidth = 10, leftClick = 16, alt = 8; var brushWidth = 10; //call("ij.Prefs.get", "startup.brush", "10"); var floodType = "8-connected"; //call("ij.Prefs.get", "startup.flood", "8-connected"); /* The macro named "AutoRunAndHide" runs when ImageJ starts and the file containing it is not displayed when ImageJ opens it:*/ // macro "AutoRunAndHide" {} function UseHEFT() { requires("1.38f"); stateOfHEFT = call("ij.io.Opener.getOpenUsingPlugins"); if (stateOfHEFT == "false") { setOption("OpenUsingPlugins", true); showStatus("TRUE \(images opened by HandleExtraFileTypes\)"); } else { setOption("OpenUsingPlugins", false); showStatus("FALSE \(images opened by ImageJ\)"); } } UseHEFT(); // The macro named "AutoRun" runs when ImageJ starts. macro "AutoRun" { // run all the .ijm scripts provided in macros/AutoRun/ autoRunDirectory = getDirectory("imagej") + "/macros/AutoRun/"; if (File.isDirectory(autoRunDirectory)) { list = getFileList(autoRunDirectory); // make sure startup order is consistent Array.sort(list); for (i = 0; i < list.length; i++) { if (endsWith(list[i], ".ijm") && !startsWith(list[i], "~")) { runMacro(autoRunDirectory + list[i]); } } } } /* These popup menu commands (right-click on image) can include named macros in this file like "Fancy Scale Bar" For "Tiff...", "Animated Gif ... ", and "PNG..." the TIFF and PNG commands do not work if there is a space between the command and the dots but you need the space for anim GIF */ var pmCmdList = newMenu("Popup Menu", newArray("Rename...", "Duplicate...", "Invert", "UnInvert LUT", "Revert", "Auto Crop (guess background color)", "Close All Other Open Images", "Close Results and Clear ROIs", "Threshold by Selection or ROIs [F8]", "-", "Run last run macro or script again", "Record...", "Capture Screen", "-", "Analyze Particles [F2]", "Copy Image Scale [F3]", "ASC Set Scale [F4]", "-", "Fancy Scale Bar [F5]", "Add Summary Table to Copy of Image", "ASC ROI Color Coder and Summary [F6]", "Add Multiple Lines of Fancy Text To Image", "Add Additional Geometries to Table [F7]", "-", "Hide Overlay", "Show Overlay", "Remove Overlay [F10]", "Remove Last Overlay Selection", "Flatten", "Remove Black Edge Objects", "Erode & Prune to Fully(4)-Connected Skeleton", "-", "Save: LZW-TIF PNG JPG Anims etc. [F11]", "Tiff...", "PNG...", "Jpeg...", "-", "Set Selection or Trim [F12]", "Clear", "Clear Outside", "-", "Scale to Fit", "View 100%", "-", "Stitch Grid of Images", "-", "Find Commands...", "Search...", "Control Panel...", "Paste Control...", "Monitor Memory...", "Help...")); macro "Popup Menu" { pmCmd = getArgument(); if (pmCmd == "Help...") showMessage("About Popup Menu", "To customize this menu, edit the line that starts with\n\"var pmCmdList\" in ImageJ/macros/StartupMacros.fiji.ijm."); else run(pmCmd); } macro "Abort Macro or Plugin (or press Esc key) Action Tool - CbooP51b1f5fbbf5f1b15510T5c10X" { setKeyDown("Esc"); } var dCmds = newMenu("Developer Menu Tool", newArray("ImageJ Website", "News", "IJ Release Notes on Github", "Major Version IJ Release Notes", "Documentation", "ImageJ Wiki", "Resources", "Macro Language", "Macros", "Built-in Macro Functions", "Startup Macros...", "Plugins", "Source Code", "Mailing List Archives", "Mailing List at NIH", "ImageJ Forum", "Fiji Forum", "Zulu JDK FX package", "-", "Record...", "Capture Screen ", "Monitor Memory...", "List Commands...", "Control Panel...", "Search...", "Debug Mode", "-", "ASC Macros: Homepage", "ASC Macros: Gifsicle Homepage", "ASC Macros: ImageMagick Homepage")); macro "Developer Menu Tool - C037T0b11DT9b10eTfb10v" { cmd = getArgument(); if (cmd == "ImageJ Website") run("URL...", "url=https://imagej.net/index.html"); else if (cmd == "News") run("URL...", "url=https://wsr.imagej.net/notes.html"); else if (cmd == "IJ Release Notes on Github") run("URL...", "url=https://raw.githubusercontent.com/imagej/imagej1/master/release-notes.html"); else if (cmd == "Major Version IJ Release Notes") run("URL...", "url=https://wsr.imagej.net/notes.html"); else if (cmd == "Documentation") run("URL...", "url=https://imagej.net/docs/index.html"); else if (cmd == "ImageJ Wiki") run("URL...", "url=https://imagej.net/"); else if (cmd == "Resources") run("URL...", "url=https://imagej.net/developer/index.html"); else if (cmd == "Macro Language") run("URL...", "url=https://imagej.net/developer/macro/macros.html"); else if (cmd == "Macros") run("URL...", "url=https://imagej.net/macros/"); else if (cmd == "Built-in Macro Functions") run("URL...", "url=https://imagej.net/developer/macro/functions.html"); else if (cmd == "Plugins") run("URL...", "url=https://imagej.net/plugins/index.html"); else if (cmd == "Plugin Update Sites") run("URL...", "url=https://imagej.github.io/list-of-update-sites/index.html"); else if (cmd == "CLIJ FAQ") run("URL...", "url=https://clij.github.io/clij2-docs/faq/index.html"); else if (cmd == "Source Code") run("URL...", "url=https://imagej.net/developer/source/index.html"); else if (cmd == "Mailing List Archives") run("URL...", "url=https://list.nih.gov/archives/imagej.html"); else if (cmd == "Mailing List at NIH") run("URL...", "url=https://list.nih.gov/cgi-bin/wa.exe?A0=IMAGEJ"); else if (cmd == "ImageJ Forum") run("URL...", "url=https://forum.image.sc/tags/imagej"); else if (cmd == "Fiji Forum") run("URL...", "url=https://forum.image.sc/tags/fiji"); else if (cmd == "Zulu JDK FX package") run("URL...", "url=https://www.azul.com/downloads/?version=java-8-lts&os=windows&architecture=x86-64-bit&package=jdk-fx#zulu"); else if (cmd == "Debug Mode") setOption("DebugMode", true); else if (cmd == "ASC Macros: Homepage") run("URL...", "url=https://fs.magnet.fsu.edu/~lee/asc/ImageJUtilities/ASC_ImageJ_Utils.html"); else if (cmd == "ASC Macros: Gifsicle Homepage") run("URL...", "url=https://www.lcdf.org/gifsicle/index.html"); else if (cmd == "ASC Macros: ImageMagick Homepage") run("URL...", "url=https://imagemagick.org/script/download.php"); else if (cmd != "-") run(cmd); } /* Stacks Menu Tool Expanded from the built-in tool to add extra menu items: pjl Thursday, April 11, 2019 v231026: Switched from built-in "Image to Stack" to "Images to Stack Plus" primarily for slice labels" v240130: Restore Images to Stack because of issues with "Images to Stack Plus" */ var sCmds = newMenu("Stacks Menu Tool", newArray("Add Slice", "Delete Slice", "Set Slice...", "-", "3D Project...", "Grouped Z Project...", "Z Project...", "Orthogonal Views", "Plot Z-axis Profile", "Reslice [/]...", "-", "Images to Stack", "Images to Stack Plus", "Stack to Images", "Make Montage...", "-", "Make Substack...", "Stack to Hyperstack...", "-", "Combine...", "Concatenate...", "Flip Z", "Label...", "Animation Options...", "-", "Stack Sorter", "Series Labeler", "Fancy Slice Labels", "Reduce...", "Reverse", "Align Stack", "Align Stack with SIFT", "Set Label...", "Set Slice...", "-", "Start Animation", "Stop Animation", "Animation Options...", "-", "Next Slice [>]", "Previous Slice [<]", "-", "T1 Head (2.4M, 16-bits)")); macro "Stacks Menu Tool - C037T0b11ST8b09tTcb09k" { cmd = getArgument(); if (cmd == "Images to Stack") run(cmd, " "); else if (cmd != "-") run(cmd); } macro "Align Stack" { setOption("ScaleConversions", true); run("StackReg ", ""); } macro "Align Stack with SIFT" { run("Linear Stack Alignment with SIFT"); } macro "Developer Menu Built-in Tool" {} /* Default startup macro - replaced above to add more menu entries */ macro " Stacks Menu Built-in Tool" {} /* Default startup macro - replaced above to add more menu entries */ macro "Brush Built-in Tool" {} /* Default startup macro */ macro "Pencil Built-in Tool" {} macro "Flood Filler Built-in Tool" {} /* Default startup macro */ macro "Overlay Brush Built-in Tool" {} macro " Spray Can Built-in Tool" {} /* Removed to leave room for other tools that are more useful */ macro "Arrow Built-in Tool" {} /* Default startup macro */ macro "Selection Rotator Built-in Tool" {} macro " Smooth Wand Built-in Tool" {} /* Doesn't seem to do anything, versatile wand loaded from runatstartup instead */ macro " LUT Menu Built-in Tool" {} /* Commands available elsewhere, remove to leave room for other tools */ macro "Pixel Inspector Built-in Tool" {} macro " Bezier Curve Tool Built-in Tool" {} /* Removed to leave room for other tools that are more useful */ /* Dynamic Stats and Histogram Tools These two macro tools demonstrate how to perform dynamic measurements by dragging an ROI around. The "Dynamic Stats Tool" displays statistics in a text window and the "Dynamic Histogram Tool" displays a histogram in a plot window. Copy this file to the ImageJ/macros/toolsets folder and these macros will appear in the ">>" toolbar menu. */ var xroi, yroi, x, y, x1, y1; var leftClick = 16; var size = 50; // macro "Dynamic Stats Tool - C000T1d12dT9d12S" { macro "Dynamic Stats Tool Tool - Cc7aD0eD17D1cD2bD2fD59D5bD5cD5dD5eD7cD89Da9DaaDc9DdeDeaDebDf9DfaDffCedeD07D1eD3aD3cD3eD6aD6dD99DaeDb7Dd9CebdD27D3dD58D6eD8bD8dD8eDa8DabDacDadDdcDeeCbbdD06D64D72D84D96Da3Dd3CfefD21D29D38D39D52D69D97Da4DafDe1DedC66bD03D05D14D36D68D73D85D92Da2Dd2Cb48D0bD48D4bD4cD4dD4eD8aD9aDb8DbfDcfDdaDdbDe9CddeD02D65D88Db6Dd4Dd5Dd6C99cD30D31D33D34D35D77Db2Db3Db4Db5De3De4De5De6C339D12D16D32D40D41D42D43D44D45D46D62D75D86D87D93D94Dc2Dc3Dc4Dc5Dc6De2Df3Df4Df5Df6C45aD04D13D15D22D26D63D74D76D78D95Df2Ca27D18D49D5aD7aD7dD7eD8fD9fDfcC905D09D0aD1bD1fD2cD2eD5fD8cD9cD9dD9eDbaDbbDbcDbdDbeDfdDfeCa16D08D0fD28D2dD4aD7fD9bDb9DecDefCd9bD1aD4fD6fD79DcaDceDdf" { getCursorLoc(x, y, z, flags); if (selectionType == -1) makeRectangle(x - size / 2, y - size / 2, size, size); getSelectionCoordinates(xroi, yroi); l = lengthOf(xroi); xdroi = newArray(l); ydroi = newArray(l); stype = selectionType(); title1 = "Dynamic Stats"; title2 = "[" + title1 + "]"; f = title2; if (!isOpen(title1)) run("New... ", "name=" + title2 + " type=[Text File] width=20 height=6"); while (flags & leftClick != 0) { getCursorLoc(x1, y1, z, flags); for (i = 0; i < lengthOf(xroi); i++) { xdroi[i] = xroi[i] + x1 - x; ydroi[i] = yroi[i] + y1 - y; } makeSelection(stype, xdroi, ydroi); getStatistics(area, mean, min, max, std, histogram); stats = "Area:" + area + "\nMean:" + d2s(mean, 2) + "\nMin:" + min + "\nMax:" + max + "\nsd:" + d2s(std, 2); print(f, "\\Update:"); print(f, stats); wait(10); } } /* Use this tool to label a line selection with the length of the line. You can move the label until the mouse button is released. https://imagej.net//macros/tools/LabelLineTool.txt mod PJL for unit label, angle, overlay, use List instead of Measure etc. */ macro "Label Line Length Tool - C78aD1dD2fD5aD97Dd4CabcD2cD5dD69Da6Cc66D27D37D47CebbD57D58C469D0eD3eD4bD7bDb8Df5C358D3cD4dD8aDc7C258D0fD1eD1fD2dD2eD3dD4cD5bD5cD6aD6bD79D7aD89D98D99Da7Da8Db6Db7Dc6Dd5Dd6De4De5Df3Df4Cb33D00D01D02D03D04D05D06D07D08D10D11D12D13D14D15D16D17D18D28D38D48CeccD20D21D22D23D24D25D26CfffD0dD3fD4aD87Dc4CddeD3bD4eD78Db5C89bD6cDa9De3De6C369D88Dc5CccdD9aDd7Df2CeefD8bDc8" { // macro "Label Line Length Tool - Ca00P323ece00P424dde00" { getCursorLoc(x, y, z, flags); getPixelSize(unit, pixelWidth, pixelHeight); getDimensions(imageWidth, imageHeight, channels, slices, frames); fontSize = maxOf(14, imageWidth / 75); bestFont = getBestAvailableFont("SansSerif"); setFont(bestFont, fontSize, 'bold antialiased'); if (selectionType < 5 || selectionType > 7) exit("Line selection required"); List.setMeasurements; length = toString(List.get("Length"), 3); angle = toString(List.get("Angle"), 3); if (angle == 0 || angle == 90 || angle == -90) label = length + " " + unit; else label = length + " " + unit + ", " + angle + fromCharCode(0x2009) + fromCharCode(0x00B0); // run("Draw"); Overlay.addSelection("green"); run("Select None"); snapshot(); while (flags & 16 != 0) { drawString(label, x, y); getCursorLoc(x, y, z, flags); wait(25); reset(); } // drawString(label, x, y); setColor("green"); Overlay.drawString(label, x, y); List.clear(); } /* This tool draws a line and overlays its profile https://imagej.net//macros/tools/LineAndProfileTool.txt + font size modifiers */ macro "Line Profile Tool - Cd56D1cD2bD2cD37D3bD57D59D73Da3Dc3Dd3C54aD2dD6aDa7CfeeD1bD29D41D42D82D97Db4De2CbadD0eD2fD4bD6cDa9Dc5CecdD32D43D51D5aD85Da2Dc4CeaaD2aD34D47D62D83D95C108D0fD1eD1fD2eD3dD4cD5bD5cD6bD7aD89D98D99Da8Db7Dc6Dd5Dd6De5Df4Cc23D1dD3aD44D45D48D49D52D53D54D58D63D84D93Db3De3CcbeD88De6C615Df3C429D4dD8aDc7De4Cd78D33D38D39D46D4aD55D56D64D74D94Cd99D0dD65D92Dd4Df2CedfD5dD9aDd7C87bD3cD3eD79D7bDb6Db8Df5" { // macro "Line Profile Tool - C037L0ff0G0e2b157581c1f000" { /* Modification of https://imagej.net/macros/tools/LineAndProfileTool.txt PJL */ getCursorLoc(x, y, z, flags); xstart = x; ystart = y; x2 = x; y2 = y; getDimensions(imageWidth, imageHeight, channels, slices, frames); fontSize = maxOf(14, imageWidth / 75); fontSize /= getZoom; bestFont = getBestAvailableFont("SansSerif"); setFont(bestFont, fontSize, 'bold antialiased'); setLineWidth(2); if (getZoom >= 2) setLineWidth(1); while (true) { getCursorLoc(x, y, z, flags); if (flags & 16 == 0) { Overlay.addSelection; exit; } if (x != x2 || y != y2) { makeLine(xstart, ystart, x, y); overlayProfile(xstart, ystart, x, y, fontSize); } x2 = x; y2 = y; wait(30); } } function overlayProfile(x1, y1, x2, y2, fontSize) { Overlay.clear; p = getProfile(); Array.getStatistics(p, min, max, mean, stdDev); List.setMeasurements; text = 'length=' + d2s(List.getValue('Length'), 2) + ', min=' + d2s(min, 2) + ', max=' + d2s(max, 2); showStatus(text); range = max - min; scale = 50 / getZoom; Overlay.moveTo(x1, y1); setColor(Roi.getDefaultColor); for (i = 0; i < lengthOf(p); i++) { xs = (x2 - x1) / lengthOf(p); ys = (y1 - y2) / lengthOf(p); d = -scale * (p[i] - min) / range; a = atan2(y2 - y1, x2 - x1); Overlay.lineTo(x1 + i * xs - d * sin(a), y1 - i * ys + d * cos(a)); } Overlay.lineTo(x2, y2); a = 360 - a * 180 / PI; x = x1 + ys * fontSize * 1.25; y = y1 + xs * fontSize * 1.25; Overlay.drawString(d2s(min, 0) + '-' + d2s(max, 0), x, y, a); Overlay.show; } /* These two macros loop through the tools listed in an array using "F8" and "F9" as keyboard shortcuts (forward and reverse cycling). https://imagej.net/docs/guide/146-35.html */ var toolsIndex; var toolbarTools = newArray("rectangle", "roundrect", "oval", "ellipse", "brush", "polygon", "freehand", "line", "freeline", "polyline", "arrow", "wand", "dropper", "angle", "point", "multipoint", "text"); macro "Cycle Tools Fwd [Q]" { setTool(toolbarTools[toolsIndex++]); if (toolsIndex == toolbarTools.length) toolsIndex = 0; } macro "Cycle Tools Rwd" { if (toolsIndex < 0) toolsIndex = toolbarTools.length - 1; setTool(toolbarTools[toolsIndex--]); } macro "Roi_Manager_Add_and_Outline_in_Overlay [F9]" { /* Originally from https://imagej.net/macros/RoiManagerMacros.txt This version requires ASC-modified BAR color functions: getHexColorFromColorName v230713: Detects selection, adds stroke with and color preferences and sound confirmation v230718: Confirmation beep is now a preference setting (see Roi_Manager_Add_and_Outline_in_Overlay-Prefs_v______.ijm) v230724: Simplified. */ macroL = "Roi_Manager_Add_and_Outline_in_Overlay_v230724.ijm"; if (selectionType < 0) exit("No selection to add to ROI Manager"); saveSettings(); refsMacro = call("ij.Prefs.get", "asc.roi.prefs.macro", "Not yet run"); roiName = ""; if (refsMacro == "Not yet run") { if (getBoolean("The 'Roi_Manager_Add_and_Outline_in_Overlay-Prefs' macro has not be run yet; do you want to exit and run that first?")) exit(); } // else IJ.log(refsMacro); strokeColor = call("ij.Prefs.get", "asc.roi.stroke.color", "yellow"); strokeWidth = parseInt(call("ij.Prefs.get", "asc.roi.stroke.width", 1)); shadowColor = call("ij.Prefs.get", "asc.roi.shadow.color", "none"); shadowDropN = parseInt(call("ij.Prefs.get", "asc.roi.shadowdrop.multiplier", 1)); fillColor = call("ij.Prefs.get", "asc.roi.fill.color", "none"); confirmBeep = call("ij.Prefs.get", "asc.roi.confirm.beep", true); if (!startsWith(strokeColor, "#")) strokeColor = getHexColorFromColorName(strokeColor); roiManager("add"); roiName = Roi.getName; if (shadowColor != "none") { run("Translate... ", "x=" + shadowDropN * strokeWidth + " y=" + shadowDropN * strokeWidth); setSelectionName(roiName + "_" + shadowColor + "-shadow"); Overlay.addSelection(shadowColor, floor(1.5 * strokeWidth * abs(shadowDropN))); roiManager("Deselect"); } roiManager("select", RoiManager.size() - 1); Overlay.addSelection(strokeColor, strokeWidth) if (confirmBeep) beep(); restoreSettings; if (roiName != "") showStatus("Selected area added as ROI named:" + roiName); /* End of Roi_Manager_Add_and_Outline_in_Overlay function */ } 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 & leftClick == 0) exit(); if (x != x2 || y != y2) lineTo(x, y); x2 = x; y2 = y; wait(10); } } function setColorToBackgound() { savep = getPixel(0, 0); /* finds corner pixel value. */ makeRectangle(0, 0, 1, 1); /*selects corner pixel. */ run("Clear", "slice"); /* sets corner pixel to background color. */ background = getPixel(0, 0); /* stores this background value. */ run("Select None"); /*necessary? */ setPixel(0, 0, savep); /*resets corner pixel to original value. */ setColor(background); /* setColor to the background color. */ } macro "-" {} /* Menu Divider */ macro "Run last run macro or script again" { /* v240308: Added prefs save. This is nicer for debugging after a restart - I don't think the intention was ever to keep rerunning the last autostart macro v240314: Makes sure you did not try to run a non-macro last time :-$ */ lastRunMacro = getInfo("macro.filepath"); if (indexOf(lastRunMacro, "AutoRun") >= 0) lastRunMacro = "null"; if (!endsWith(toLowerCase(lastRunMacro), ".ijm") && !endsWith(toLowerCase(lastRunMacro), ".txt")) lastRunMacro = "null"; if (lastRunMacro != "null") { call("ij.Prefs.set", "lastsaved.lastrun.macro", lastRunMacro); runMacro(lastRunMacro); } else { lastSavedLastRunMacro = call("ij.Prefs.get", "lastsaved.lastrun.macro", "null"); if (indexOf(lastSavedLastRunMacro, "AutoRun") < 0 && File.isFile(lastSavedLastRunMacro)) runMacro(lastSavedLastRunMacro); /* no need to resave */ // else IJ.log("No macros or scripts have run since startup and no macro found from prefs, so no rerun"); } } macro "-" {} /* Menu Divider */ macro "Run Demo" { runDemo(); } macro "-" {} /* Menu Divider */ macro "UnInvert LUT" { if (is("Inverting LUT")) run("Invert LUT"); } /* !!!! Note the macro title in the startup includes the shortcut [F2] !!!! */ macro "Analyze Particles [F2]" { /* v220921: Replaced unnecessary restoreExit with exit v231018-9: Added ROI-by-ROI option v240104: Exit if border issue is now optional. */ macroL = "Analyze_objects_v240104.ijm"; orID = getImageID(); /* get id of image and title */ t = getTitle(); tPath = getDirectory("image"); if (tPath == "") tPath = File.directory; if (indexOf(tPath, "AutoRun") >= 0) tPath = ""; getPixelSize(unit, pixelWidth, pixelHeight); /* Check to see if there is a location already set for analysis */ selType = selectionType; if ((selType >= 0 && selType < 4) || selType == 9) { getSelectionBounds(selPosStartX, selPosStartY, originalSelEWidth, originalSelEHeight); /* smallest rectangle that can completely contain the current selection */ areaSelectionExists = true; } else areaSelectionExists = false; run("Select None"); if (!is("binary")) { binaryChoice = getBoolean("Binary image required for F2 shortcut analysis; apply default auto-threshold and continue?"); if (binaryChoice) { /* Quick-n-dirty threshold if not previously thresholded */ getThreshold(t1, t2); if (t1 == -1) { run("Auto Threshold", "method=Default"); setOption("BlackBackground", false); run("Make Binary"); } } else exit("Goodbye"); } if (is("Inverting LUT")) run("Invert LUT"); setThreshold(0, 127); setOption("BlackBackground", false); /* Make sure black objects on white background for consistency */ yMax = Image.height - 1; xMax = Image.width - 1; cornerPixels = newArray(getPixel(0, 0), getPixel(1, 1), getPixel(0, yMax), getPixel(xMax, 0), getPixel(xMax, yMax), getPixel(xMax - 1, yMax - 1)); Array.getStatistics(cornerPixels, cornerMin, cornerMax, cornerMean, cornerStdDev); if (cornerMax != cornerMin) { exitNow = getBoolean("There are different pixel intensities at the corners; do you want to exit now?"); /* Sometimes the outline procedure will leave a pixel border around the outside - this next step checks for this. i.e. the corner 4 pixels should now be all black, if not, we have a "border issue". */ if (exitNow) exit("Goodbye " + macroL); invertNow = getBoolean("Invert current image contrast?"); if (invertNow) run("Invert"); } else if (cornerMean < 1) run("Invert"); nROIs = roiManager("count"); analyzeByROI = false; analyzeBySelection = false; replaceROIs = false; restrictHow = "Analyze entire image"; if (nROIs > 0 || areaSelectionExists) { Dialog.create("Restrict analysis area \(" + macroL + "\)"); restrictionOptions = newArray(restrictHow); if (areaSelectionExists) restrictionOptions = Array.concat(restrictionOptions, "Restrict analysis to current selection"); if (nROIs > 0) { if (nROIs == 1) restrictionOptions = Array.concat(restrictionOptions, "Restrict analysis to ROI"); else { restrictionOptions = Array.concat(restrictionOptions, "Restrict analysis to combined ROIs"); restrictionOptions = Array.concat(restrictionOptions, "Analyze each ROI one by one"); } } Dialog.addRadioButtonGroup("Analysis region:", restrictionOptions, restrictionOptions.length, 1, restrictionOptions[0]); Dialog.show; restrictHow = Dialog.getRadioButton(); roiManager("deselect"); } if (nROIs > 0) { orROINames = newArray(""); for (i = 0; i < nROIs; i++) { roiManager("Select", i); orROINames[i] = Roi.getName; } } if (restrictHow != "Analyze entire image") { Dialog.create("Restricted area analysis options \(" + macroL + "\)"); Dialog.addString("Object size range", "0-Infinity"); if (!startsWith(toLowerCase(unit), "pix")) Dialog.addCheckbox("Size range in pixels", false); Dialog.addString("Circularity", "0.00-1.00"); analysisOptions = newArray("Add overlay for each analyzed object", "Create mask with all new ROIs", "Display results", "Exclude objects touching edge", "Clear existing results", "Add new ROI to Manager"); analysisOptionsChecks = newArray(false, true, true, true, true, true); Dialog.addCheckboxGroup(analysisOptions.length, 1, analysisOptions, analysisOptionsChecks); holeOptions = newArray("Ignore holes", "Fill holes", "Create composite ROIs \(with holes\) instead of solid ROIs"); Dialog.addRadioButtonGroup("Hole handling:___________", holeOptions, holeOptions.length, 1, holeOptions[2]); if (nROIs > 0) Dialog.addCheckbox("If adding ROIs delete original ROIs at end", true); Dialog.addCheckbox("Batchmode off", false); Dialog.show; analyzeString = "size=" + Dialog.getString; if (!startsWith(toLowerCase(unit), "pix")) { if (Dialog.getCheckbox) analyzeString += " pixel "; } analyzeString += " circularity=" + Dialog.getString(); if (Dialog.getCheckbox()) analyzeString += " show=Overlay"; createMask = Dialog.getCheckbox(); if (Dialog.getCheckbox()) analyzeString += " display"; if (Dialog.getCheckbox()) analyzeString += " exclude"; clearResults = Dialog.getCheckbox(); addROI = Dialog.getCheckbox(); if (addROI) analyzeString += " add"; holeOption = Dialog.getRadioButton(); if (holeOption == "Fill holes") analyzeString += " include"; else if (startsWith(holeOption, "Create composite")) analyzeString += " composite"; replaceROIs = Dialog.getCheckbox(); batchmodeOff = Dialog.getCheckbox(); if (!batchmodeOff) setBatchMode(true); if (clearResults) run("Clear Results"); if (restrictHow == "Restrict analysis to current selection") { if (addROI && replaceROIs) roiManager("reset"); run("Restore Selection"); run("Analyze Particles...", analyzeString); } else { for (i = 0; i < nROIs - 1; i++) { roiManager("Select", i); run("Analyze Particles...", analyzeString); } if (addROI && replaceROIs) roiManager("Select", Array.getSequence(nROIs)); if (createMask && addROI) { newImage("Mask of " + t, "8-bit white", Image.width, Image.height, 1); finalCount = roiManager("count"); for (i = nROIs; i < finalCount - 1; i++) { roiManager("Select", i); run("Invert"); } } } } else run("Analyze Particles..."); } macro "Remove Black Edge Objects" { /* v230926 This version uses the v230106 of the removeBlackEdgeObjects function that does not require any plugins of restored settings. v250423 Added invert LUT option */ if (is("Inverting LUT")) { invLUT = getBoolean("Image has an inverted LUT: Invert LUT and invert contrast first?"); if (invLUT) { run("Invert LUT"); run("Invert"); } else if (getBoolean("Exit and rethink this?")) exit(); removeBlackEdgeObjects(); if (invLUT) { run("Invert"); run("Invert LUT"); } } else removeBlackEdgeObjects(); } /* !!!! Note the macro title in the startup includes the shortcut [F3] !!!! */ macro "Copy Image Scale [F3]" { macroL = "Copy_Image_Scale_v230104b.ijm"; requires("1.53i"); /* for flashing images */ imgC = nImages; if (imgC == 0) exit("No images open."); imgTitles = getList("image.titles"); imgSTitle = getTitle(); /* for default source image */ imgPxWs = newArray(imgC); imgPxHs = newArray(imgC); imgPxDs = newArray(imgC); imgUs = newArray(imgC); screenH = screenHeight; screenW = screenWidth; maxImgs = round(screenH / 40); iSourceImgs = newArray(); /* Images that have a scale so can be sources */ iNoScaleImgs = newArray(); /* Images that have no scale so they cannot be sources */ if (imgC < 6) { showProg = true; setBatchMode(true); /* Only to the 1st Dialog */ } else showProg = false; showTime = 2000 / imgC; iDefSImg = -1; for (i = 0, imgScaleC = 0, noImgScaleC = 0; i < imgC; i++) { selectWindow(imgTitles[i]); /* image windows are numbered sequentially in integers from 1 upwards */ /* now populate data arrays one by one */ getVoxelSize(imgPxWs[i], imgPxHs[i], imgPxDs[i], imgUs[i]); /* Now assume that if there is no unit there is no scale so this must be a target not a source */ if (imgUs[i] == "pixels" || imgPxWs[i] == 1 || imgUs[i] == "") { iNoScaleImgs[noImgScaleC] = i; noImgScaleC++; /* count the number of images without a scale - these cannot be source images */ if (showProg) { showStatus("No image scale for " + imgTitles[i], "flash image red " + showTime + "mS"); wait(showTime); } } else { /* if there is a unit then this can be a source (or a target) */ if (imgTitles[i] == imgSTitle) iDefSImg = i; /* for default source image */ iSourceImgs[imgScaleC] = d2s(i, 0); /* converted to string for radiobuttongroup */ imgScaleC++; /* Count the number of images with a scale so that the source array can be trimmed */ if (showProg) { showStatus("Image scale retrieved for " + imgTitles[i], "flash image green " + showTime + "mS"); wait(showTime); } } } imgTotal = imgScaleC + noImgScaleC; if (imgScaleC == 1 && noImgScaleC == 0) /* The only images open already has a scale */ exit("Nothing to do; the only image open already has a scale"); else if (imgTotal < imgC) exit("Only " + imgTotal + " images out of " + imgC + "were checked for some reason, please report error"); else if (imgScaleC == 0) exit("No open images have scales to copy"); if (iDefSImg < 0) iDefSImg = iSourceImgs[0]; if (imgScaleC == 1 && noImgScaleC == 1) { /* Only one source and one target image so no decisions necessary */ sAI = iSourceImgs[0]; selectWindow(imgTitles[iNoScaleImgs[0]]); setVoxelSize(imgPxWs[sAI], imgPxHs[sAI], imgPxDs[sAI], imgUs[sAI]); showStatus("Scale auto-copied from scaled to unscaled image", "flash image green " + showTime + "mS"); } else if (imgC > maxImgs) { setBatchMode("exit and display"); Dialog.create(macroL + ": Apply current scale to all other open images"); Dialog.addMessage(imgC + " images open, the current menu is limited to " + maxImgs); imgWidthU = getWidth() * imgPxWs[imgIDCi]; Dialog.addMessage("Current image title: " + imgTitles[iDefSImg] + ", width: " + imgWidthU + " " + imgUs[iDefSImg] + "\nCurrent image pixel width: " + imgPxWs[iDefSImg] + " " + imgUs[iDefSImg] + "\nCurrent image pixel height: " + imgPxHs[iDefSImg] + " " + imgUs[iDefSImg]); Dialog.addCheckbox("Copy scale from current image to all other open images?", true); if (imgScaleC > 1) Dialog.addCheckbox("Overwrite existing scales?", false); Dialog.addCheckbox("Show progress \(slows things down a bit\)", showProg); Dialog.show(); copyToAll = Dialog.getCheckbox(); showProg = Dialog.getCheckbox(); if (!copyToAll) exit("Goodbye"); if (imgScaleC > 1) copyToAllOv = Dialog.getCheckbox(); for (i = 0; i < imgC; i++) { if (i != iDefSImg) { if (copyToAllOv || imgUs[i] == "pixel") { // tII = iNoScaleImgs[i]; selectWindow(imgTitles[i]); setVoxelSize(imgPxWs[iDefSImg], imgPxHs[iDefSImg], imgPxDs[iDefSImg], imgUs[iDefSImg]); if (showProg) showStatus("Image scale applied to " + getTitle(), "flash image green " + showTime + "mS"); } } } } else { setBatchMode("exit and display"); Dialog.create(macroL + ": Select source and target images"); Dialog.addMessage(imgC + " images open, " + imgScaleC + " with scale and " + noImgScaleC + " without"); Dialog.addCheckbox("Show progress \(slows things down a bit\)", showProg); /* Provide source image selection */ if (imgScaleC == 1) { Dialog.addMessage("Source image:__________________"); imgISInfoLine = "" + i + ": " + imgTitles[iDefSImg]; if (imgPxWs[iDefSImg] == imgPxHs[iDefSImg] && imgPxHs[iDefSImg] == imgPxDs[iDefSImg]) imgISInfoLine += ", pixel xyz size = " + imgPxHs[iDefSImg] + " " + imgUs[iDefSImg]; else imgISInfoLine += ", pixel xyz dimensions = " + imgPxWs[iDefSImg] + ", " + imgPxHs[iDefSImg] + ", " + imgPxDs[iDefSImg] + " " + imgUs[iDefSImg]; Dialog.setInsets(-5, 0, 0); Dialog.addMessage(imgISInfoLine); } else { Dialog.addMessage("Images that already have scales set:_______________"); imgISInfo = ""; for (i = 0, maxString = 0; i < imgScaleC; i++) { sN = iSourceImgs[i]; imgISInfoLine = "" + sN + ": " + imgTitles[sN]; if (imgPxWs[sN] == imgPxHs[sN] && imgPxHs[sN] == imgPxDs[sN]) imgISInfoLine += ", pixel xyz size = " + imgPxHs[sN] + " " + imgUs[sN]; else imgISInfoLine += ", pixel xyz dimensions = " + imgPxWs[sN] + ", " + imgPxHs[sN] + ", " + imgPxDs[sN] + " " + imgUs[sN]; stringL = lengthOf(imgISInfoLine); if (stringL > maxString) maxString = stringL; if (i < imgScaleC - 1) imgISInfoLine + = "\n"; imgISInfo += imgISInfoLine; } Dialog.addMessage(imgISInfo); Dialog.addRadioButtonGroup("Select scale source image from list above:", iSourceImgs, round(imgScaleC / 10), 10, iDefSImg); } /* Provide target image selection(s) */ if (noImgScaleC > 0) { imgNSInfo = ""; Dialog.addMessage("Target image\(s\) without scale:"); if (noImgScaleC > 1) Dialog.addCheckbox("Select all " + noImgScaleC + " images without scale \(overrides selections below\)", false); targetNSCheckBoxGroup = newArray(); imgNSInfoLine = ""; imgNSInfo = ""; for (i = 0, maxString = 0; i < noImgScaleC; i++) { tN = iNoScaleImgs[i]; imgNSInfoLine = "" + tN + ": " + imgTitles[tN]; targetNSCheckBoxGroup = Array.concat(targetNSCheckBoxGroup, d2s(tN, 0)); stringL = lengthOf(imgNSInfoLine); if (stringL > maxString) maxString = stringL; if (i < noImgScaleC - 1) imgNSInfoLine + = "\n"; imgNSInfo += imgNSInfoLine; } Dialog.addMessage(imgNSInfo); targetNSDefaults = Array.fill(newArray(noImgScaleC), true); Dialog.setInsets(5, 20, 0); Dialog.addCheckboxGroup(maxOf(1, round(noImgScaleC / 10)), 10, targetNSCheckBoxGroup, targetNSDefaults); /* Dialog.addCheckboxGroup(rows, columns, labels, defaults) */ } if (imgScaleC > 1) { Dialog.addMessage("Target image\(s\) that already have a scale:_______________"); Dialog.addCheckbox("Select all " + imgScaleC + " images with scale \(overrides selections below\)", false); targetISCheckBoxGroup = newArray(); for (i = 0; i < imgScaleC; i++) { /* Don't generate title and image information again as that is already listed in the potential source section */ sN = iSourceImgs[i]; targetISCheckBoxGroup = Array.concat(targetISCheckBoxGroup, d2s(sN, 0)); } targetISDefaults = Array.fill(newArray(imgScaleC), false); Dialog.addMessage("Choose to overide scales in image\(s\) with scale listed in first section:"); Dialog.setInsets(5, 20, 0); Dialog.addCheckboxGroup(maxOf(1, round(imgScaleC / 10)), 10, targetISCheckBoxGroup, targetISDefaults); } Dialog.show(); showProg = Dialog.getCheckbox(); if (imgScaleC == 1) sII = iSourceImgs[0]; else sII = parseInt(Dialog.getRadioButton()); /* works fine without parseInt but this could avoid issues in the future */ sPixelWidth = imgPxWs[sII]; sPixelHeight = imgPxHs[sII]; sPixelDepth = imgPxDs[sII]; sPixelUnit = imgUs[sII]; /* Provide target image selection(s) */ tImgs = newArray(); tempTImgs = newArray(); if (!showProg) setBatchMode("true"); if (noImgScaleC > 0) { if (noImgScaleC > 1) targetAllNSImgs = Dialog.getCheckbox(); else targetAllNSImgs = false; for (i = 0; i < noImgScaleC; i++) if (Dialog.getCheckbox()) tempTImgs = Array.concat(tempTImgs, targetNSCheckBoxGroup[i]); if (targetAllNSImgs) tImgs = targetNSCheckBoxGroup; else tImgs = tempTImgs; tempTImgs = newArray(); } if (imgScaleC > 1) { targetAllSImgs = Dialog.getCheckbox(); for (i = 0; i < imgScaleC; i++) if (Dialog.getCheckbox()) tempTImgs = Array.concat(tempTImgs, targetISCheckBoxGroup[i]); if (targetAllSImgs) tImgs = Array.concat(tImgs, targetISCheckBoxGroup); else tImgs = Array.concat(tImgs, tempTImgs); } /* End 'Select source and target images' Dialog */ targetN = tImgs.length; if (targetN < 1) exit("No target images selected"); else { for (i = 0; i < targetN; i++) { iTargetImg = parseInt(tImgs[i]); selectWindow(imgTitles[iTargetImg]); setVoxelSize(sPixelWidth, sPixelHeight, sPixelDepth, sPixelUnit); if (showProg) showStatus("Image scale applied to " + imgTitles[iTargetImg], "flash image green " + showTime + "mS"); } } } setBatchMode("exit and display"); showStatus("Scale copy macro completed"); } // macro "MySEM Set Scale Tool - CbooP51b1f5fbbf5f1b15510T5c10C " { // run("MySEM Set Scale"); // } macro "MySEM Set Scale" { run("MySEM Set Scale"); } /* !!!! Note the ASC Set Scale macro title in the startup includes the shortcut [F4] !!!! */ macro "ASC Set Scale [F4]" { /* ASC microscope calibrations based on shared calibration file. The shared calibration file uses the same calibrations used for ASC Photoshop installations. The Photoshop "psp" is modified by removing the quotation marks and special characters and using commas as value separators. If edited in Excel the output should be pure text (e.g. MSDOS). Full history in ijm file. v230510: DSX aspect ratio supported. v230516 Change name of getEXIF function v230809: Added VueScan dpi import. v230810: Minor change to text. F1: Updated indexOfArray functions. v231005: More descriptive log output. Removed restoreExit requirement. v231006: EsB images: If the bad final scan line has not already been removed a checkbox is added for its removal ("_crp" will be appended to filename). v240119: EVO images: Adds image area above anno bar selection option. b: adds crop and rename options v240313: Added some spaces after commas. v240329: Does not try to extract scale from OI EDS generated TIFs. Changed full name to "ASC-Set_Scale_from_Calibrations_or_headers_etc_". v240411: Local settings directory fixed for ImageJ. v240509: Gives you choice of options when a VueScan DPI header is detected. v240827: Mostly cosmetic changes. Dialog error fixed but I suspect there is an unbalanced parenthesis somewhere. v250505: Now determines if running Fiji by presence of Fiji_Package_Maker plugin. v250919: Offers Bioformats as metadata source for TIF images (works with new Olympus/Evident PRECiV software). v260304: Adds OME headers used by Olympus PRECiV software and sitinguishes between old and new format DSX1000 images. v260306-9: Fixed some issues with SmartSEM header not being read for microscope ID. Displays objective lens information from DEX=PRECiV. v260309: LEXT functionality restored. v260313: Use endsWidth to determine filetype from filename instead of strings. Checks for image widths > 1 inch. v260317: Moved calibrate-by-selection option up to speed things up if that is all that is wanted. Logs OME instrument errors rather than bails on them. v260323: Removed redundant fileName2. */ macroL = "ASC-Set_Scale_from_Calibrations_or_headers_etc_v260323.ijm"; if (nImages < 1) exit("This macro requires an open image"); mBytes = d2s(getValue("image.size") / 10E5, 2); path = getDirectory("image"); /* Gets the path+name of the active image */ fileName = getInfo("image.filename"); if (fileName == "") fileName = getTitle(); if (endsWith(fileName, " - macro image")) fileName = replace(fileName, " - macro image", ""); /* Fixes an issue with Bio-Formats import renames the file so the link to the original is lost */ fullPath = path + fileName; if (!File.exists(fullPath)) IJ.log("Original file \(" + fileName + "\) was not found, this could cause problems"); orImageID = getImageID(); prefsNameKey = "asc_SetScale."; micronS = getInfo("micrometer.abbreviation"); xChar = fromCharCode(0x00D7); userPath = getInfo("user.dir"); fS = File.separator; metaData = getMetadata("Info"); diagnostics = false; tifFile = false; tiffFile = false; feiTIF = false; zeissTIF = false; smartSEM = false; vsiFile = false; frameFactor = false; bioformats = true; microscope = "Not found"; microscopeInfo = "Microscope information unavailable"; scaleAppliedInfo = ""; ascCalibMicroscopes = newArray("Zeiss 1540EsB", "Zeiss 1540XB", "BX41M", "LEXT"); /* Just the microscopes that have preset calibrated scales available */ getDimensions(imageWidth, imageHeight, channels, slices, frames); /* only width and slices currently used */ totalPixels = imageWidth * imageHeight; degCh = fromCharCode(0x00B0); CZScale = newArray(); CZUnit = micronS; selType = selectionType(); unitChoices = newArray("mm", micronS, "microns", "nm", "Angstroms", "pm"); /* DO NOT CHANGE W/O changing pmSF nmSF. micronS is micrometer symbol defined above, Angstrom = fromCharCode(0x212B) does not work in dialogs */ kUnits = newArray("m", "mm", micronS, "nm", "pm"); oddballUnitChoices = newArray("cm", "inches", "human hairs"); microscopes = newArray(""); localFileDate = 0; netFileDate = 0; zFileDate = 0; foundBF = false; /* ASC message theme */ infoColor = "#006db0"; /* Honolulu blue */ instructionColor = "#798541"; /* green_dark_modern (121,133,65) AKA Wasabi */ infoWarningColor = "#ff69b4"; /* pink_modern AKA hot pink */ infoFontSize = 12; /*********************/ tableScale = false; if (Table.size != 0) { tablePW = Table.get("PixelWidth", 0); /* This value can be added to the table by the ASC Extended Geometries macro */ tablePAR = Table.get("PixelAR", 0); /* This value can be added to the table by the ASC Extended Geometries macro */ tableUnit = Table.getString("Unit", 0); /* This value can be added to the table by the ASC Extended Geometries macro */ tableTitle = Table.title; if (tablePW != NaN && tablePAR != NaN && tableUnit != "null") { tableScale = true; tablePW = parseFloat(tablePW); tablePAR = parseFloat(tablePAR); tablePH = tablePW / tablePAR; tableLCF = (tablePH + tablePW) / 2; } } if (selType >= 0){ /* It will speed this macro up if we just skip everything else is the selection is what we want to work with, especially if working from a remote drive. */ if (selType == 5) { getLine(x1, y1, x2, y2, null); lengthPx = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); selAngle = (180 / PI) * Math.atan2((y1 - y2), (x1 - x2)); LineLengthinfo = "Line Length: Calibrate to selected line length \(" + lengthPx + " pixels, " + selAngle + fromCharCode(0x00B0) + "\)"; } else { getSelectionBounds(null, null, selWidth, selHeight); SelectionWidthinfo = "Selection width: Calibrate to selection bounds width \(" + selWidth + " pixels\)"; SelectionHeightinfo = "Selection height: Calibrate to selection bounds height \(" + selHeight + " pixels\)"; SelectionWidthHeightinfo = "Sel. width/height: Calibrate to selection width and height \(for aspected pixels\)"; } selTypes = newArray("rectangle", "oval", "polygon", "freehand", "traced", "straight line", "segmented line", "freehand line", "angle", "composite", "point"); justSelection = getBoolean("There is a " + selTypes[selType] + " selection already, do you want to calibrate using this selection?", "Yes", "No \(Selection will be removed\)"); if (justSelection){ microscopes = newArray(); Dialog.create("Choose selection calibration method"); if (selectionType == 5) { Dialog.addMessage(LineLengthinfo, infoFontSize, infoColor); microscopes = Array.concat(microscopes, "Line_length"); } else if (selectionType >= 0) { Dialog.addMessage(SelectionWidthinfo + "\n" + SelectionHeightinfo + "\n" + SelectionWidthHeightinfo, infoFontSize, infoColor); microscopes = Array.concat(microscopes, "Selection_width", "Selection_height", "Selection_width/height"); } if (tableScale) { Dialog.addMessage("Embedded scale from table:\n" + tableTitle + "\nPixel calibration: " + tableLCF + " " + tableUnit, infoFontSize, infoColor); microscopes = Array.concat("Scale_from_table", microscopes); } /* default to last used */ if (selType == 5) iScope = indexOfArray(microscopes, "Line_length", 0); else if (selType >= 0) iScope = indexOfArray(microscopes, "Selection_width", 0); Dialog.addRadioButtonGroup("Choose microscope or method:", microscopes, round(microscopes.length / 2.5), 3, microscopes[iScope]); Dialog.show(); microscope = Dialog.getRadioButton(); if (startsWith(microscope, "Selection_width")) distanceP = selWidth; else if (microscope == "Selection_height") distanceP = selHeight; else if (microscope == "Line_length") distanceP = lengthPx; else distanceP = 0; if (microscope == "Selection_width/height") aR = selWidth / selHeight; else aR = 1; Dialog.create("Set scale based on selection \(" + macroL + "\)"); if (microscope == "Selection_width/height") Dialog.addNumber("Selection_width", distanceP, 6, 10, "pixels"); else Dialog.addNumber(microscope, distanceP, 6, 10, "pixels"); getPixelSize(unit, pixelWidth, pixelHeight); Dialog.addNumber("Calibrated distance", distanceP * pixelWidth, 6, 10, "in scaled units"); aRLabel = ""; if (microscope == "Selection_width/height") aRLabel = "From selWidth / selHeight"; Dialog.addNumber("Pixel aspect ratio", aR, 6, 10, aRLabel); moreUnitChoices = Array.concat(unitChoices, oddballUnitChoices, "pixels"); Dialog.addChoice("Choose Unit", moreUnitChoices, moreUnitChoices[indexOfArray(moreUnitChoices, unit, "pixels")]); if (slices > 1) Dialog.addNumber("Z axis voxel depth", 0, 6, 10, "in scaled units"); Dialog.show; distanceP = Dialog.getNumber; distanceC = Dialog.getNumber; aR = Dialog.getNumber; /* width/height */ unit = Dialog.getChoice; if (slices > 1) zVoxel = Dialog.getNumber; else zVoxel = 0; pixelWidth = aR / distanceP; pixelHeight = pixelWidth / aR; if (unit == "um") unit = micronS;; if (slices > 1) run("Properties...", "channels=" + channels + " slices=" + slices + " frames=" + frames + " pixel_width=" + pixelWidth + " pixel_height=" + pixelHeight + " voxel_depth=" + zVoxel + " unit=" + unit); /* This macro version does not open up the properties window to add a z-scale */ else run("Set Scale...", "distance=" + distanceP + " known=" + distanceC + " pixel=" + aR + " unit=" + unit); exit(); } else { run("Select None"); selType = -1; } } if (endsWith(toLowerCase(fileName), ".ols")){ olsFile = true; microscopes = Array.concat(microscopes, "LEXT"); microscope = "LEXT"; } else olsFile = false; if (endsWith(toLowerCase(fileName), ".dsx")) dsxFile = true; else dsxFile = false; dsxSettings = false; if (olsFile || dsxFile) bioformats = false; /* units are wrong for vls DSX and OLS bioformat import */ /* First lets quickly check for a Bio-Formats readable scale */ if (File.exists(fullPath) && bioformats){ BFs = getScaleFromBioformats(fullPath); unlikely = false; if (BFs[0] > (25400 / imageWidth)) unlikely = true; /* Seems unlikely that the image width is more than 1" */ sensibleScale = sensibleUnits(BFs[0], micronS); BFs[1] = BFs[1] * sensibleScale[0] / BFs[0]; BFs[2] = BFs[2] * sensibleScale[0] / BFs[0]; BFs[0] = sensibleScale[0]; unit = sensibleScale[1]; /* the Bio-formats extensions should return the physical pixel sizes in microns */ if (BFs[0] > 0 && BFs[1] > 0){ foundBF = true; boolInfo = "Bio-Formats: pixel xyz size " + BFs[0] + xChar + BFs[1] + xChar + BFs[2] + " " + unit + " \(image width = " + imageWidth * BFs[0] + " " + unit + "\)"; if (unlikely) boolInfo += ". Image width would be wider than 1 inch!"; applyAndExit = getBoolean(boolInfo, "Apply scale and exit?", "Apply and continue"); setVoxelSize(BFs[0], BFs[1], BFs[2], unit); if (BFs[0] == BFs[1]) IJ.log("Pixel size set from Bioformats metadata: " + BFs[0] + " " + unit + ", Z = " + BFs[2] + " " + unit); else IJ.log("Pixel size set from Bioformats metadata: X = " + BFs[0] + " " + unit + ", Y = " + BFs[1] + " " + unit + ", Z = " + BFs[2] + " " + unit); showStatus("Image dimensional scale set using Bioformats extensions", "flash green"); if (applyAndExit) exit(); } } getVoxelSize(voxelWidth, voxelHeight, voxelDepth, voxelUnit); /* Now check table for embedded values */ if (indexOf(metaData, "openmicroscopy.org") >= 0) openMicroscopy = true; else openMicroscopy = false; if (checkForPluginNameContains("Exif_reader")) exifReading = true; else exifReading = false; if (path != "") { if (fileName != "") { if (endsWith(toLowerCase(fileName), ".vsi")) { vsiFile = true; if (indexOf(metaData, "BX 41M-LED") >= 0) microscope = "BX41M"; else if (indexOf(metaData, "DSX1000") >= 0 || (indexOf(metaData, "OLYMPUS") >= 0 && indexOf(metaData, "Physical pixel size = ") >= 0)) microscope = "DSX1000"; } else if (endsWith(toLowerCase(fileName), ".tiff")) tiffFile = true; else if (endsWith(toLowerCase(fileName), ".tif")) tifFile = true; } } if (indexOf(metaData, "VueScan") >= 0 && exifReading) { microscope = "VueScan"; metaVS = getExifDataForSelectedImage(); XRes = getVueScanExifTagFromMetaData(metaVS, "X Resolution"); if (indexOf(XRes, "not found") < 0) { YRes = getVueScanExifTagFromMetaData(metaVS, "Y Resolution"); if (indexOf(XRes, "dots per inch") >= 0) { if (getBoolean("VueScan DPI Header detected", "Apply header DPI and exit?", "Continue with full options")) { dpiX = parseInt(XRes); dpiY = parseInt(YRes); pixelAR = d2s(pixelWidth / pixelHeight, 5); /* Enforce sinsible pixel AR */ if (pixelAR == "1.00000") pixelAR = 1; pixelW = 25.4 / dpiX; /* in mm */ newUnits = sensibleUnits(pixelW, "mm"); run("Set Scale...", "distance=1 known=" + newUnits[0] + " pixel=" + pixelAR + " unit=" + newUnits[1]); scaleAppliedInfo = cleanLabel("File: " + fileName + "\nPixel size set from VueScan Exif header: dpi = " + dpiX + ", pixel width = " + newUnits[0] + " " + newUnits[1] + ", pixel aspect ratio = " + pixelAR); IJ.log(scaleAppliedInfo); setMetadata("Info", metaVS); keepGoing = getBoolean(scaleAppliedInfo, "More options", "Finish"); } } } } if (dsxFile) { microscope = "DSX1000\(old\)"; umPerPixelX = parseInt(getDSXExifTagFromMetaData(metaData, "ColorDataPerPixelX")) * 10E-7; umPerPixelY = parseInt(getDSXExifTagFromMetaData(metaData, "ColorDataPerPixelY")) * 10E-7; umPerPixelOrX = parseInt(getDSXExifTagFromMetaData(metaData, "ImageDataPerPixelX")) * 10E-7; umPerPixelOrY = parseInt(getDSXExifTagFromMetaData(metaData, "ImageDataPerPixelY")) * 10E-7; if (umPerPixelX != umPerPixelOrX) IJ.log("Note that for " + path + " the stored output X scale \(" + umPerPixelX + " microns per pixel\) was different from the original acquisition scale \(" + umPerPixelOrX + " microns per pixel\). The output scale is used."); pixelAR = d2s(umPerPixelX / umPerPixelY, 5); /* Enforce sinsible pixel AR */ if (pixelAR == "1.00000") pixelAR = 1; newUnits = sensibleUnits(umPerPixelX, "um"); applyAndExit = getBoolean("Pixel size from Olympus DSX image header: " + newUnits[0] + " " + newUnits[1] + ", Aspect ratio: " + pixelAR, "Apply scale and exit", "Apply and continue"); run("Set Scale...", "distance=1 known=" + newUnits[0] + " pixel=" + pixelAR + " unit=" + newUnits[1]); scaleAppliedInfo = cleanLabel("File: " + fileName + "\nPixel size set from Olympus DSX image header: " + newUnits[0] + " " + newUnits[1] + ", Aspect ratio: " + pixelAR); IJ.log(cleanLabel(scaleAppliedInfo)); showStatus("ASC set scale: " + scaleAppliedInfo, "flash image green"); bioformats = false; if (applyAndExit) exit(); /* DSX scale will only be as saved from Olympus app, so no modifications will be necessary */ } else if (vsiFile && microscope == "DSX1000" && indexOf(metaData, "Physical pixel size = ") >= 0 && indexOf(metaData, "Calibration units") >= 0){ vsiHeaders = split(metaData, "\n"); // Table.showArrays(microscope, vsiHeaders); calibrationLineN = indexOfArrayThatContains(vsiHeaders, "Physical pixel size", - 1); unitsLineN = indexOfArrayThatContains(vsiHeaders, "Calibration units", - 1); if (calibrationLineN >= 0 && unitsLineN >= 0){ calibrationString = substring(vsiHeaders[calibrationLineN], 23, lengthOf(vsiHeaders[calibrationLineN]) - 1); unitsString = substring(vsiHeaders[unitsLineN], 23); xyPixelSize = split(calibrationString, ", "); if (xyPixelSize.length == 2){ pixelDistX = parseFloat(xyPixelSize[0]); pixelDistY = parseFloat(xyPixelSize[1]); pixelAR = d2s(pixelWidth / pixelHeight, 5); /* Enforce sinsible pixel AR */ if (pixelAR == "1.00000") pixelAR = 1; newUnits = sensibleUnits(pixelDistX, unitsString); run("Set Scale...", "distance=1 known=" + newUnits[0] + " pixel=" + pixelAR + " unit=" + newUnits[1]); scaleAppliedInfo = cleanLabel("File: " + fileName + "\nPixel size set from Olympus VSI image header: " + newUnits[0] + " " + newUnits[1]); if (pixelAR != "1") scaleAppliedInfo += ", Aspect ratio: " + pixelAR; IJ.log(scaleAppliedInfo); bioformats = false; showStatus("ASC set scale: " + scaleAppliedInfo, "flash image green"); keepGoing = getBoolean(scaleAppliedInfo, "More options", "Finish"); if (!keepGoing) exit(); } } } else { onZ = startsWith(toLowerCase(path), "z"); sizeWarning = 6; /* warns if file over 6 MB */ if (mBytes > sizeWarning || onZ) { messageTxt = "Be patient! Entire image file may be searched for metadata, and the file is "; if (mBytes > sizeWarning) messageTxt += "" + mBytes + " MB"; if (mBytes > sizeWarning && onZ) messageTxt += ", and is "; if (onZ) messageTxt += "stored in the slow network drive Z"; showStatus(messageTxt); } useHeader = false; /* 1st check for previously embedded data */ infoLines = split(metaData, "\n"); iSEMName = indexOfArrayThatStartsWith(infoLines, "Sem = ", -1); smartSEMInfos = newArray(""); if (iSEMName >= 0) microscope = substring(infoLines[iSEMName], 6); if (microscope == "Not found") { smartSEMInfos = newArray("SmartSEM header not found"); smartSEM = false; } else smartSEMInfos = newArray(""); feiSEMName = "Not found"; if (tifFile || tiffFile) { if (microscope == "Not found" && !dsxFile) fullFileString = File.openAsString(fullPath); if (tifFile) { if (microscope == "Not found") smartSEMInfos = extractTIFFHeaderInfoToArray(fullFileString, "DP_", "SAMPLE_ID", 200, "\n", " ", "End of Header", newArray("SmartSEM header not found")); else smartSEMInfos = infoLines; if (smartSEMInfos[0] != "SmartSEM header not found") { iSEMName = indexOfArrayThatStartsWith(smartSEMInfos, "Sem = ", -1); if (iSEMName >= 0){ zeissSEMNameLine = smartSEMInfos[iSEMName]; zeissSEMName = String.trim(substring(zeissSEMNameLine, 6)); zeissSEMNames = newArray("Ultra 40 XB", "1540XB", "Zeiss EVO 10"); zeissASCNames = newArray("Zeiss 1540EsB", "Zeiss 1540XB", "Zeiss EVO 10"); iMicroscope = indexOfArrayThatContains(zeissSEMNames, zeissSEMName, -1); if (iMicroscope >= 0) { zeissTIF = true; microscope = zeissASCNames[iMicroscope]; iCZScaleValue = indexOfArrayThatStartsWith(smartSEMInfos, "Image Pixel Size = ", -1); CZScaleValue = smartSEMInfos[iCZScaleValue]; CZScaleString = substring(CZScaleValue, 19); CZScale = split(CZScaleString, " "); distPerPixel = parseFloat(CZScale[0]); CZUnit = CZScale[1]; if (startsWith(CZUnit, "micro") || endsWith(CZUnit, "ons") || CZUnit == "um" || CZUnit == "µm") CZUnit = micronS; iUnit = indexOfArray(kUnits, CZUnit, -1); iStoreRes = indexOfArrayThatStartsWith(smartSEMInfos, "Store resolution = ", -1); CZFrameString = smartSEMInfos[iStoreRes]; CZFrame = split(substring(CZFrameString, 19), " * "); if (CZUnit == "m" && distPerPixel > 0.01) { /* full unit was missing on one of the test image so here is a backup plan */ iFrameWidth = indexOfArrayThatStartsWith(smartSEMInfos, "Width = ", -1); CZFrameWidthValue = smartSEMInfos[iFrameWidth]; CZFrameString = substring(CZFrameWidthValue, 8); CZScale = split(CZFrameString, " "); CZUnit = CZScale[1]; if (startsWith(CZUnit, "micro") || endsWith(CZUnit, "ons") || CZUnit == "um" || CZUnit == "µm") CZUnit = micronS; distPerPixel = parseFloat(CZScale[0]) / parseFloat(CZFrame[0]); iUnit = indexOfArray(kUnits, CZUnit, -1); } scaleArray = sensibleUnits(distPerPixel, CZUnit); distPerPixel = scaleArray[0]; CZUnit = scaleArray[1]; CZScale = Array.concat(distPerPixel, CZFrame); } } } if (!zeissTIF && !smartSEM) { if (feiSEMName == "Not found") feiPhenomInfos = extractTIFFHeaderInfoToArray(fullFileString, "[User]", "[HiResIllumination]", 300, "\n", " ", "End of Header", newArray("FEI Phenom header not found")); if (feiPhenomInfos[0] != "FEI Phenom header not found") { // Table.showArrays(feiSEMName, feiPhenomInfos); iSEMName = indexOfArrayThatStartsWith(feiPhenomInfos, "SystemType=", -1); feiSEMNameLine = feiPhenomInfos[iSEMName]; feiSEMName = substring(feiSEMNameLine, 11); ifeiScaleValue = indexOfArrayThatStartsWith(feiPhenomInfos, "PixelWidth=", -1); if (ifeiScaleValue >= 0) feiScaleValue = feiPhenomInfos[ifeiScaleValue]; else exit("Could not find 'PixelWidth=' line in FEI header even though " + feiShortName + " was found"); distPerPixel = parseFloat(substring(feiScaleValue, 11)); iStoreResX = indexOfArrayThatStartsWith(feiPhenomInfos, "ResolutionX=", -1); feiFrameWidth = parseInt(feiPhenomInfos[iStoreResX]); iStoreResY = indexOfArrayThatStartsWith(feiPhenomInfos, "ResolutionY=", -1); feiFrameHeight = parseInt(feiPhenomInfos[iStoreResY]); scaleArray = sensibleUnits(distPerPixel, "m"); distPerPixel = scaleArray[0]; feiUnit = scaleArray[1]; feiScale = newArray(distPerPixel, feiFrameWidth, feiFrameHeight); if (feiSEMName != "Not found") { feiTIF = true; microscope = feiSEMName; feiInfo = "FEI : " + feiSEMName + " SEM"; } else feiTIF = false; } } } if (microscope != "Not found" && indexOfArray(microscopes, microscope, -1) < 0) microscopes = Array.concat(microscopes, microscope); else if (tiffFile && feiTIF) { feiSEMInfos = extractTIFFHeaderInfoToArray(fullFileString, "", 200, "\n", " ", "End of Header", newArray("FEI SEM header not found")); for (i = 0; i < feiSEMInfos.length; i++) while (startsWith(feiSEMInfos[i], " ")) feiSEMInfos[i] = substring(feiSEMInfos[i], 1); iInstrument = indexOfArray(feiSEMInfos, "", -1); semTypeLine = feiSEMInfos[iInstrument + 1]; i00 = indexOf(semTypeLine, ">"); i01 = lastIndexOf(semTypeLine, "<"); if (i00 >= 0 && i01 > i00) semType = substring(semTypeLine, i00 + 1, i01); else semType = "FEI SEM Type not found"; semEditionLine = feiSEMInfos[iInstrument + 4]; i00 = indexOf(semEditionLine, ">"); i01 = lastIndexOf(semEditionLine, "<"); if (i00 >= 0 && i01 > i00) semEdition = substring(semEditionLine, i00 + 1, i01); else semEdition = "FEI SEM Edition not found"; feiSEMName = semType + " " + semEdition; feiInfo = "FEI " + semType + ": " + feiSEMName + " SEM"; ifeiScaleValue = indexOfArrayThatContains(feiSEMInfos, "pixelWidth", -1); semCalibLine = feiSEMInfos[ifeiScaleValue]; i00 = indexOf(semCalibLine, "unit="); i01 = indexOf(semCalibLine, ">"); i02 = indexOf(semCalibLine, ""); if (i00 >= 0 && i01 >= 0 && i02 >= 0) { feiUnit = substring(semCalibLine, i00 + 6, i00 + 8); distPerPixel = parseFloat(substring(semCalibLine, i01 + 1, i02)); } scaleArray = sensibleUnits(distPerPixel, feiUnit); distPerPixel = scaleArray[0]; feiUnit = scaleArray[1]; feiScale = newArray(distPerPixel, imageWidth, imageHeight); feiTIF = true; microscope = feiSEMName; if (indexOfArray(microscopes, microscope, -1) < 0) microscopes = Array.concat(microscopes, microscope); } else if (openMicroscopy) { microscope = "Open Microscopy"; instrumentLineN = indexOfArrayThatContains(infoLines, "= 0){ microscope = "DSX1000-PRECiV"; objectiveLineN = indexOfArrayThatContains(infoLines, "") -1); dsxSettings = true; } } calibrationLineN = indexOfArrayThatContains(infoLines, "= 0){ pixelWidth = parseFloat(substring(calibrationLine, indexOf(calibrationLine, "PhysicalSizeX=") + 15, indexOf(calibrationLine, "PhysicalSizeXUnit=") - 2)); pixelHeight = parseFloat(substring(calibrationLine, indexOf(calibrationLine, "PhysicalSizeY=") + 15, indexOf(calibrationLine, "PhysicalSizeYUnit=") - 2)); unit = substring(calibrationLine, indexOf(calibrationLine, "PhysicalSizeXUnit=") + 19, indexOf(calibrationLine, "PhysicalSizeY=") - 2); pixelAR = d2s(pixelWidth / pixelHeight, 5); /* Enforce sinsible pixel AR */ if (pixelAR == "1.00000") pixelAR = 1; newUnits = sensibleUnits(pixelWidth, unit); run("Set Scale...", "distance=1 known=" + newUnits[0] + " pixel=" + pixelAR + " unit=" + newUnits[1]); scaleAppliedInfo = macroL + "\n" + cleanLabel("File: " + fileName + "\nPixel size set for DSX1000-PRECiV: " + newUnits[0] + " " + newUnits[1] + ", pixel AR: " + pixelAR); if (dsxSettings) scaleAppliedInfo += "\nObjective: Nominal Mag. = " + xChar + nomMag + "\nCalibrated Mag. = " + xChar + calMag + "\nNA = " + lensNA + "\nImaging mode: " + mode; IJ.log(scaleAppliedInfo); bioformats = false; showStatus("ASC set scale completed", "flash image green"); keepGoing = getBoolean(scaleAppliedInfo, "More options", "Finish"); if (!keepGoing) exit(); } } } } else IJ.log("Unable to read instrument line from OME header"); if (indexOfArray(microscopes, microscope, -1) < 0) microscopes = Array.concat(microscopes, microscope); dimensionsLineN = indexOfArrayThatContains(infoLines, "= 0) unit = "micron"; } else IJ.log("Unable to read dimensions line from OME header"); } } } if (foundBF) microscopes = Array.concat(microscopes, "Bioformats"); /* Bioformats does not work correctly with dsx and older vsi files */ microscopes = Array.concat(microscopes, "DPI", "Frame_width", "Manual_input", "Clear_scale"); for (i = 0; i < microscopes.length; i++) if (microscopes[i] == "Not found" || microscopes[i] == "") microscopes = Array.deleteIndex(microscopes, i); /* FSU Microscope Information */ /* Microscopes with manual calibrations available */ EsBinfo = "Zeiss 1540EsB: Field Emission Scanning Electron Microscope \(Polaroid Mag\)"; XBinfo = "Zeiss 1540XB: Field Emission Scanning Electron Microscope \(Polaroid Mag\)"; BX41Minfo = "BX41M-LED: Olympus BX41M-LED Light Microscope"; // BX41MinfoDetails = "TV: " + xChar + "0.63 2588" + xChar + "1960\n 1294" + xChar + "980 \(Binning 2x2\), 640" + xChar + "480 \(Binning 4" + xChar + "4\), 424" + xChar + "318 \(Binning 6" + xChar + "6\)"; LEXTinfo = "LEXT: Olympus OLS 3100 Scanning Confocal Laser Microscope"; /* Other microscopes */ DSXinfo = "DSX1000 \(old\): Olympus DSX1000 Digital Light Microscope \(original file required\)"; PRECiVDSXinfo = "DSX1000-PRECiV: Olympus DSX1000 Digital Light Microscope \(PRECiV\)"; VSIinfo = "Olympus VSI file:: Olympus vsi file, could be DSX or BX41M"; EVO10info = "Zeiss EVO 10: LaB6 Scanning Electron Microscope \(original file required\)"; Heliosinfo = "FEI Helios G4 UC: Dual Beam FESEM/FIB"; PhenomXLG2info = "Phenom XLG2: Phenom XL G2 Benchtop SEM"; OMEinfo = "Open Microscopy: OME Metadata \(use for DSX1000-PRECiV TIF\)"; Bioformatsinfo = "Bioformats: Bio-Formats extensions metadata \(should read PRECiV vsi\)"; if (indexOf(microscope, "DSX") >= 0 && bioformats) Bioformatsinfo += " \(can use for DSX1000-PRECiV\)"; FrameWidthinfo = "Frame width: If you know the calibrated width of your image"; dpiinfo = "DPI: Use for flatbed scanners \(typical presets provided\)"; /* preset values from excel file */ dialog1Message = "Macro: " + macroL; if (scaleAppliedInfo != "") dialog1Message += "\n \n***** " + scaleAppliedInfo + "*****\n"; /* If an ASC calibration is available load it an report the file information */ pathCalibFile = ""; if (microscope == "Not found" || indexOfArray(ascCalibMicroscopes, microscope, -1) >= 0){ calFileName = "Measurement_Scales_ASC.csv"; pathCalibFile = getScaleCalibrationFilePath(calFileName); if (pathCalibFile != ""){ fileDate = File.dateLastModified(pathCalibFile); filestring = File.openAsString(pathCalibFile); dirCalibFile = substring(pathCalibFile, 0, indexOf(pathCalibFile, calFileName)); calibrations = split(filestring, "\n"); calGuess = 0; magString = "x" + extractMag(fileName); entries = calibrations.length; labels = newArray(entries); pixels = newArray(entries); distances = newArray(entries); units = newArray(entries); orFrameWidth = newArray(entries); dialog1Message += "\nImage:" + getTitle() + "\nCalibration file \(" + calFileName + "\) from directory:\n" + dirCalibFile + "\nLast modified: " + fileDate; } } infoText = "\n-----------------------------------------------------------\nASC microscopes and calibration methods:\n-----------------------------------------------------------"; if (microscope != "Not found") { infoText += "\nIdentified microscope: " + microscope; if (zeissTIF) { if (microscope == "Zeiss EVO 10") infoText += "\n" + EVO10info; if (CZScale[0] >= 0) { infoText += "\n SmartSEM Zeiss Scale: Pixel size = " + CZScale[0] + " " + CZUnit + ", frame width = " + CZScale[1] + " pixels\n "; if (microscope == "Zeiss EVO 10") infoText += "\n This scale is applied if \"Zeiss EVO 10\" is selected\n "; } else infoText += "\nSmartSEM scale not found"; } else if (feiTIF) infoText += "\n" + feiInfo; else if (microscope == "DSX1000\(old\)") infoText += "\n" + DSXinfo; else if (microscope == "DSX1000-PRECiV"){ infoText += "\n" + PRECiVDSXinfo; if (dsxSettings) infoText += "\n Objective: Nominal Mag. = " + xChar + nomMag + ", Calibrated Mag. = " + xChar + calMag + ", NA = " + lensNA + "\n Imaging mode: " + mode; } else if (vsiFile) infoText += "\n" + VSIinfo; else if (dsxFile) infoText += "\n" + DSXinfo; } if (pathCalibFile != "" && (microscope == "Not found" || indexOfArray(ascCalibMicroscopes, microscope, -1) < 0)){ if (vsiFile) infoText += "\nMicroscope with vsi files and calibration files:\n" + "\n" + BX41Minfo; else infoText += "\nMicroscopes with calibration files:\n" + EsBinfo + "\n" + XBinfo + "\n" + LEXTinfo + "\n" + BX41Minfo; infoText += "\n-----------------------------------------------------------\nOther calibration methods:"; } else if (microscope != "Not found") infoText += "\n-----------------------------------------------------------\nAlternative calibration methods:"; else infoText += "\nAlternative calibration methods:"; if (openMicroscopy && microscope == "Not found") infoText += "\n" + OMEinfo; if (bioformats && foundBF) infoText += "\n" + Bioformatsinfo; infoText += "\n" + dpiinfo + "\n" + FrameWidthinfo + "\n-----------------------------------------------------------"; dialog1Message += infoText + "\n"; if (vsiFile && !startsWith(microscope, "DSX1000") && indexOfArrayThatContains(microscopes, "BX41M", -1) < 0) microscopes = Array.concat(microscopes, "BX41M"); orMicroscope = microscope; /* *****************************Start of Main Dialog *******************************************/ Dialog.create("Choose microscope or calibration method"); Dialog.addMessage(dialog1Message, infoFontSize, infoColor); if (tableScale) { Dialog.addMessage("Embedded scale from table:\n" + tableTitle + "\nPixel calibration: " + tableLCF + " " + tableUnit, infoFontSize, infoColor); microscopes = Array.concat("Scale_from_table", microscopes); } /* default to last used */ if (tableScale) iScope = indexOfArray(microscopes, "Scale_from_table", 0); else if (microscope == "BX41M") iScope = indexOfArray(microscopes, "BX41M", 0); else if (microscope == "LEXT") iScope = indexOfArray(microscopes, "LEXT", 0); else if (zeissTIF || smartSEM) iScope = indexOfArray(microscopes, microscope, 0); else if (feiTIF) iScope = indexOfArray(microscopes, feiSEMName, 0); else if (endsWith(fileName, ".dsx")) iScope = indexOfArray(microscopes, "DSX1000\(old\)", 0); else if (openMicroscopy) { if (microscope == "DSX1000-PRECiV") iScope = indexOfArray(microscopes, "DSX1000-PRECiV", 0); else iScope = indexOfArray(microscopes, "Open Microscopy", 0); } else if (indexOf(fileName, "DPI") >= 0) iScope = indexOfArray(microscopes, "DPI", 0); else if (foundBF && (indexOf(fileName, "Bioformats") >= 0 || vsiFile)) iScope = indexOfArray(microscopes, "Bioformats", 0); else iScope = indexOfArray(microscopes, call("ij.Prefs.get", prefsNameKey + "lastScope", microscopes[1]), 1); Dialog.addRadioButtonGroup("Choose microscope or method:", microscopes, round(microscopes.length / 2.5), 3, microscopes[iScope]); Dialog.addCheckbox("Run in diagnostics mode", diagnostics); Dialog.show(); microscope = Dialog.getRadioButton(); diagnostics = Dialog.getCheckbox(); /* *****************************End of main Dialog *******************************************/ call("ij.Prefs.set", prefsNameKey + "lastScope", microscope); if (diagnostics) { IJ.log("Selected microscope: " + microscope + " from microscope list:"); Array.print(microscopes); } /* End of microscope selection dialog */ if (microscope == "Scale_from_table") run("Set Scale...", "distance=1 known=" + tablePW + " pixel=" + tablePAR + " unit=" + tableUnit); else if (microscope == "openMicroscopy" || microscope == "DSX1000-PRECiV") run("Set Scale...", "distance=1 known=" + distPerPixel + " pixel=1 unit=" + unit); /*****************************Microscopes with calibrations in table*************************/ else if (indexOfArray(ascCalibMicroscopes, microscope, -1) >= 0) { if (microscope == "Zeiss 1540EsB" || microscope == "Zeiss 1540XB" || microscope == "BX41M") frameFactor = true; for (i = 0, counter = 0; i < entries; i++) { columns = split(calibrations[i], "\,"); /* csv file */ microscopeShort = replace(microscope, "Zeiss 1540EsB", "EsB"); microscopeShort = replace(microscopeShort, "Zeiss 1540XB", "XB"); if (indexOf(columns[0], microscopeShort) >= 0) { labels[counter] = columns[0]; pixels[counter] = parseFloat(columns[1]); distances[counter] = parseFloat(columns[2]); units[counter] = replace(columns[3], "um", micronS); orFrameWidth[counter] = columns[4]; counter++; } } labels = Array.trim(labels, counter); pixels = Array.trim(pixels, counter); distances = Array.trim(distances, counter); units = Array.trim(units, counter); if (diagnostics) Table.showArrays("Extracted calibrations from csv file", labels, pixels, distances, units); labelsFull = newArray(counter); for (i = 0; i < counter; i++) labelsFull[i] = labels[i] + ": " + pixels[i] + " pixels = " + distances[i] + " " + units[i]; radioColumns = maxOf(1, round(counter / 20)); radioRows = maxOf(1, round(counter / radioColumns)); if (microscope == "BX41M") { microscopeInfo = replace(BX41Minfo, " ", " "); orFrameChoices = newArray("424", "640", "1294", "2588"); defaultVoxelMicronDepth = newArray(59.08, 16.81, 4.20, 1.87, 0.59, 0.47); if (imageWidth < 500) frameGuess = 0; if (imageWidth < 768) frameGuess = 1; else if (imageWidth < 1400) frameGuess = 2; else frameGuess = 3; } else if (orMicroscope == "LEXT") microscopeInfo = LEXTinfo; else if (orMicroscope == "Zeiss 1540EsB" || orMicroscope == "Zeiss 1540XB") { if (orMicroscope == "Zeiss 1540EsB") microscopeInfo = replace(EsBinfo, " ", " "); else if (orMicroscope == "Zeiss 1540XB") microscopeInfo = replace(XBinfo, " ", " "); else microscopeInfo = "Whoops, something is wrong with the microscope ID"; orFrameChoices = newArray("512", "1024", "2048", "3072"); if (imageWidth < 513) frameGuess = 0; else if (imageWidth < 1025) frameGuess = 1; else if (imageWidth < 2049) frameGuess = 2; else frameGuess = 3; if (CZScale.length > 1) { if (CZScale[1] > 0) { frameHeader = indexOfArray(orFrameChoices, CZScale[1], -1); /* Override with header information if available */ if (frameHeader >= 0) frameGuess = frameHeader; } } } } else if (orMicroscope == "Zeiss EVO 10" || microscope == "Zeiss EVO 10") microscopeInfo = replace(EVO10info, " ", " "); else if (orMicroscope == "DSX1000-PRECiV" || microscope == "DSX1000-PRECiV") microscopeInfo = replace(PRECiVDSXinfo, " ", " "); else if (orMicroscope == "DPI" || microscope == "DPI") microscopeInfo = replace(dpiinfo, " ", " "); else if (orMicroscope == "DSX1000\(old\)" || microscope == "DSX1000\(old\)") microscopeInfo = replace(DSXinfo, " ", " "); else if (orMicroscope != "") microscopeInfo = orMicroscope; if (microscope == "DSX1000-PRECiV") microscope = "Bioformats"; if (indexOf("Bioformats Frame_width Clear_scale Selection_width Selection_height Selection_width/height Line_length", microscope) < 0){ /*****************************************Start Named Microsopes Calibration Dialog******************************************/ Dialog.create("Choose " + cleanLabel(microscopeInfo) + " Calibration"); Dialog.addMessage("Macro version:" + macroL, infoFontSize - 2, "black"); Dialog.addMessage(cleanLabel(microscopeInfo) + "\nImage width = " + imageWidth + " pixels", infoFontSize, infoColor); Dialog.addMessage("Method: " + microscope, infoFontSize, infoColor); if (microscope == "DSX1000\(old\)") { /* Olympus DSX1000 options */ if (diagnostics) Array.print(dsxInfos); iDSXScale = indexOfArrayThatContains(dsxInfos, "ImageDataPerPixelX", -1); iDSXColorScale = indexOfArrayThatContains(dsxInfos, "ColorDataPerPixelX", -1); orDSXScale = dsxInfos[iDSXScale]; moDSXScale = dsxInfos[iDSXColorScale]; i0 = indexOf(moDSXScale, ""); if (i0 != -1) { i1 = indexOf(moDSXScale, "", i0); tagData = substring(moDSXScale, i0 + 20, i1); } pmPerPixel = parseInt(tagData); i2 = indexOf(orDSXScale, ""); if (i2 != -1) { i3 = indexOf(orDSXScale, "", i2); tagData2 = substring(orDSXScale, i2 + 20, i3); } pmPerPixelOr = parseInt(tagData2); if (pmPerPixelOr != pmPerPixel) scaledOutput = true; else scaledOutput = false; if (pmPerPixel > 0) { if (pmPerPixel < 10E3) defUnit = "nm"; else if (pmPerPixel < 10E6) defUnit = micronS; else defUnit = "mm"; messageTxtDSX = "NEW: DSX images have been checked for a limited number of images\nUSE CAUTION!\n"; if (scaledOutput) messageTxtDSX += " Particularly with montages, which this image appears to be\nthe original image scale was"; else messageTxtDSX += "The image scale is"; messageTxtDSX += " " + pmPerPixelOr + " pm per pixel\n"; if (scaledOutput) messageTxtDSX += "but the scale that will be used is the color output scale which is \n" + pmPerPixel + " pm per pixel,"; messageTxtDSX += " Image width " + imageWidth * pmPerPixel + " pm"; if (scaledOutput) messageTxtDSX += "\n*** Use the same original images option and combine original images in Fiji-Image for full resolution***"; Dialog.addMessage(messageTxtDSX, infoFontSize, infoColor); if (scaledOutput) Dialog.addMessage("The saved color image scale = " + pmPerPixel + " pm per pixel\nImage width = " + imageWidth * pmPerPixel + " pm", infoFontSize, infoColor); if (scaledOutput) Dialog.addCheckbox("Use original image scale instead?", false); Dialog.addRadioButtonGroup(microscope + " base units", unitChoices, 3, 3, defUnit); Dialog.addCheckbox("Output height map calibration to log window if available?", false); } /* End Olympus DSX1000 options */ } else if (feiTIF) { /* FEI SEM options */ Dialog.addMessage("NEW: FEI " + feiSEMName + " images have been checked for a limited number of images\nUSE CAUTION! particularly with montages\nImage scale = " + feiScale[0] + " " + feiUnit + " per pixel\nImage width = " + feiScale[1] * feiScale[0] + " " + feiUnit, infoFontSize, infoColor); Dialog.addRadioButtonGroup(microscope + " base units", unitChoices, 3, 3, feiUnit); } else if (microscope == "Zeiss EVO 10") { /* EVO 10 LaB6 SEM options */ Dialog.addMessage("SmartSEM Zeiss Scale: Pixel size = " + CZScale[0] + " " + CZUnit + ", frame width = " + CZScale[1] + " pixels", infoFontSize, infoColor); Dialog.addRadioButtonGroup(microscope + " base units:", unitChoices, Math.ceil(unitChoices.length / 3), 3, CZUnit); Dialog.addCheckbox("Select " + imageWidth + xChar + d2s(imageWidth * 0.703125, 0) + " image area above standard Zeiss annotation bar", false); Dialog.setInsets(0, 100, 0); Dialog.addCheckbox("Crop to selection above and add 'crp' suffix to image", false); /* Actually +"_crop" but underscore does not show in Dialog */ /************************************Calibrated Microscopes Section Setup***************************************/ } else if (indexOfArray(ascCalibMicroscopes, microscope, -1) >= 0){ if ((microscope == "Zeiss 1540EsB" && CZScale.length > 0) || (microscope == "Zeiss 1540XB" && CZScale.length > 1)) { if (CZScale[0] >= 0) { if (microscope == "Zeiss 1540EsB" && (imageHeight == 2304 || imageHeight == 1536 || imageHeight == 768 || imageHeight == 384)) { cropEsB = call("ij.Prefs.get", prefsNameKey + "cropEsB", true); Dialog.addCheckbox("Crop to remove bad final scan line from EsB image \(image will be renamed\)?", cropEsB); } Dialog.addMessage("SmartSEM Zeiss Scale: Pixel size = " + CZScale[0] + " " + CZUnit + ", frame width = " + CZScale[1] + " pixels", infoFontSize, infoColor); /* Integrate SmartSEM header option into scale arrays */ labels = Array.concat("Use SmartSEM Scale", labels); pixels = Array.concat(1 / CZScale[0], pixels); distances = Array.concat(1, distances); units = Array.concat(CZUnit, units); orFrameWidth = Array.concat(CZScale[1], orFrameWidth); } } if (frameFactor) { Dialog.addRadioButtonGroup("Original Frame_width:", orFrameChoices, 1, lengthOf(orFrameChoices), orFrameChoices[frameGuess]); for (i = 0; i < labels.length; i++) { magI = indexOf(labels[i], magString); if (magI >= 0) { calGuess = i; i = labels.length; } } calGuess = maxOf(calGuess, 0); Dialog.addRadioButtonGroup("Calibration:", labels, radioRows, radioColumns, labels[calGuess]); } else { for (i = 0; i < labelsFull.length; i++) { magI = indexOf(labelsFull[i], magString); if (magI >= 0) { calGuess = i; i = labelsFull.length; } } calGuess = maxOf(calGuess, 0); Dialog.addRadioButtonGroup("Calibration:", labelsFull, radioRows, radioColumns, labelsFull[calGuess]); if (microscope == "DPI") Dialog.addNumber("Overrides with dpi number:", 0, 0, 5, "leave as zero for no"); } } /************************************Calibrated Microscopes Section Setup End ***************************************/ Dialog.show(); /************************ Start Named Microscopes "Get" Section***************************************/ if (microscope == "DSX1000\(old\)") { /* Get Olympus DSX1000 (original DSX software) options */ unit = Dialog.getRadioButton; if (scaledOutput) { if (Dialog.getCheckbox) { pmPerPixel = pmPerPixelOr; IJ.log("Warning: Reverted to original image scale"); } } pmSF = newArray(1E-9, 1E-6, 1E-6, 1E-3, 1E-2, 1); /* unitChoices should be: "mm", micronS, "microns", "nm", "Angstroms", "pm" */ distPerPixel = pmPerPixel * pmSF[indexOfArray(unitChoices, unit, -1)]; run("Set Scale...", "distance=1 known=" + distPerPixel + " pixel=1 unit=" + unit); IJ.log(cleanLabel("File: " + fileName + "\nPixel size set \(" + microscope + "\): " + distPerPixel + " " + unit)); if (Dialog.getCheckbox) { iDSXScaleZ = indexOfArrayThatContains(dsxInfos, "ImageDataPerPixelY", -1); dsxScaleZ = dsxInfos[iDSXScaleZ]; i0 = indexOf(dsxScaleZ, ""); if (i0 != -1) { i1 = indexOf(dsxScaleZ, "", i0); tagData = substring(dsxScaleZ, i0 + 20, i1); } hICal = parseFloat(tagData); IJ.log("Intensity height map calibration = 1 intensity level per " + hICal + " pm"); hRange = 256 * 256 * hICal; /* unitChoices reminder: "mm", micronS, "microns", "nm", "Angstroms", "pm" */ if (hRange > 10E7) hUnit = "mm"; else if (hRange > 10E4) hUnit = micronS; else if (hRange > 10E1) hUnit = "nm"; else hUnit = pm; hRange = hRange * pmSF[(indexOfArray(unitChoices, hUnit, -1))]; IJ.log(cleanLabel("For 16-bit height map the full height range is = " + hRange + " " + hUnit)); } } /* End Olympus DSX1000 (original DSX software) options */ else if (feiTIF) { /* FEI FESEM options */ /* pixelWidth and unit extracted above before dialog */ nmSFs = newArray(1E-6, 1E-3, 1E-3, 1, 10, 1E3); /* unitChoices should be: "mm", micronS, "microns", "nm", "Angstroms", "pm" */ iUnit = indexOfArray(unitChoices, feiUnit, -1); if (iUnit >= 0) nmPerPixel = feiScale[0] / nmSFs[iUnit]; else exit("FEI units \(" + unit + "\) not as expected"); unitChoice = Dialog.getRadioButton(); iUnitChoice = indexOfArray(unitChoices, unitChoice, -1); if (iUnitChoice >= 0) distPerPixel = nmPerPixel * nmSFs[iUnitChoice]; else exit("FEI unit choice \(" + unitChoice + "\) not as expected"); run("Set Scale...", "distance=1 known=" + distPerPixel + " pixel=1 unit=" + unitChoice); IJ.log(cleanLabel("File: " + fileName + "\nPixel size set for FEI microscope: " + distPerPixel + " " + unitChoice)); } /* End FEI FESEM options */ else if (microscope == "Zeiss EVO 10") { /* Get Zeiss EVO 10 LaB6 SEM options */ /* pixelWidth and unit extracted above before dialog */ unitChoice = Dialog.getRadioButton(); nonAnnoSel = Dialog.getCheckbox(); annoCrop = Dialog.getCheckbox(); /* End EVO 10 Dialog */ nmSFs = newArray(1E-6, 1E-3, 1E-3, 1, 10, 1E3); /* unitChoices should be: "mm", micronS, "microns", "nm", "Angstroms", "pm" */ iUnit = indexOfArray(unitChoices, CZUnit, -1); if (iUnit >= 0 && CZScale.length > 0) nmPerPixel = CZScale[0] / nmSFs[iUnit]; else exit("CZ units \(" + CZUnit + "\) not as expected"); iUnitChoice = indexOfArray(unitChoices, unitChoice, -1); if (iUnitChoice >= 0) distPerPixel = nmPerPixel * nmSFs[iUnitChoice]; else exit("CZ unit choice \(" + unitChoice + "\) not as expected"); if (nonAnnoSel) { makeRectangle(0, 0, imageWidth, imageWidth * 0.703125); /* Selects everything except the annotaton bar */ if (annoCrop) { run("Crop"); run("Select None"); if (endsWith(fileName, ".tif")) fileName = replace(fileName, ".tif", "_crp.tif"); rename(fileName); } } run("Set Scale...", "distance=1 known=" + distPerPixel + " pixel=1 unit=" + unitChoice); IJ.log(cleanLabel("File: " + fileName + "\nPixel size set for " + microscope + ": " + distPerPixel + " " + unitChoice)); /* End Zeiss EVO 10 options */ } /************************************Calibrated Microscopes "Get" Section***************************************/ else if (indexOfArray(ascCalibMicroscopes, microscope, -1) >= 0){ /* Older Zeiss SmartSEM scan correction option */ if (CZScale.length > 0 && microscope == "Zeiss 1540EsB" && (imageHeight == 2304 || imageHeight == 1536 || imageHeight == 768 || imageHeight == 384)) { if (Dialog.getCheckbox) { imageHeight -= 1; makeRectangle(0, 0, imageWidth, imageHeight); run("Crop"); run("Select None"); if (endsWith(fileName, ".tif")) fileName = replace(fileName, ".tif", "_crp.tif"); rename(fileName); IJ.log("Bad final scan line of EsB image removed. Image renamed: " + fileName); call("ij.Prefs.set", prefsNameKey + "cropEsB", true); } else call("ij.Prefs.set", prefsNameKey + "cropEsB", false); } /* End of older Zeiss SmartSEM scan correction option */ if (frameFactor) frameSize = Dialog.getRadioButton(); chosenCalibration = Dialog.getRadioButton(); if (frameFactor) entry = indexOfArray(labels, chosenCalibration, -1); else entry = indexOfArray(labelsFull, chosenCalibration, -1); if (entry < 0) exit("No calibration selected"); if (frameFactor) pixelDistance = pixels[entry] * frameSize / orFrameWidth[entry]; else pixelDistance = pixels[entry]; distPerPixel = distances[entry] / pixelDistance; run("Set Scale...", "distance=1 known=" + distPerPixel + " pixel=1 unit=" + units[entry]); IJ.log(cleanLabel("File: " + fileName + "\nPixel size set for " + microscope + ": " + distPerPixel + " " + units[entry])); /************************************ End Calibrated Microscopes "Get" Section***************************************/ } /* End of Named Microscope dialog */ } else if (microscope == "Bioformats"){ if (File.exists(fullPath)){ BFs = getScaleFromBioformats(fullPath); /* the Bio-formats extensions should return the physical pixel sizes in microns */ unlikely = false; if (BFs[0] > (25400 / imageWidth)) unlikely = true; /* Seems unlikely that the image width is more than 1" */ sensibleScale = sensibleUnits(BFs[0], micronS); BFs[1] = BFs[1] * sensibleScale[0] / BFs[0]; BFs[2] = BFs[2] * sensibleScale[0] / BFs[0]; BFs[0] = sensibleScale[0]; unit = sensibleScale[1]; if (BFs[0] > 0 && BFs[1] > 0){ setVoxelSize(BFs[0], BFs[1], BFs[2], unit); if (BFs[0] == BFs[1]) IJ.log("Pixel size set from Bioformats metadata: " + BFs[0] + " " + unit + ", Z = " + BFs[2] + " " + unit); else IJ.log("Pixel size set from Bioformats metadata: X = " + BFs[0] + " " + unit + ", Y = " + BFs[1] + " " + unit + ", Z = " + BFs[2] + " " + unit); if (unlikely) IJ.log("Warning: Image width has been set to wider than 1 inch!"; showStatus("Image dimensional scale set using Bioformats extensions", "flash green"); exit(); } } } else if (microscope == "Frame_width") { Dialog.create("Choose Full Frame_width \(" + macroL + "\)"); getDimensions(frameWidthPixels, null, null, null, null); frameWidthInUnits = frameWidthPixels; toScaled(frameWidthInUnits); Dialog.addNumber("Width of image \(" + frameWidthPixels + " pixls\) in selected unit", frameWidthInUnits, 8, 20, unit); iU = indexOfArray(unitChoices, unit, 2); Dialog.addChoice("Choose Unit", unitChoices, unitChoices[iU]); Dialog.show(); frameWidthInUnits = Dialog.getNumber(); unit = Dialog.getChoice(); if (unit == "um") unit = micronS; run("Set Scale...", "distance=" + frameWidthPixels + " known=" + frameWidthInUnits + " pixel=1 unit=" + unit); IJ.log(cleanLabel("File: " + fileName + "\nPixel size set from frame width: " + frameWidthPixels + " " + unit)); } else if (microscope == "Clear_scale") run("Set Scale...", "distance=0 known=0 pixel=1 unit=pixel"); /******************* Extra slice thickness option for multi-slice BX41 stack ***************************/ if ((microscope == "BX41M") && (slices > 1)) { defaultVoxelMicronDepth = newArray(59.08, 16.81, 4.20, 1.87, 0.59, 0.47); getPixelSize(unit, pixelWidth, pixelHeight); if (voxelDepth == 1) voxelDepth = defaultVoxelMicronDepth[entry]; Dialog.create("Set Voxel Depth for BX41M EDF Stack \(" + macroL + "\)"); Dialog.addMessage("Current voxel depth = " + voxelDepth + " " + unit + "\nDefault voxel depth for this objective is " + defaultVoxelMicronDepth[entry] + " " + micronS, infoFontSize, infoColor); Dialog.addNumber("Set voxel depth:", voxelDepth, 6, 7, micronS); Dialog.show(); run("Properties...", "unit=" + micronS + " voxel_depth=" + Dialog.getNumber); } /**************** End of Extra slice thickness option for multi-slice BX41 stack ***********************/ /*****************Option to add scale to active table ***************************/ nTable = Table.size; if (tableScale == false && nTable > 0) { if (getBoolean("Would you like to embed the scale in the active table?")) { getPixelSize(unit, pixelWidth, pixelHeight); pixelAspect = (pixelWidth / pixelHeight); Table.setColumn("PixelWidth", Array.fill(newArray(nTable), pixelWidth)); Table.setColumn("PixelAR", Array.fill(newArray(nTable), pixelAspect)); Table.setColumn("Unit", Array.fill(newArray(nTable), unit)); } } /***************** Active table option ***************************/ selectImage(orImageID); showStatus("ASC set scale completed", "flash image green"); /* End of ASC-Set_Scale_from_Calibrations macro */ } macro "Erode & Prune to Fully(4)-Connected Skeleton" { /* Uses Gabriel Landini's skeletonization algorithm, different from the one in ImageJ similar to skeleton1 but 4-connected 3 Dec 2003 uses BinaryThin2_.class available from http://www.mecourse.com/landinig/software/software.html assumes a white object on a black background v171108..... v220818: Auto-thresholds, no longer uses restoreExit. v240731: Pruning is now optional. "Trapped squares" now include 8-pixel trap (one corner missing). Better organization of dialogs. Restores selections and LUT condition. v250625: Fixed "tSkel" to fName. v250626: Additional options. v250728 Fiixed missing "+" in options. */ macroL = "4(fully)-Cnctd_Skeleton_v250728.ijm"; if (!checkForPlugin("morphology_collection")) exit("Exiting: Gabriel Landini's morphology suite is needed to run this function."); fullFName = getInfo("image.filename"); if (fullFName == "") { fName = stripKnownExtensionFromString(getTitle()); extension = ""; } else { path = getInfo("image.directory"); fName = File.getNameWithoutExtension(path + fullFName); extension = substring(fullFName, lengthOf(fName)); } fName += "_skel"; orID = getImageID(); selType = selectionType; if (selType >= 0) run("Select None"); run("Duplicate...", "title=&fName slice"); run("8-bit"); if (!is("binary")) { /* Quick-n-dirty threshold if not previously thresholded */ getThreshold(t1, t2); if (t1 == -1) { run("Auto Threshold", "method=Default"); setOption("BlackBackground", false); run("Make Binary"); IJ.log("Default auto-threshold applied to " + fName); } } if (is("Inverting LUT")) { hadInvertedLUT = true; run("Invert LUT"); } else hadInvertedLUT = false; getStatistics(null, meanI, null, null, null, null); getDimensions(imageWidth, imageHeight, imageChannels, imageSlices, imageFrames); if (meanI < 128) blackSkel = false; else blackSkel = true; dFixes = 0; sFixes = 0; skelInt = 255; bgInt = 0; dFixes = 0; sFixes = 0; semiPixels = 0; zoomOPC = 100 * getZoom(); /* ASC message theme */ infoColor = "#006db0"; /* Honolulu blue */ instructionColor = "#798541"; /* green_dark_modern (121,133,65) AKA Wasabi */ infoWarningColor = "#ff69b4"; /* pink_modern AKA hot pink */ infoFontSize = 12; getLocationAndSize(xW, yW, widthW, heightW); Dialog.create(macroL + ": Erosion limits"); Dialog.addMessage("File: " + fullFName, infoFontSize * maxOf(0.5, minOf(1, 80 / lengthOf(fullFName))), infoColor); Dialog.addMessage("Directory: " + path, infoFontSize * maxOf(0.5, minOf(1, 80 / lengthOf(path))), infoColor); Dialog.addNumber("Denoise with median filter first:", 0, 1, 4, "radius in pixels"); Dialog.addNumber("Limit number of erosions?", -1, 0, 3, "-1 = until idempotence reached"); if (imageWidth * imageHeight > 4000000) Dialog.addMessage("The following fixes can take a long time on large images:"); if (blackSkel) Dialog.addCheckbox("Black skeleton recognized; is this true?", true); else Dialog.addCheckbox("White skeleton recognized; is this true?", true); Dialog.addCheckbox("Fix broken diagonals for full connection?", true); Dialog.addCheckbox("Fix trapped solid squares by clearing top and bottom pixels?", true); Dialog.addCheckbox("Fill open squares with single pixel diagonal disconnect?", false); Dialog.addCheckbox("Rerun erosions after diagonal and solid square fixes?", true); Dialog.addCheckbox("Prune \(performed after erosions and fixes\)?", true); Dialog.addCheckbox("Output fix report to log window?", false); Dialog.show(); medianRadius = Dialog.getNumber(); erosionN = Dialog.getNumber(); if (blackSkel){ if (!Dialog.getCheckbox()) blackSkel = false; } else if (!Dialog.getCheckbox()) blackSkel = true; dFix = Dialog.getCheckbox(); sFix = Dialog.getCheckbox(); semiPixel = Dialog.getCheckbox(); reErode = Dialog.getCheckbox(); pruneIt = Dialog.getCheckbox(); report = Dialog.getCheckbox(); xMax = imageWidth - 1; yMax = imageHeight - 1; /* Based on skeleton3.txt by G. Landini: a skeletonization algorithm, different from the one in ImageJ similar to skeleton1 but 4-connected 3 Dec 2003 uses BinaryThin2_.class available from http://www.mecourse.com/landinig/software/software.html assumes a white object on a black background */ setBatchMode(true); if (blackSkel) run("Invert"); showStatus("Creating Skeleton"); if (medianRadius > 0) run("Median...", "radius=" + medianRadius); run("BinaryThin2 ", "kernel_a='1 1 1 2 1 2 0 0 0 ' kernel_b='2 1 1 0 1 1 0 0 2 ' rotations='rotate 45' iterations=&erosionN white"); if (dFix) { for (i = 1; i < imageWidth - 1; i++) { /* start 2nd column, end penultimate column */ showStatus("Fixing broken diagonals: " + dFixes + " fixes"); showProgress(i, imageWidth - 1); for (j = bgInt; j < imageHeight - 1; j++) { /* start 1st row, end penultimate row */ if ((getPixel(i, j)) == skelInt) { if ((getPixel(i + 1, j)) == bgInt) { if ((getPixel(i, j + 1)) == bgInt) { if ((getPixel(i + 1, j + 1)) == skelInt) { setPixel(i + 1, j, skelInt); dFixes += 1; } } } /* conditions end iteration at first exception */ /* assume right and left fill-ins will balance out */ if ((getPixel(i - 1, j)) == bgInt) { if ((getPixel(i, j + 1)) == bgInt) { if ((getPixel(i - 1, j + 1)) == skelInt) { setPixel(i - 1, j, skelInt); dFixes += 1; } } } /* conditions end iteration at first exception */ } } } } if (sFix) { /* next open up trapped squares */ for (i = 1; i < imageWidth - 2; i++) { /* start 2nd column, end penultimate but one column */ showStatus("Fixing trapped squares: " + sFixes + " fixes"); showProgress(i, imageWidth - 1); for (j = 1; j < imageHeight - 3; j++) { /* start 2nd row, end penultimate but two row */ coreInt = getPixel(i, j); if (coreInt == bgInt) coreOpen = true; else coreOpen = false; skelN = 0; diagN = 0; for (k = 0; k < 3; k++) { for (l = 0; l < 3; l++) { pixInt = getPixel(i + k, j + l); if (pixInt == skelInt) skelN++; if (k != 1 && l != 1 && pixInt == bgInt) diagN++; } } if (skelN >= 8) { /* trapped square */ if ((getPixel(i + 1, j - 1)) == bgInt) { setPixel(i + 1, j, bgInt); sFixes += 1; } if ((getPixel(i + 1, j + 3)) == bgInt) { setPixel(i + 1, j + 2, bgInt); sFixes += 1; } } else if (coreOpen && semiPixel && skelN == 7 && diagN == 1) { setPixel(i, j, skelInt); semiPixels++; } } } } if ((dFix || sFix) && reErode) run("BinaryThin2 ", "kernel_a='1 1 1 2 1 2 0 0 0 ' kernel_b='2 1 1 0 1 1 0 0 2 ' rotations='rotate 45' iterations=&erosionN white"); if (blackSkel) run("Invert"); if (pruneIt) { setBatchMode("exit & display"); /* Probably not necessary if exiting gracefully but otherwise harmless */ Dialog.create("Save before pruning: Options"); Dialog.addNumber("Limit number of pruning iterations?", -1, 0, 3, "0 = No pruning, -1 = until idempotence reached"); Dialog.addCheckbox("Pin edge of image?", false); Dialog.addCheckbox("Apply to duplicate of image or active selection", false); Dialog.addRadioButtonGroup("Save before pruning?", newArray("Yes", "No", "Exit"), 1, 3, "No"); if (medianRadius > 0) fName += "_M" + medianRadius + "Smth"; fName += extension; Dialog.addString("Autosave directory:", path, lengthOf(path) + 5); Dialog.addString("Autosave filename:", fName, lengthOf(fName) + 5); Dialog.show(); pruneN = Dialog.getNumber(); pinEdge = Dialog.getCheckbox(); if (Dialog.getCheckbox()) { run("Duplicate...", " "); if (pinEdge) run("Canvas Size...", "width=" + (imageWidth + 2) + " height=" + (imageHeight + 2) + " position=Center"); xW += 20; xY += 20; run("Set... ", "zoom=&zoomOPC x=&xW y=&xY"); } else if (pinEdge) run("Canvas Size...", "width=" + (imageWidth + 2) + " height=" + (imageHeight + 2) + " position=Center"); saveOpt = Dialog.getRadioButton(); path = Dialog.getString(); if (!endsWith(path, File.separator)) path += File.separator; autoPath = path + Dialog.getString; if (saveOpt == "Yes") save(autoPath); if (saveOpt == "Exit") exit("Bye bye"); /* Based on PruneAll by G. Landini: a pruning algorithm, different from the one in ImageJ. Prunes all branches of a skeleton leaving only the closed loops 8-connected 4 Dec 2003: uses BinaryThin_.class available from https://blog.bham.ac.uk/intellimic/g-landini-software assumes a white object on a black background */ setBatchMode(true); if (blackSkel) run("Invert"); showStatus("Pruning Skeleton"); run("BinaryThin ", "kernel_a='0 2 2 0 1 0 0 0 0' rotations='rotate 45' iterations=&pruneN white"); if (blackSkel) run("Invert"); } if (hadInvertedLUT) run("Invert LUT"); if (pinEdge) run("Canvas Size...", "width=" + imageWidth + " height=" + imageHeight + " position=Center"); if (selType >= 0) { finalID = getImageID(); selectImage(orID); run("Restore Selection"); selectImage(finalID); } setBatchMode("exit & display"); /* Probably not necessary if exiting gracefully but otherwise harmless */ beep(); wait(300); beep(); wait(300); beep(); if (report) IJ.log(dFixes + " diagonal fixes and " + sFixes + " trapped square fixes applied"); call("java.lang.System.gc"); /* force a garbage collection */ showStatus(macroL + " completed: " + dFixes + " diagonal fixes and " + sFixes + " trapped square fixes applied", "flash green"); } /* !!!! Note the macro title in the startup includes the shortcut [F5] !!!! */ macro "Fancy Scale Bar [F5]" { /* Original code by Wayne Rasband, improved by Frank Sprenger and deposited on the ImageJ mailing server: (http:imagej.588099.n2.nabble.com/Overlay-Scalebar-Plugins-td6380378.html#a6394996). KS added choice of font size, scale bar height, + any position for scale bar and some options that allow to set the image calibration (only for overlay, not in Meta data). Kees Straatman, CBS, University of Leicester, May 2011 Grotesquely modified by Peter J. Lee NHMFL to produce shadow and outline effects. v161008-v230804: Listed in ijm file. v230809: Renames image if not new but expanded. Removes left-right margin tweak for 'under' option. v230810: Minor change to text and default options. F1: updates indexOf functions. v230906: Minor tweaks to text scale bar overun and default colors and styles. v230911: Fix for color prefs index issue. b: Fix for missing new selection coordinates. v230915: Main menu optimized for clarity. v230918: Reordered code. Removed unused plugins. Prefs keys made more consistent. v230919: More prefs keys added. Improved font choices. Fixed color preferences. b: simplified line labels and removed all text rotation as it was not satisfactory. Removed excess decimal places (based on pixel width). v230922: In line-mode the text label and measurements can be combined and extra line end option added with tweaks to text position to minimize overlaps. RegEx also corrected in font function. v230929: Improved line label position section. Line label style saved separately from scaleBar style. v231024: Tweak to offset option in main dialog. v231209: Overlay shadows are now transparent and use the "darkness" preference for the opacity. v240123: Attempted tweaks of side-by-side positioning within manual selection. Changed some names for clarity (because I was getting confused). Changed intensity to visual luminosity for raised/indented decision. F1: updated function unCleanLabel. F2: Updated sensibleScales function. v240313: Added spaces after commas. F1: Update safeSaveAndClose. v240709: Color selection update. F1: Update functions. v250530: Just add default sb thickness in dialog. */ macroL = "Fancy_Scale_Bar_v250530.ijm"; fullMenuHeight = 988; /* pixels for v230920 */ requires("1.52i"); /* Utilizes Overlay.setPosition(0) from IJ >1.52i */ saveSettings(); /* To restore settings at the end */ micron = getInfo("micrometer.abbreviation"); prefsNameKey = "fancy.scale"; selEType = selectionType; if (is("Inverting LUT")) { run("Invert LUT"); /* more effectively removes Inverting LUT */ if (selEType < 0) run("Invert"); /* Assumes you wanted the apparent contrast */ } if (selEType >= 0) { getSelectionBounds(selEX, selEY, selEWidth, selEHeight); if ((selEWidth + selEHeight) < 6) selEType = -1; /* Ignore junk selections that are suspiciously small */ if (selEType == 5) getSelectionCoordinates(selLX, selLY); } getStatistics(areaPix, meanInt, minInt, maxInt, stdInt); activeImage = getTitle(); imageDir = getDir("image"); orImageID = getImageID(); imageDepth = bitDepth(); if (imageDepth != 24) { if (imageDepth < 8 || imageDepth > 16) { if (getBoolean("Image depth of " + imageDepth + " does not work for this macro; change to 8-bit?")) { run("8-bit"); imageDepth = bitDepth(); } else restoreExit("Goodbye"); } else if ((imageDepth == 8) && indexOf(getInfo(), "Display range:") < 0) { run("RGB Color"); imageDepth = bitDepth(); } } medianBGIs = guessBGMedianIntensity(); medianBGI = round((medianBGIs[0] + medianBGIs[1] + medianBGIs[2]) / 3); bgI = maxOf(0, medianBGI); run("Select None"); checkForUnits(); getDimensions(imageWidth, imageHeight, channels, slices, frames); imageAR = imageWidth / imageHeight; remAllOverlays = false; overlayN = Overlay.size; if (overlayN > 0) remAllOverlays = true; if (imageDepth == 16) bgIpc = round(bgI * 100 / 65536); else bgIpc = round(bgI * 100 / 255); if ((stdInt < 5 && (meanInt > 205 || meanInt < 50)) || bgIpc > 95 || bgIpc < 5) fancyStyle = "No fancy formatting"; else fancyStyle = "Standard for busy images"; /* End simple text default options */ startSliceNumber = getSliceNumber(); remSlices = slices - startSliceNumber; if (selEType == 5) sbFontSize = maxOf(10, round((imageHeight + imageWidth) / 90)); /* set minimum default font size as 12 */ else sbFontSize = maxOf(12, round((minOf(imageHeight, imageWidth)) / 30)); /* set minimum default font size as 12 */ getVoxelSize(pixelWidth, pixelHeight, pixelDepth, selectedUnit); pixelAR = pixelWidth / pixelHeight; sensibleScale = sensibleScales(pixelWidth, selectedUnit, sbFontSize * 10); pixelWidth = parseFloat(sensibleScale[0]); pixelHeight = pixelWidth / pixelAR; selectedUnit = sensibleScale[1]; sciPW = d2s(pixelWidth, -2); trueDPMax = 0 - parseInt(substring(sciPW, indexOf(sciPW, "E") + 1)); setVoxelSize(pixelWidth, pixelHeight, pixelDepth, selectedUnit); if (selectedUnit == "um") selectedUnit = micron; sF = getScaleFactor(selectedUnit); micronS = getInfo("micrometer.abbreviation"); lcf = (pixelWidth + pixelHeight) / 2; lcfFactor = 1 / lcf; pixAr = pixelHeight / pixelWidth; dOutS = 5; /* default outline stroke: % of font size */ dShO = 7; /* default outer shadow drop: % of font size */ dIShO = 4; /* default inner shadow drop: % of font size */ notFancy = false; /* set default tweaks */ outlineStroke = dOutS; shadowDrop = dShO; shadowDisp = dShO; shadowBlur = floor(0.75 * dShO); shadowDarkness = 30; textLabel = ""; diagnostics = false; if (sF > 0) { nSF = newArray(1, sF / (1E-2), sF / (1E-3), sF / (1E-6), sF / (1E-6), sF / (1E-9), sF / (1E-10), sF / (1E-12), sF / (2.54E-2), sF / (1E-4)); overrideUnitChoices = newArray(selectedUnit, "cm", "mm", micronS, "microns", "nm", "Å", "pm", "inches", "human hairs"); } if (selEType >= 0) { if (selEType != 5) { sbWidth = pixelWidth * selEWidth; sbDP = autoCalculateDecPlacesFromValueOnly(sbWidth); sbPreciseWidthString = d2s(sbWidth, sbDP + 3); } else { lineXPx = abs(selLX[1] - selLX[0]); /* used for label offsets later */ lineYPx = abs(selLY[1] - selLY[0]); /* used for label offsets later */ lineLengthPx = sqrt(pow(lineXPx, 2) + pow(lineYPx, 2)); /* NOTE: not corrected for pixel aspect ratio */ sbWidth = sqrt(pow(lineXPx * pixelWidth, 2) + pow(lineYPx * pixelHeight, 2)); sbDP = minOf(trueDPMax, autoCalculateDecPlacesFromValueOnly(sbWidth) + 2); /* Add more dp for line labeling */ sbPreciseWidthString = d2s(sbWidth, minOf(trueDPMax, sbDP + 3)); sbWidth = parseFloat(sbPreciseWidthString); lineAngle = Math.toDegrees(atan2((selLY[1] - selLY[0]) * pixelHeight, (selLX[1] - selLX[0]) * pixelWidth)); lineXPx = abs(selLX[1] - selLX[0]); lineYPx = abs(selLY[1] - selLY[0]); lineMidX = (selLX[0] + selLX[1]) / 2; lineMidY = (selLY[0] + selLY[1]) / 2; } } else { sbWidth = pixelWidth * imageWidth / 5; sbDP = autoCalculateDecPlacesFromValueOnly(sbWidth) + 2; /* Add more dp for line labeling */ sbWidthString = d2s(sbWidth, sbDP); sbWidthString = d2s(sbWidth, sbDP); } selOffsetX = minOf(imageWidth / 20, maxOf(dOutS, round(imageWidth / 120))); selOffsetY = minOf(imageHeight / 20, maxOf(dOutS, round(maxOf(imageHeight / 180, 0.35 * sbFontSize)))); indexSBWidth = parseInt(substring(d2s(sbWidth, -1), indexOf(d2s(sbWidth, -1), "E") + 1)); dpSB = maxOf(0, 1 - indexSBWidth); sbWidth1SF = round(sbWidth / pow(10, indexSBWidth)); sbWidth2SF = round(sbWidth / pow(10, indexSBWidth - 1)); preferredSBW = newArray(10, 20, 25, 50, 75); /* Edit this list to your preferred 2 digit numbers */ sbWidth2SFC = closestValueFromArray(preferredSBW, sbWidth2SF, 100); /* alternatively could be sbWidth1SF*10 */ if (selEType != 5) sbWidth = pow(10, indexSBWidth - 1) * sbWidth2SFC; fScaleBarOverlays = countOverlaysByName("cale"); /* ASC message theme */ infoColor = "#006db0"; /* Honolulu blue */ instructionColor = "#798541"; /* green_dark_modern (121, 133, 65) AKA Wasabi */ infoWarningColor = "#ff69b4"; /* pink_modern AKA hot pink */ infoFontSize = 12; degChar = fromCharCode(0x00B0); divChar = fromCharCode(0x00F7); plusminus = fromCharCode(0x00B1); modeStr = "scale bar"; if (selEType >= 0) { if (selEType != 5) { locChoices = newArray("Top Left", "Top Right", "Bottom Center", "Bottom Left", "Bottom Right", "At Center of New Selection", "At Selection Center"); iLoc = indexOfArray(locChoices, call("ij.Prefs.get", prefsNameKey + ".location", locChoices[6]), 6); } else { locChoices = newArray("Over center"); if (lineMidX > imageWidth / 2) locChoices = Array.concat(locChoices, "Left of line start", "Right of line start", "Left of line end", "Right of line end"); else locChoices = Array.concat(locChoices, "Right of line start", "Left of line start", "Right of line end", "Left of line end"); iLoc = indexOfArray(locChoices, call("ij.Prefs.get", prefsNameKey + ".location", locChoices[0]), 0); modeStr = "length line"; } } else { locChoices = newArray("Top Left", "Top Right", "Bottom Center", "Bottom Left", "Bottom Right", "Under Image Left", "Under Image Right", "At Center of New Selection"); iLoc = indexOfArray(locChoices, call("ij.Prefs.get", prefsNameKey + ".location", locChoices[4]), 4); } if (fullMenuHeight > 0.9 * screenHeight) compactMenu = true; /* used to limit menu size for small screens */ else compactMenu = false; if (compactMenu) menuLabel = "Scale Bar Parameters \(compact menu for low resolution screens\): " + macroL; else menuLabel = "Scale Bar Parameters: " + macroL; Dialog.create(menuLabel); if (pixelHeight != pixelWidth) Dialog.addMessage("Warning: Non-square pixels \(pixelHeight/pixelWidth = " + pixelHeight / pixelWidth + "\)", infoFontSize, infoWarningColor); if (selEType == 5) { Dialog.addMessage("Currently in length labeling mode: Select none or a non-straight-line selection to draw a scale bar", infoFontSize, infoWarningColor); Dialog.addNumber("Selected line length \(" + d2s(lineLengthPx, trueDPMax) + " pixels\):", sbWidth, trueDPMax, 10, selectedUnit); Dialog.addString("Selected line angle \(" + lineAngle + degChar + " from horizontal\):", d2s(lineAngle, 2), 5); Dialog.addString("Length/angle separator \(i.e. , \):", "No angle label", 10); Dialog.addString("Text label \(end with ':' of '=' to add length/angle\)", textLabel, 20); Dialog.setInsets(-5, 100, 5); Dialog.addMessage("Leave the 'Text label' blank if you only want to show the measurements", infoFontSize, infoColor); } else { Dialog.addMessage("Currently in scale bar mode: Use the straight line selection tool to activate length labeling mode", infoFontSize + 1, infoWarningColor); dText = "Length of scale bar"; if (selEType >= 0) dText += " \(precise length = " + sbPreciseWidthString + "\)"; Dialog.addNumber(dText + ":", sbWidth, dpSB, 10, selectedUnit); } if (sF > 0) { if (sbWidth > 999 || sbWidth < 1) { Dialog.setInsets(0, 255, 0); Dialog.addMessage("Consider changing the unit:", infoFontSize, infoWarningColor); } newUnits = newArray("" + selectedUnit + " Length x1", "cm \(Length x" + nSF[1] + "\)", "mm \(Length x" + nSF[2] + "\)", micronS + " \(Length x" + nSF[3] + "\)", "microns \(Length x" + nSF[4] + "\)", "nm \(Length x" + nSF[5] + "\)", "Å \(Length x" + nSF[6] + "\)", "pm \(Length x" + nSF[7] + "\)", "inches \(Length x" + nSF[8] + "\)", "human hair \(Length x" + nSF[9] + "\)"); } Dialog.addChoice("Override unit with new choice?", newUnits, newUnits[0]); Dialog.addNumber("Font size \(\"FS\"\):", sbFontSize, 0, 4, ""); Dialog.addNumber("Thickness of " + modeStr + " \(default 70\):", call("ij.Prefs.get", prefsNameKey + ".barHeightPC", 70), 0, 3, "% of '!' character width"); grayChoices = newArray("white", "black", "off-white", "off-black", "lightGray", "gray", "darkGray"); colorChoicesStd = newArray("red", "green", "blue", "cyan", "magenta", "yellow", "pink", "orange", "violet"); colorChoicesMaterials = newArray("bronze", "antique_bronze", "brass", "dull_brass", "brick", "chrome", "copper", "aged_copper", "dusky_copper", "light_copper", "garnet", "burnished_gold", "gold", "slate_gray", "titanium", "vault_garnet", "plaza_brick", "vault_gold"); colorChoicesMod = newArray("aqua_modern", "blue_accent_modern", "blue_dark_modern", "blue_modern", "blue_honolulu", "gray_modern", "green_dark_modern", "green_modern", "green_modern_accent", "green_spring_accent", "orange_modern", "pink_modern", "purple_modern", "red_modern", "tan_modern", "violet_modern", "yellow_modern"); colorChoicesNeon = newArray("jazzberry_jam", "radical_red", "wild_watermelon", "outrageous_orange", "supernova_orange", "atomic_tangerine", "neon_carrot", "sunglow", "laser_lemon", "electric_lime", "screamin'_green", "magic_mint", "blizzard_blue", "dodger_blue", "shocking_pink", "razzle_dazzle_rose", "hot_magenta"); colorChoicesFSU = newArray("stadium_night", "westcott_water", "legacy_blue"); /* Materials colors moved to Materials set */ colorChoices = Array.concat(grayChoices, colorChoicesStd, colorChoicesMaterials, colorChoicesMod, colorChoicesNeon, colorChoicesFSU); if (startsWith(fancyStyle, "No")) { if ((bgIpc > 50 && !startsWith(locChoices[iLoc], "Under")) || (bgIpc < 50 && startsWith(locChoices[iLoc], "Under"))) { iTC = 1; iBC = 0; iTG = 1; iBG = 0; } else { iTC = 0; iBC = 1; iTG = 0; iBG = 1; } } else { if ((bgIpc > 30 && !startsWith(locChoices[iLoc], "Under")) || (bgIpc < 30 && startsWith(locChoices[iLoc], "Under"))) { iTC = 0; iBC = 1; iTG = 0; iBG = 1; } else { iTC = 1; iBC = 0; iTG = 1; iBG = 0; } } /* Recall non-BW colors */ if (imageDepth == 24) { iTC = indexOfArray(colorChoices, call("ij.Prefs.get", prefsNameKey + ".font.color", colorChoices[iTC]), iTC); iBC = indexOfArray(colorChoices, call("ij.Prefs.get", prefsNameKey + ".outline.color", colorChoices[iBC]), iBC); } iTG = indexOfArray(grayChoices, call("ij.Prefs.get", prefsNameKey + ".font.gray", grayChoices[iTG]), iTG); iBG = indexOfArray(grayChoices, call("ij.Prefs.get", prefsNameKey + ".outline.gray", grayChoices[iBG]), iBG); /* Reverse Black/white if it looks like it will not work with background intensity Note: keep white/black color order in colorChoices for intensity reversal after background intensity check */ if (imageDepth == 24) { Dialog.addChoice("Color of " + modeStr + " and text \(median intensity = " + bgIpc + "%\):", colorChoices, colorChoices[iTC]); Dialog.addChoice("Outline (background) color:", colorChoices, colorChoices[iBC]); } else { Dialog.addChoice("Gray tone of " + modeStr + " and text:", grayChoices, grayChoices[iTG]); Dialog.addChoice("Gray tone of background:", grayChoices, grayChoices[iBG]); if (compactMenu) { Dialog.setInsets(-65, 370, 0); Dialog.addMessage("Image depth is " + imageDepth + " bits:\nOnly gray tones used unless\noverlays are selected below", infoFontSize, infoColor); } else { Dialog.setInsets(-3, 50, 0); Dialog.addMessage("Image depth is " + imageDepth + " bits: Only gray tones used unless overlays are selected below", infoFontSize, infoColor); } } if (selEType != 5) { Dialog.addChoice("Location of " + modeStr + ":", locChoices, locChoices[iLoc]); Dialog.setInsets(-3, 50, 0); /* top, left, bottom */ Dialog.addMessage("'Under Image' options: Expands the frame. Overrides style options below with simple text", infoFontSize, infoColor); } else { if (compactMenu) Dialog.addChoice("Location of " + modeStr + ":", locChoices, locChoices[iLoc]); else Dialog.addRadioButtonGroup("Location of " + modeStr + ":___________", locChoices, 1, locChoices.length, locChoices[iLoc]); } fancyStyles = newArray("Standard for busy images", "Minimal stroke and shadows", "No fancy formatting"); iFancy = indexOfArray(fancyStyles, call("ij.Prefs.get", prefsNameKey + ".fancyStyle", fancyStyles[0]), 0); if (startsWith(locChoices[iLoc], "Under")) fancyStyle = "No fancy formatting"; /* Overrides preferences because there is no point to fancy formatting on a plain background */ if (compactMenu) Dialog.addChoice("Choose fancy style:___________", fancyStyles, fancyStyles[iFancy]); else Dialog.addRadioButtonGroup("Choose fancy style:___________", fancyStyles, 1, 3, fancyStyles[iFancy]); fancyStyleEffectsOptions = newArray("No text", "No shadows", "Raised", "Recessed", "Transparent"); if ((imageAR >= 2 && selEType != 5) || startsWith(locChoices[iLoc], "Under")) sideBySide = true; else sideBySide = false; if (startsWith(fancyStyle, "No") || startsWith(locChoices[iLoc], "Under")) { noShadow = true; noOutline = true; raised = false; } else { noShadow = false; noOutline = false; raised = false; } fancyStyleEffectsDefaults = newArray(false, noShadow, raised, raised, false); if (selEType != 5) { fancyStyleEffectsDefaults = Array.concat(fancyStyleEffectsDefaults, sideBySide); fancyStyleEffectsOptions = Array.concat(fancyStyleEffectsOptions, "Side-by-Side"); } fancyStyleEffectsPrefs = call("ij.Prefs.get", prefsNameKey + ".fancyStyleEffects", "not found"); if (fancyStyleEffectsPrefs != "not found") { fancyStyleEffects = split(fancyStyleEffectsPrefs, "|"); for (i = 0; i < fancyStyleEffects.length && i < fancyStyleEffectsOptions.length; i++) fancyStyleEffectsDefaults[i] = fancyStyleEffects[i]; } Dialog.setInsets(5, 0, -8); Dialog.addMessage("Text style modifiers \('Recessed' and 'Raised' do not apply to overlays, 'Under Image' not styled\):", infoFontSize, infoColor); fSL = fancyStyleEffectsOptions.length; fSColumns = minOf(6, fSL); fSRows = round(fSL / fSColumns); if (fSColumns * fSRows < fSL) fSRows += 1; Dialog.addCheckboxGroup(fSRows, fSColumns, fancyStyleEffectsOptions, fancyStyleEffectsDefaults); sBStyleChoices = newArray("Solid Bar", "I-Bar", "Open Arrow", "Open Arrows", "Filled Arrow", "Filled Arrows", "Notched Arrow", "Notched Arrows"); if (selEType != 5) iSBS = indexOfArray(sBStyleChoices, call("ij.Prefs.get", prefsNameKey + ".style", sBStyleChoices[0]), 0); else iSBS = indexOfArray(sBStyleChoices, call("ij.Prefs.get", prefsNameKey + ".lineLabelStyle", sBStyleChoices[2]), 2); if (compactMenu) Dialog.addChoice("Bar styles and formatting:___________", sBStyleChoices, sBStyleChoices[iSBS]); else Dialog.addRadioButtonGroup("Bar styles and formatting:___________", sBStyleChoices, 2, 4, sBStyleChoices[iSBS]); if (selEType == 5) { if (compactMenu) { Dialog.setInsets(-33, 410, -10); Dialog.addMessage("Single arrow points in\nthe direction drawn", infoFontSize, infoColor); } else Dialog.addMessage("Single arrow points in the direction drawn", infoFontSize, infoColor); } barHThicknessChoices = newArray("Small", "Medium", "Large"); iHT = indexOfArray(barHThicknessChoices, call("ij.Prefs.get", prefsNameKey + ".barHeader.thickness", barHThicknessChoices[0]), 0); Dialog.addChoice("Arrowhead/bar header thickness", barHThicknessChoices, barHThicknessChoices[iHT]); xOffsetString = "Default: " + selOffsetX + " px, " + round(selOffsetX / 2) + " 'under' locations, " + round(selOffsetX / 10) + " for selections"; yOffsetString = "Default: " + selOffsetY + " px, " + round(selOffsetY / 2) + " 'under' locations, " + round(selOffsetY / 10) + " for selections"; Dialog.addString("X pixel min offset from edge \(leave for defaults\)", xOffsetString, 37); Dialog.addString("Y pixel min offset from edge \(leave for defaults\)", yOffsetString, 37); fontNameChoice = getFontChoiceList(); fontChoiceText = "Font name:"; if (indexOfArrayThatContains(fontNameChoice, "Black", -1) < 0 && indexOfArrayThatContains(fontNameChoice, "ExtraBold", -1) < 0) Dialog.addMessage("No 'Black' or 'ExtraBold' fonts found: These work best here", infoFontSize, infoWarningColor); else if (indexOfArrayThatContains(fontNameChoice, "Black", -1) > 0 && indexOfArrayThatContains(fontNameChoice, "ExtraBold", -1) > 0) fontChoiceText = "Font name \('Black' or 'ExtraBold' recommended\):"; iFN = indexOfArray(fontNameChoice, call("ij.Prefs.get", prefsNameKey + ".font", fontNameChoice[0]), 0); Dialog.addChoice(fontChoiceText, fontNameChoice, fontNameChoice[iFN]); outputLabel = "Output"; if (imageDepth == 16 || imageDepth == 32) { newChoices = newArray("New 8-bit image"); /* Fancy style effects do not work for 16-bit images */ if (compactMenu) outputLabel += " \(8-bit or RGB image required for fancy effects\):"; else outputLabel += " \(image needs to be converted to 8-bit to display all the fancy style effects\):______________"; } else { newChoices = newArray("New image", "Add to image"); if (compactMenu) outputLabel += " \('Add to image' modifies current image\):"; else outputLabel += " \('Add to image' will modify the current image\):_______________________"; } overwriteChoices = newArray("Add as overlays"); if (imageWidth <= 23000) overwriteChoices = Array.concat(newChoices, overwriteChoices); iOver = indexOfArray(overwriteChoices, call("ij.Prefs.get", prefsNameKey + ".output", overwriteChoices[0]), 0); if (compactMenu) Dialog.addChoice(outputLabel, overwriteChoices, overwriteChoices[iOver]); else Dialog.addRadioButtonGroup(outputLabel, overwriteChoices, 1, lengthOf(overwriteChoices), overwriteChoices[iOver]); if (overlayN > 0 && fScaleBarOverlays > 0) { Dialog.setInsets(0, 235, 0); Dialog.addCheckbox("Remove the " + fScaleBarOverlays + " existing named scale bar overlays", true); } if (overlayN > fScaleBarOverlays) { Dialog.setInsets(0, 235, 0); Dialog.addCheckbox("Remove all " + overlayN + " existing overlays \(simple text is unnamed\)", remAllOverlays); } if (imageDepth != 24) { Dialog.setInsets(5, 0, 0); /* top, left, bottom */ Dialog.addChoice("Overlay color of " + modeStr + " and text:", colorChoices, colorChoices[iTC]); Dialog.setInsets(0, 0, 5); /* top, left, bottom */ Dialog.addChoice("Overlay outline (background) color:", colorChoices, colorChoices[iBC]); } ovShadowOpacity = call("ij.Prefs.get", prefsNameKey + ".ovShadowOpacity", 50); Dialog.addSlider("Overlay shadow opacity \(if overlay selected above\)", 0, 100, ovShadowOpacity); if (slices > 1) Dialog.addString("Slice range for labeling \(1-" + slices + "\):", startSliceNumber + "-" + slices, 20); else if (channels > 1) Dialog.addMessage("All " + channels + " channels will be identically labeled.", infoFontSize, warningColor); finalOptions = newArray("Tweak formatting?", "Diagnostic mode?"); finalOptionsChecks = newArray(false, false); if (imageDir != "") { if (lengthOf(imageDir) > 50) outputD = substring(imageDir, 0, 25) + "..." + substring(imageDir, lengthOf(imageDir) - 25); else outputD = imageDir; Dialog.addMessage("Files saved to " + outputD + " \('+scale' added to name\)", infoFontSize, infoColor); } finalOptions = Array.concat("saveTIFF", "saveJPEG", finalOptions); finalOptionsChecks = Array.concat(call("ij.Prefs.get", prefsNameKey + ".output.saveTIFF", 1), call("ij.Prefs.get", prefsNameKey + ".output.saveJPEG", 1), finalOptionsChecks); Dialog.addCheckboxGroup(1, finalOptions.length, finalOptions, finalOptionsChecks); Dialog.show(); sbLengthInUnits = Dialog.getNumber; if (selEType == 5) { angleLabel = Dialog.getString; angleSeparator = Dialog.getString; textLabel = Dialog.getString; } if (sF > 0) overrideUnit = Dialog.getChoice; else overrideUnit = ""; fontSize = Dialog.getNumber; sbHeightPC = Dialog.getNumber; if (imageDepth == 24) { scaleBarColor = Dialog.getChoice; outlineColor = Dialog.getChoice; } else { scaleBarColor = Dialog.getChoice; outlineColor = Dialog.getChoice; } if (selEType != 5 || compactMenu) selPos = Dialog.getChoice; else selPos = Dialog.getRadioButton(); if (compactMenu) fancyStyle = Dialog.getChoice(); else fancyStyle = Dialog.getRadioButton(); /* fancy style effects checkbox group order: "No text", "No shadows", "Raised", "Recessed", Side-by-side */ noText = Dialog.getCheckbox(); fancyStyleEffectsString = "" + d2s(noText, 0); noShadow = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(noShadow, 0); raised = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(raised, 0); recessed = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(recessed, 0); transparent = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(transparent, 0); if (selEType != 5) sideBySide = Dialog.getCheckbox(); /* End of checkbox group */ /* Overrides: */ if (startsWith(fancyStyle, "No") || startsWith(selPos, "Under")) { notFancy = true; noShadow = true; noOutline = true; raised = false; transparent = false; } else { noShadow = false; noOutline = false; } if (compactMenu) sBStyle = Dialog.getChoice; else sBStyle = Dialog.getRadioButton; barHThickness = Dialog.getChoice; selOffsetXString = Dialog.getString; selOffsetYString = Dialog.getString; if (selOffsetXString != xOffsetString) selOffsetX = parseInt(selOffsetXString); else if (startsWith(selPos, "Under")) selOffsetX = round(selOffsetX / 2); else if (indexOf(selPos, "election") >= 0) selOffsetX = round(selOffsetX / 10); if (selOffsetYString != yOffsetString) selOffsetY = parseInt(selOffsetYString); else if (startsWith(selPos, "Under")) selOffsetY = round(selOffsetY / 2); else if (indexOf(selPos, "election") >= 0) selOffsetY = round(selOffsetY / 10); // fontStyle = Dialog.getChoice; fontName = Dialog.getChoice; if (compactMenu) overWrite = Dialog.getChoice(); else overWrite = Dialog.getRadioButton(); if (overlayN > 0 && fScaleBarOverlays > 0) remOverlays = Dialog.getCheckbox(); else remOverlays = false; if (overlayN > fScaleBarOverlays) remAllOverlays = Dialog.getCheckbox(); else remAllOverlays = false; allSlices = false; labelRest = true; if (slices > 1) { sliceRangeS = Dialog.getString; /* changed from original to allow negative values - see below */ sliceRange = split(sliceRangeS, "-"); if (sliceRange.length == 2) { startSliceNumber = parseInt(sliceRange[0]); endSlice = parseInt(sliceRange[1]); } if ((startSliceNumber == 0) && (endSlice == slices)) allSlices = true; if (startSliceNumber == endSlice) labelRest = false; } else { startSliceNumber = 1; endSlice = 1; } if (sF > 0) { oU = indexOfArray(newUnits, overrideUnit, 0); oSF = nSF[oU]; selectedUnit = overrideUnitChoices[oU]; } if (imageDepth != 24) { scaleBarColorOv = Dialog.getChoice; outlineColorOv = Dialog.getChoice; } ovShadowOpacity = Dialog.getNumber; call("ij.Prefs.set", prefsNameKey + ".ovShadowOpacity", ovShadowOpacity); saveTIFF = Dialog.getCheckbox(); call("ij.Prefs.set", prefsNameKey + ".output.saveTIFF", saveTIFF); saveJPEG = Dialog.getCheckbox(); call("ij.Prefs.set", prefsNameKey + ".output.saveJPEG", saveJPEG); tweakF = Dialog.getCheckbox(); diagnostics = Dialog.getCheckbox(); /* End of Main Dialog */ if (notFancy && ((bgIpc < 3 && endsWith(scaleBarColor, "black")) || (bgIpc > 97 && endsWith(scaleBarColor, "white")))) { Dialog.create("No-contrast warning"); contrastTxt = "The background intensity is " + bgIpc + "%, and also the scale bar color is " + scaleBarColor; Dialog.addMessage(contrastTxt, infoFontSize, infoWarningColor); Dialog.addCheckbox("Reverse colors?", true); Dialog.show(); if (Dialog.getCheckbox()) { if (bgIpc < 3) scaleBarColor = replace(scaleBarColor, "black", "white"); else scaleBarColor = replace(scaleBarColor, "white", "black"); } } if (startsWith(selPos, "Under")) { selOffsetY /= 2; reverseContrast = false; if (bgIpc < 3 && endsWith(outlineColor, "white")) reverseContrast = true; if (bgIpc > 97 && endsWith(outlineColor, "black")) reverseContrast = true; if (!sideBySide || reverseContrast) { if (reverseContrast) { contrastTxt = "The background intensity is " + bgIpc + "%, whereas the expansion color is " + outlineColor; if (bgIpc < 3) { yesLabel = "Change background to black"; if (scaleBarColor == "black") yesLabel += ", and scale bar to white"; } else { yesLabel = "Change background to white"; if (scaleBarColor == "white") yesLabel += ", and scale bar to black"; } noLabel = "No change"; } Dialog.create("'Under' scale bar tweaks"); if (reverseContrast) { Dialog.addMessage(contrastTxt, infoFontSize, infoWarningColor); Dialog.addCheckbox(yesLabel, reverseContrast); } Dialog.addCheckbox("Side-by-side scale bar and scale?", sideBySide); Dialog.show(); if (reverseContrast) reverseContrast = Dialog.getCheckbox(); sideBySide = Dialog.getCheckbox(); if (reverseContrast) { if (bgIpc < 3) { outlineColor = "black"; if (scaleBarColor == "black") scaleBarColor = "white"; } else { outlineColor = "white"; if (scaleBarColor == "white") scaleBarColor = "black"; } } } } if (endsWith(overWrite, "overlays")) applyOverlays = true; else applyOverlays = false; if (fontName == "SansSerif" || fontName == "Serif" || fontName == "Monospaced") fontStyle = "bold antialiased"; else fontStyle = "antialiased"; setFont(fontName, fontSize, fontStyle); if (noShadow && noOutline && !raised && !recessed) notFancy = true; sbLengthInPixels = sbLengthInUnits / pixelWidth; if (sF > 0) sbLengthInUnits *= oSF; /* now safe to change units */ if (textLabel != "") label = textLabel; /* textLabel is only in menu if in line mode */ if (textLabel == "" || endsWith(textLabel, ":") || endsWith(textLabel, ": ") || endsWith(textLabel, "=") || endsWith(textLabel, "= ")) { if (selEType != 5 || (selEType == 5 && trueDPMax <= 0)) selLengthLabel = removeTrailingZerosAndPeriod(toString(sbLengthInUnits)); else selLengthLabel = d2s(sbLengthInUnits, trueDPMax); if (endsWith(textLabel, ":") || endsWith(textLabel, ": ")) label += " " + selLengthLabel + " " + selectedUnit; else label = selLengthLabel + " " + selectedUnit; } if (selEType == 5 && (textLabel == "" || endsWith(textLabel, ":") || endsWith(textLabel, ": ") || endsWith(textLabel, "=") || endsWith(textLabel, "= "))) { if (angleSeparator != "No angle label") label += angleSeparator + " " + angleLabel + degChar; } labelWidth = getStringWidth(label); labelSemiL = labelWidth / 2; if (!noText && !sideBySide && selEType != 5) { stringOF = 0.9 * labelWidth / sbLengthInPixels; if (!startsWith(selPos, "Under") && stringOF > 1) { shrinkFactor = getNumber("Initial label '" + label + "' is " + stringOF + "x scale bar \(+10% margin\); shrink font by x", 1 / stringOF); fontSize *= shrinkFactor; setFont(fontName, fontSize); labelWidth = getStringWidth(label); labelSemiL = labelWidth / 2; } else if (startsWith(selPos, "Under")) { stringFL = (1.1 * (labelWidth + sbLengthInPixels) + selOffsetX) / imageWidth; if (stringFL > 1) exit("Combined scale width and text are too long for the 'Under' option"); } } if (startsWith(fancyStyle, "Minimal")) { dOutS = 1.5; /* default outline stroke: % of font size */ dShO = 1.5; /* default outer shadow drop: % of font size */ dIShO = 1; /* default inner shadow drop: % of font size */ /* set default tweaks */ outlineStroke = maxOf(1, dOutS); shadowDrop = maxOf(outlineStroke, dShO); shadowDisp = maxOf(outlineStroke, dShO); shadowBlur = maxOf(outlineStroke, 0.75 * dShO); } if (!notFancy) { if (tweakF) { Dialog.create("Scale Bar Format Tweaks: " + macroL); Dialog.addMessage("Font size \(FS\): " + fontSize, infoFontSize, infoColor); Dialog.addNumber("Outline stroke:", dOutS, 1, 3, "% of font size \(\"%FS\"\)"); if (!noShadow) { Dialog.addNumber("Shadow drop: " + plusminus, dShO, 1, 3, "%FS"); Dialog.addNumber("Shadow shift \(+ve right\)", dShO, 1, 3, ": %FS"); Dialog.addNumber("Shadow Gaussian blur:", maxOf(0.5, 0.75 * dShO), 1, 3, "%FS"); Dialog.addNumber("Shadow darkness \(darkest = 100%\):", 30, 1, 3, "% \(negative = glow\)"); } Dialog.show(); outlineStroke = Dialog.getNumber; if (!noShadow) { shadowDrop = Dialog.getNumber; shadowDisp = Dialog.getNumber; shadowBlur = Dialog.getNumber; shadowDarkness = Dialog.getNumber; } } } if (!diagnostics) { if (imageWidth + imageHeight > 20000) setBatchMode("hide"); /* a little help for large images */ else setBatchMode(true); } /* save last used color settings in user in preferences */ fontHeight = getValue("font.height"); spaceWidth = getStringWidth(" "); pxConv = fontHeight / fontSize; fontLineWidth = getStringWidth("!"); sbHeight = maxOf(2, round(fontLineWidth * sbHeightPC / 100)); /* set minimum default bar height as 2 pixels */ if (imageDepth == 24) { call("ij.Prefs.set", prefsNameKey + ".font.color", scaleBarColor); call("ij.Prefs.set", prefsNameKey + ".outline.color", outlineColor); if (applyOverlays) { scaleBarColorOv = scaleBarColor; outlineColorOv = outlineColor; } } else { if (applyOverlays) { scaleBarColor = scaleBarColorOv; outlineColor = outlineColorOv; call("ij.Prefs.set", prefsNameKey + ".font.color", scaleBarColor); call("ij.Prefs.set", prefsNameKey + ".outline.color", outlineColor); } else { call("ij.Prefs.set", prefsNameKey + ".font.gray", scaleBarColor); call("ij.Prefs.set", prefsNameKey + ".outline.gray", outlineColor); } } tS = "" + stripKnownExtensionFromString(unCleanLabel(activeImage)); if (selEType != 5) { if (endsWith(tS, "_EmbScale")) tS = replace(tS, "_EmbScale", ""); /* just removes my preferred note for embedded scale */ if (!endsWith(tS, "cale")) tS = tS + "_scale"; } else if (indexOf(tS, "LLabel") < 0) tS += "_LLabel"; c = 1; tS0 = tS; while (isOpen(tS)) { tS = "" + tS0 + c; c++; } if (remAllOverlays) run("Remove Overlay"); else if (remOverlays) { removeOverlaysByName("cale"); removeOverlaysByName("SB"); /* ImageJ scale bars */ } if (startsWith(overWrite, "New")) { selectImage(orImageID); run("Select None"); run("Duplicate...", "title=" + tS + " duplicate"); if (startsWith(overWrite, "New 8") || startsWith(overWrite, "New R")) { if (startsWith(overWrite, "New 8")) run("8-bit"); else run("RGB Color"); call("ij.Prefs.set", prefsNameKey + ".reduceDepth", true); /* not used here but saved for future version of fast'n fancy variant */ } activeImage = getTitle(); activeImageID = getImageID(); imageDepth = bitDepth(); } else activeImageID = orImageID; if (startsWith(selPos, "Under")) { expH = fontHeight + 2 * selOffsetY; if (!sideBySide) expH += 4 * sbHeight; imageHeight += expH; } // setFont(fontName, fontSize, fontStyle); setFont(fontName, fontSize); /* save last used settings in user in preferences */ // call("ij.Prefs.set", prefsNameKey + ".font.style", fontStyle); call("ij.Prefs.set", prefsNameKey + ".font", fontName); if (selEType != 5) call("ij.Prefs.set", prefsNameKey + ".style", sBStyle); else call("ij.Prefs.set", prefsNameKey + ".lineLabelStyle", sBStyle); call("ij.Prefs.set", prefsNameKey + ".barHeightPC", sbHeightPC); call("ij.Prefs.set", prefsNameKey + ".fancyStyle", fancyStyle); call("ij.Prefs.set", prefsNameKey + ".fancyStyleEffects", fancyStyleEffectsString); call("ij.Prefs.set", prefsNameKey + ".location", selPos); call("ij.Prefs.set", prefsNameKey + ".output", overWrite); call("ij.Prefs.set", prefsNameKey + ".barHeader.thickness", barHThickness); // if (imageDepth!=16 && imageDepth!=32 && fontStyle!="unstyled") fontStyle += "antialiased"; /* antialising will be applied if possible */ fontFactor = fontSize / 100; if (outlineStroke != 0) outlineStroke = maxOf(1, round(fontFactor * outlineStroke)); /* if some outline is desired set to at least one pixel */ if (!noShadow) { negAdj = 0.5; /* negative offsets appear exaggerated at full displacement */ if (shadowDrop < 0) shadowDrop *= negAdj; if (shadowDisp < 0) shadowDisp *= negAdj; if (shadowBlur < 0) shadowBlur *= negAdj; if (shadowDrop >= 0) shadowDrop = maxOf(1, round(fontFactor * shadowDrop)); else if (shadowDrop <= 0) shadowDrop = minOf(-1, round(fontFactor * shadowDrop)); if (shadowDisp >= 0) shadowDisp = maxOf(1, round(fontFactor * shadowDisp)); else if (shadowDisp <= 0) shadowDisp = minOf(-1, round(fontFactor * shadowDisp)); if (shadowBlur >= 0) shadowBlur = maxOf(1, round(fontFactor * shadowBlur)); else if (shadowBlur <= 0) shadowBlur = minOf(-1, round(fontFactor * shadowBlur)); if (selOffsetX < (shadowDisp + shadowBlur + 1)) selOffsetX += (shadowDisp + shadowBlur + 1); /* make sure shadow does not run off edge of image */ if (selOffsetY < (shadowDrop + shadowBlur + 1)) selOffsetY += (shadowDrop + shadowBlur + 1); if (shadowDrop == 0 && shadowDisp == 0 && shadowBlur == 0) noShadow = true; } if (noOutline) { outlineStroke = 0; outlineStrokePC = 0; } if (selEType != 5) { // if (fontStyle=="unstyled") fontStyle=""; if (selPos == "Top Left") { selEX = selOffsetX; selEY = selOffsetY; } else if (selPos == "Top Right") { selEX = imageWidth - sbLengthInPixels - selOffsetX; selEY = selOffsetY; } else if (selPos == "Bottom Center") { selEX = imageWidth / 2 - sbLengthInPixels / 2; selEY = imageHeight - sbHeight - (selOffsetY); } else if (selPos == "Bottom Left") { selEX = selOffsetX; selEY = imageHeight - sbHeight - (selOffsetY); } else if (selPos == "Bottom Right") { selEX = imageWidth - sbLengthInPixels - selOffsetX; selEY = imageHeight - sbHeight - selOffsetY; } else if (selPos == "Under Image Left") { selEX = selOffsetX; selEY = imageHeight - maxOf(fontHeight / 2, sbHeight / 2) - (selOffsetY); } else if (selPos == "Under Image Right") { selEX = imageWidth - sbLengthInPixels - selOffsetX; if (!noText) selEX -= labelWidth; selEY = imageHeight - maxOf(fontHeight / 2, sbHeight / 2) - selOffsetY; } else if (selPos == "At Center of New Selection") { if (is("Batch Mode") == true) setBatchMode("exit & display"); /* toggle batch mode off */ run("Select None"); setTool("rectangle"); title = "position"; msg = "draw a box in the image where you want the scale bar to be centered"; waitForUser(title, msg); getSelectionBounds(newSelEX, newSelEY, newSelEWidth, newSelEHeight); selEX = Math.constrain(newSelEX + round((newSelEWidth / 2) - maxOf(labelWidth, sbLengthInPixels) / 2), selOffsetX, imageWidth - selOffsetY - sbLengthInPixels); selEY = Math.constrain(newSelEY + round(newSelEHeight / 2) + (sbHeight + fontHeight) / 2, selOffsetY, imageHeight - selOffsetY - sbHeight); if (is("Batch Mode") == false && !diagnostics) setBatchMode("hide"); /* toggle batch mode back on */ } else if (selPos == "At Selection Center") { selEX = Math.constrain(selEX + round((selEWidth / 2) - maxOf(labelWidth, sbLengthInPixels) / 2), selOffsetX, imageWidth - selOffsetY - sbLengthInPixels); selEY = Math.constrain(selEY + round(selEHeight / 2) + (sbHeight + fontHeight) / 2, selOffsetY, imageHeight - selOffsetY - sbHeight); } if (sBStyle != "Solid Bar") { if (startsWith(selPos, "Bottom") || startsWith(selPos, "Under")) selEY -= sbHeight / 2; if (startsWith(selPos, "Top")) selEY += sbHeight; } } else { /* line label positions */ rSelLX = Array.rankPositions(selLX); if (endsWith(selPos, "tart")) { iXY1 = 0; iXY2 = 1; } else { iXY1 = 1; iXY2 = 0; } if (startsWith(selPos, "Right")) { if (selLX[iXY1] + 2 * fontLineWidth > imageWidth && lineMidX > imageWidth / 2) selPos = "Left of line end"; else { finalLabelX = Math.constrain(selLX[iXY1] + 2 * fontLineWidth, selOffsetX, imageWidth - labelWidth - fontLineWidth); if (selLX[iXY1] > selLX[iXY2]) finalLabelY = Math.constrain(selLY[iXY1] + fontHeight / 2, fontHeight, imageHeight - fontHeight); else { finalLabelY = Math.constrain(selLY[iXY1] + fontHeight, fontHeight, imageHeight - fontHeight); finalLabelX = Math.constrain(selEX - fontSize, selOffsetX, imageWidth - labelWidth - fontLineWidth); } } } else if (startsWith(selPos, "Left")) { finalLabelX = Math.constrain(selLX[iXY1] - fontLineWidth - labelWidth, selOffsetX, imageWidth - lwidth); if (selLX[iXY1] > selLX[iXY2]) { finalLabelY = Math.constrain(selLY[iXY1] + fontHeight, fontHeight + fontLineWidth, imageHeight - fontHeight); } else finalLabelY = Math.constrain(selLY[iXY1] - fontHeight / 2, fontHeight + fontLineWidth, imageHeight - fontHeight); } else { finalLabelX = Math.constrain(lineMidX - labelWidth / 2, selOffsetX, imageWidth - labelWidth); finalLabelY = Math.constrain(lineMidY + fontHeight / 2, fontHeight, imageHeight - fontHeight); if (abs(lineAngle) < 10) finalLabelY -= fontHeight; /* for horizontal lines literally Over center" */ } } /* edge limits for bar - assume intent is not to annotate edge objects */ maxSelEY = imageHeight - round(sbHeight / 2) + selOffsetY; selEY = maxOf(minOf(selEY, maxSelEY), selOffsetY); maxSelEX = imageWidth - (sbLengthInPixels + selOffsetX); /* stop overrun on scale bar by label and side-by-side label adjustments */ if (sideBySide) { spaceWidth *= 2; /* tweaks for better spacing for side-by-side */ sbsWidth = labelWidth + spaceWidth + sbLengthInPixels; if (sbsWidth > imageWidth) exit("The selected side-by-side scale bar is too wide for the image"); sbsHeight = maxOf(fontHeight, sbHeight); /* adjust for side-by-side orientation */ selEX = Math.constrain(selEX, selOffsetX, imageWidth - sbsWidth - selOffsetX - sbsWidth); if (selPos == "At Selection Center") selEY += fontHeight / 4; selEY = Math.constrain(selEY, selOffsetY + sbsHeight, imageHeight - fontHeight / 2 - selOffsetY); finalLabelY = selEY + fontHeight / 2; if (endsWith(selPos, "Left")) finalLabelX = selEX + spaceWidth + sbLengthInPixels; else if (endsWith(selPos, "Right")) { finalLabelX = imageWidth - labelWidth - selOffsetX; selEX = finalLabelX - sbLengthInPixels - spaceWidth; } else finalLabelX = selEX + spaceWidth + sbLengthInPixels; } else if (selEType != 5) { selEX = Math.constrain(selEX, selOffsetX, maxSelEX); /* stop text overrun */ stringOver = (labelWidth - sbLengthInPixels * 0.8); endPx = selEX + labelWidth; oRun = endPx - imageWidth + selOffsetX; if (oRun > 0) selEx -= oRun; /* Adjust label location */ if (selEY <= 1.5 * fontHeight) textYcoord = selEY + sbHeight + fontHeight; else textYcoord = selEY - sbHeight; textXOffset = round((sbLengthInPixels - getStringWidth(label)) / 2); finalLabelX = selEX + textXOffset; finalLabelY = textYcoord; } if (startsWith(sBStyle, "Solid")) arrowStyle = "headless"; else if (startsWith(sBStyle, "I-Bar")) arrowStyle = "bar"; else if (startsWith(sBStyle, "Open")) arrowStyle = "open"; else if (startsWith(sBStyle, "Filled")) arrowStyle = "filled"; else if (startsWith(sBStyle, "Notched")) arrowStyle = "notched"; else arrowStyle = ""; if (arrowStyle != "") { if (endsWith(sBStyle, "s")) arrowStyle += " double"; if (transparent) arrowStyle += " outline"; arrowStyle += " " + barHThickness; } if (notFancy && transparent && applyOverlays) simpleTransOv = true; else simpleTransOv = false; if (startsWith(selPos, "Under")) { originalBGCol = Color.background; setBackgroundFromColorName(outlineColor); run("Canvas Size...", "width=" + imageWidth + " height=" + imageHeight + " position=Top-Center"); if (!startsWith(overWrite, "New")) rename(stripKnownExtensionFromString(unCleanLabel(activeImage)) + "_exp"); Color.setBackground(originalBGCol); } if (!notFancy || simpleTransOv) { /* Create new image that will be used to create bar/label */ newImage("label_mask", "8-bit black", imageWidth, imageHeight, 1); setColor(255, 255, 255); /* Although text should overlap any bar we write it first here so that we can also create a separate text mask to use later */ if (!noText) { tempID = getImageID; newImage("text_mask", "8-bit black", imageWidth, imageHeight, 1); writeLabel7(fontName, fontSize, "white", label, finalLabelX, finalLabelY, false); if (!is("binary")) { setThreshold(0, 128); setOption("BlackBackground", false); run("Convert to Mask"); } run("Select None"); selectImage(tempID); } if (sBStyle == "Solid Bar" && selEType != 5) fillRect(selEX, selEY, sbLengthInPixels, sbHeight); /* Rectangle drawn to produce thicker bar */ else { if (selEType != 5) makeArrow(selEX, selEY, selEX + sbLengthInPixels, selEY, arrowStyle); else makeArrow(selLX[0], selLY[0], selLX[1], selLY[1], arrowStyle); /* Line location is as drawn (no offsets) */ Roi.setStrokeColor("white"); Roi.setStrokeWidth(sbHeight / 2); run("Add Selection..."); Overlay.flatten; run("8-bit"); closeImageByTitle("label_mask"); rename("label_mask"); } setThreshold(0, 128); setOption("BlackBackground", false); run("Convert to Mask"); run("Select None"); newImage("outline_template", "8-bit black", imageWidth, imageHeight, 1); getSelectionFromMask("label_mask"); run("Enlarge...", "enlarge=" + outlineStroke + " pixel"); run("Invert"); getSelectionFromMask("label_mask"); run("Invert"); run("Select None"); run("Convert to Mask"); /* Now create outline around text in case of overlap */ if (!noText) { newImage("outline_text", "8-bit black", imageWidth, imageHeight, 1); // if (!is("binary")) run("Convert to Mask"); getSelectionFromMask("text_mask"); safeColornameFill("white"); run("Enlarge...", "enlarge=" + outlineStroke + " pixel"); run("Invert"); run("Select None"); run("Convert to Mask"); imageCalculator("Max", "outline_template", "outline_text"); imageCalculator("XOR create", "outline_template", "label_mask"); selectWindow("Result of outline_template"); rename("outline_filled"); imageCalculator("OR", "label_mask", "outline_text"); selectWindow("outline_filled"); getSelectionFromMask("text_mask"); run("Enlarge...", "enlarge=" + outlineStroke + " pixel"); safeColornameFill("white"); run("Select None"); selectWindow("label_mask"); getSelectionFromMask("text_mask"); safeColornameFill("white"); selectWindow("outline_template"); if (is("Inverting LUT")) run("Invert LUT"); o_tBG = getPixel(0, 0); getSelectionFromMask("label_mask"); if (o_tBG == 0) safeColornameFill("black"); else safeColornameFill("white"); run("Select None"); } else { newImage("outline_filled", "8-bit black", imageWidth, imageHeight, 1); getSelectionFromMask("label_mask"); run("Enlarge...", "enlarge=" + outlineStroke + " pixel"); safeColornameFill("white"); } /* If Overlay chosen add fancy scale bar as overlay */ if (endsWith(overWrite, "verlays")) { /* Create shadow and outline selection masks to be used for overlay components */ scaleBarColorHex = getHexColorFromColorName(scaleBarColor); outlineColorHex = getHexColorFromColorName(outlineColor); if (!noShadow && isOpen("label_mask")) { /* Create ovShadowMask */ selectWindow("label_mask"); run("Select None"); run("Duplicate...", "title=ovShadowMask"); getSelectionFromMask("label_mask"); getSelectionBounds(xShad, yShad, wShad, hShad); setSelectionLocation(xShad + shadowDisp, yShad + shadowDrop); dilation = outlineStroke + maxOf(1, round(shadowBlur / 2)); run("Enlarge...", "enlarge=" + dilation + " pixel"); setBackgroundFromColorName("white"); run("Clear", "slice"); if (transparent) { getSelectionFromMask("label_mask"); setBackgroundFromColorName("black"); run("Clear", "slice"); } run("Select None"); } /* shadow and outline selection masks have now been created */ selectImage(activeImageID); for (sl = startSliceNumber; sl < endSlice + 1; sl++) { setSlice(sl); if (allSlices) sl = 0; if (!noShadow && isOpen("ovShadowMask")) { getSelectionFromMask("ovShadowMask"); shadowHex = "#" + "" + String.pad(toHex(255 * ovShadowOpacity / 100), 2) + "000000"; setSelectionName("Scale bar shadow"); run("Add Selection...", "fill=" + shadowHex); } if (isOpen("outline_template")) { getSelectionFromMask("outline_template"); wait(10); getSelectionBounds(gSelX, gSelY, gWidth, gHeight); if (diagnostics) IJ.log("Outline bounds: " + gSelX + ", " + gSelY + ", " + gWidth + ", " + gHeight); if (gSelX == 0 && gSelY == 0 && gWidth == Image.width && gHeight == Image.height) run("Make Inverse"); setSelectionName("Scale bar outline " + outlineColor); run("Add Selection...", "fill=" + outlineColorHex); } /* alignment of overlay drawn text varies with font so the label_mask is reused instead of redrawing the text directly */ if (!transparent && isOpen("label_mask")) { getSelectionFromMask("label_mask"); setSelectionName("Scale label " + scaleBarColor); run("Add Selection...", "fill=" + scaleBarColorHex); Overlay.setPosition(sl); run("Select None"); if (allSlices) sl = endSlice + 1; } } run("Select None"); closeImageByTitle("ovShadowMask"); } /* End overlay + (!(startsWith(fancyStyle, "No")) fancy scale bar section */ else { /* Create shadow and outline selection masks to be used for bitmap components */ if (!noShadow) createShadowDropFromMask7Safe("label_mask", shadowDrop, shadowDisp, shadowBlur, shadowDarkness, outlineStroke); if (startsWith(overWrite, "Add to image")) tS = activeImage; selectImage(activeImageID); if (slices == 1 && channels > 1) { /* process channels instead of slices */ labelChannels = true; startSliceNumber = 1; endSlice = channels; } else labelChannels = false; for (sl = startSliceNumber; sl < endSlice + 1; sl++) { if (labelChannels) Stack.setChannel(sl); else setSlice(sl); run("Select None"); if (!noShadow && shadowDarkness != 0) { if (shadowDarkness > 0) imageCalculator("Subtract", tS, "shadow"); else imageCalculator("Add", tS, "shadow"); } run("Select None"); if (!noOutline) { /* apply outline around label */ if (isOpen("outline_filled")) { if (!transparent) getSelectionFromMask("outline_filled"); else getSelectionFromMask("outline_template"); } if (selectionType >= 0) { setBackgroundFromColorName(outlineColor); run("Clear", "slice"); if (fontSize >= 12 && !applyOverlays) { run("Enlarge...", "enlarge=1 pixel"); run("Gaussian Blur...", "sigma=0.55"); run("Convolve...", "text1=[-0.0556 -0.0556 -0.0556 \n-0.0556 1.4448 -0.0556 \n-0.0556 -0.0556 -0.0556] slice"); /* moderate sharpen */ } run("Select None"); } } /* color label */ // setColor("red"); if (!transparent) { setColorFromColorName(scaleBarColor); if (!noText) writeLabel7(fontName, fontSize, scaleBarColor, label, finalLabelX, finalLabelY, true); if (sBStyle == "Solid Bar" && selEType != 5) fillRect(selEX, selEY, sbLengthInPixels, sbHeight); /* Rectangle drawn to produce thicker bar */ else { if (selEType != 5) makeArrow(selEX, selEY, selEX + sbLengthInPixels, selEY, arrowStyle); else makeArrow(selLX[0], selLY[0], selLX[1], selLY[1], arrowStyle); /* Line location is as drawn (no offsets) */ if (sBStyle == "Solid Bar") Roi.setStrokeWidth(sbHeight); else Roi.setStrokeWidth(sbHeight / 2); run("Fill"); /* safeColornameFill does not work here - fill only one arrow head! */ } run("Select None"); if (!noText && (imageDepth == 16 || imageDepth == 32)) writeLabel7(fontName, fontSize, scaleBarColor, label, finalLabelX, finalLabelY, true); /* force anti-aliasing */ if (!noText) { getSelectionFromMask("outline_text"); safeColornameFill(outlineColor); run("Select None"); } } if (raised || recessed) { outlineRGBs = getColorArrayFromColorName(outlineColor); outlineLuminosity = (outlineRGBs[0] * 0.299 + outlineRGBs[1] * 0.587 + outlineRGBs[2] * 0.114) / 256; /* http://en.wikipedia.org/wiki/YUV */ scaleBarRGBs = getColorArrayFromColorName(scaleBarColor); scaleBarLuminosity = (scaleBarRGBs[0] * 0.299 + scaleBarRGBs[1] * 0.587 + scaleBarRGBs[2] * 0.114) / 256; if (outlineLuminosity > scaleBarLuminosity) { if (raised && !recessed) { raised = false; recessed = true; } else if (!raised && recessed) { raised = true; recessed = false; } } fontLineWidth = getStringWidth("!"); rAlpha = fontLineWidth / 40; if (raised) { getSelectionFromMask("label_mask"); if (!noOutline) run("Enlarge...", "enlarge=1 pixel"); run("Convolve...", "text1=[ " + createConvolverMatrix("raised", fontLineWidth) + " ] slice"); if (rAlpha > 0.33) run("Gaussian Blur...", "sigma=" + rAlpha); run("Select None"); } if (recessed) { getSelectionFromMask("label_mask"); if (!noOutline && !raised) run("Enlarge...", "enlarge=1 pixel"); run("Enlarge...", "enlarge=1 pixel"); run("Convolve...", "text1=[ " + createConvolverMatrix("recessed", fontLineWidth) + " ] slice"); if (rAlpha > 0.33) run("Gaussian Blur...", "sigma=" + rAlpha); run("Select None"); } } } } tempTitles = newArray("shadow", "label_mask", "text_mask", "outline_template", "outline_text", "outline_filled", "outline_only_template"); if (!diagnostics) for (i = 0; i < lengthOf(tempTitles); i++) closeImageByTitle(tempTitles[i]); } else { if (!transparent) { selectImage(activeImageID); scaleBarColorHex = getHexColorFromColorName(scaleBarColor); setColor(scaleBarColorHex); if (applyOverlays) finalLabelY -= fontSize / 5; for (sl = startSliceNumber; sl < endSlice + 1; sl++) { setSlice(sl); if (allSlices) sl = 0; if (applyOverlays) { /* If Overlay chosen add fancy scale bar as overlay */ if (!noText) Overlay.drawString(label, finalLabelX, finalLabelY); Overlay.show; if (selEType != 5) makeArrow(selEX, selEY, selEX + sbLengthInPixels, selEY, arrowStyle); else makeArrow(selLX[0], selLY[0], selLX[1], selLY[1], arrowStyle); /* Line location is as drawn (no offsets) */ Roi.setStrokeColor(scaleBarColorHex); if (sBStyle == "Solid Bar") Roi.setStrokeWidth(sbHeight); else Roi.setStrokeWidth(sbHeight / 2); setSelectionName("Scale label " + scaleBarColor); run("Add Selection...", "fill=" + scaleBarColorHex); Overlay.setPosition(sl); /* Sets the stack position (slice number) */ Overlay.show; run("Select None"); } else { if (sBStyle == "Solid Bar" && selEType != 5) fillRect(selEX, selEY, sbLengthInPixels, sbHeight); /* Rectangle drawn to produce thicker bar */ else { if (selEType != 5) makeArrow(selEX, selEY, selEX + sbLengthInPixels, selEY, arrowStyle); else makeArrow(selLX[0], selLY[0], selLX[1], selLY[1], arrowStyle); /* Line location is as drawn (no offsets) */ if (sBStyle == "Solid Bar") Roi.setStrokeWidth(sbHeight); else Roi.setStrokeWidth(sbHeight / 2); run("Fill"); } if (!noText) writeLabel7(fontName, fontSize, scaleBarColor, label, finalLabelX, finalLabelY, true); run("Select None"); } if (allSlices) sl = endSlice + 1; } } /* End simple-Text fancy scale bar section */ } restoreSettings(); if (selEType == 5) { /* Restore original line to original image */ selectImage(orImageID); makeLine(selLX[0], selLY[0], selLX[1], selLY[1]); selectImage(activeImageID); } setSlice(startSliceNumber); if (saveJPEG) safeSaveAndClose("jpeg", imageDir, tS, false); if (saveTIFF) safeSaveAndClose("tiff", imageDir, tS, false); setBatchMode("exit & display"); /* exit batch mode */ if (applyOverlays) Overlay.selectable(true); beep(); beep(); beep(); call("java.lang.System.gc"); showStatus("Fancy Scale Bar Added", "flash image green"); } /* "Fast'nFancy Scale Bar Rerun" macro had been placed here but has not been updated yet to the new features in the the Fancy Scale Bar macro v210621 */ macro "Fancy Slice Labels" { /* This macro adds multiple lines of text to a copy of the image. Peter J. Lee Applied Superconductivity Center at National High Magnetic Field Laboratory. ANSI encoded for Windows. Slices can be named with sequential numbers using imageJ's "stack sorter" Non-formated slice labels can be applied with more variables and previews to images using ImageJ's "Label Stacks" and Dan White's (MPI-CBG) "Series Labeler", so you might want to try that more sophisticated programming first. v180629 First version based on v180628 of the Fancy Text Label macro. v181018 First working version >:-} . v190415 Adds option to update embedded slice labels. v190627 Skips slices without labels rather than clearing them :-$ . Also Function updates. v190628 Adds options to add prefixes, suffixes and a counter as well as replacing strings in slice labels. Minor fixes. + v200707 Changed imageDepth variable name added macro label. + v210316-v210325 Changed toChar function so shortcuts (i.e. "pi") only converted to symbols if followed by a space. + v210503 Split menu options so that auto-generation menu is simpler + v211022 Updated color choices v220310-11 Added warning if some slices had no label (TIF-lzw does not store labels). f2: updated pad function f3: updated colors + v220727 Minor format changes f1-f3: updated colors + v230418-9: Simplified. Replaced inner shadow with raised/recessed. Font autoshrunk per slice if text overflows margins. Centers adjusted per slice. + v240709: Updated color selection. + v250121: Added not-fancy formatting option (much quicker and uses anti-aliasing). Fixed some formatting issues. */ macroL = "Fancy_Slice_Labels_v250121.ijm"; if (nImages < 1) exit("This macro \(" + macroL + "\) requires at least one image to be open"); requires("1.47r"); saveSettings; if (selectionType >= 0) { selEType = selectionType; selectionExists = true; getSelectionBounds(orSelEX, orSelEY, orSelEWidth, orSelEHeight); baseMenuHeight = 587; } else { selectionExists = false; baseMenuHeight = 460; } originalImage = getTitle(); /* Set options for black objects on white background as this works better for publications */ run("Options...", "iterations=1 white count=1"); /* Set the background to white */ run("Colors...", "foreground=black background=white selection=yellow"); /* Set the preferred colors for these macros */ setOption("BlackBackground", false); run("Appearance...", " "); /* do not use Inverting LUT * /* Check to see if a Ramp legend rather than the image has been selected by accident */ if (matches(originalImage, ".*Ramp.*") == 1) showMessageWithCancel("Title contains \"Ramp\"", "Do you want to label" + originalImage + " ?"); getDimensions(imageWidth, imageHeight, channels, slices, frames); sH = screenHeight(); sW = screenWidth(); menuRowHeight = 29; maxMenuSlices = floor((sH - baseMenuHeight) / menuRowHeight); maxLabelString = imageWidth / 10; /* Assumes a minimum font character width of 5 */ startSliceNumber = getSliceNumber(); remSlices = nSlices - startSliceNumber; allSliceLabels = newArray(); for (i = 0, emptyLabelN = 0, maxString = 0; i < nSlices; i++) { setSlice(i + 1); allSliceLabels[i] = getInfo("slice.label"); if (allSliceLabels[i] == "") emptyLabelN++; else { stringL = lengthOf(allSliceLabels[i]); if (stringL > maxString) maxString = stringL; } } maxLabelString = minOf(maxLabelString, maxString); setSlice(startSliceNumber); imageDims = imageHeight + imageWidth; imageDepth = bitDepth(); if (imageDepth == 16) { Dialog.create("Bit depth conversion"); Dialog.addMessage("Sorry, this macro does not work well with 16-bit images./nBut perhaps a labeled 16-bit image is unnecessary?"); conversionChoice = newArray("RGB Color", "8-bit Gray", "Exit"); Dialog.addRadioButtonGroup("Choose:", conversionChoice, 3, 1, "8-bit Gray"); Dialog.show(); convertTo = Dialog.getRadioButton(); if (convertTo == "8-bit Gray") run("8-bit"); else if (convertTo == "RGB Color") run("RGB Color"); else restoreExit("Goodbye"); } imageDepth = bitDepth(); id = getImageID(); maxFontW = imageWidth / (1.2 * maxLabelString); maxFontH = imageHeight / (1.2 * getValue("font.height")); fontSize = floor(minOf(maxFontW, maxFontH)); /* default font size */ if (fontSize < 12) fontSize = maxOf(10, fontSize); /* set minimum default font size as 10 */ defScaleBarFS = maxOf(12, round((minOf(imageHeight, imageWidth)) / 30)); /* used as information in the 1st options dialog */ setFont("", fontSize, "bold antialiased"); fontHeight = getValue("font.height"); lineSpacing = 1.1; outlineStrokePx = maxOf(1, fontHeight / 20); /* default outline stroke: % of font size */ shadowDrop = 10; /* default outer shadow drop: % of font size */ shadowDisp = shadowDrop; shadowBlur = floor(0.6 * shadowDrop); shadowDarkness = 40; offsetX = round(1 + imageWidth / 150); /* default offset of label from edge */ offsetY = round(1 + imageHeight / 150); /* default offset of label from edge */ /* Then Dialog . . . */ Dialog.create("Label Format and Edit Options: " + macroL); Dialog.addNumber("Start slice number:", startSliceNumber, 0, 4, "#"); Dialog.addNumber("End slice number:", nSlices, 0, 4, "#"); if (emptyLabelN > 0) Dialog.addMessage("Warning: " + emptyLabelN + " slices had no label", 12, "red"); if (selectionExists == 1) { textLocChoices = newArray("Top Left", "Top Right", "Top Center", "Center", "Bottom Left", "Bottom Center", "Bottom Right", "Center of New Selection", "Center of Selection"); iLoc = indexOfArray(textLocChoices, call("ij.Prefs.get", "fancy.sliceLabels.location", textLocChoices[6]), 6); } else { textLocChoices = newArray("Top Left", "Top Right", "Top Center", "Center", "Bottom Left", "Bottom Center", "Bottom Right", "Center of New Selection"); iLoc = indexOfArray(textLocChoices, call("ij.Prefs.get", "fancy.sliceLabels.location", textLocChoices[0]), 0); } Dialog.addChoice("Location of Label:", textLocChoices, textLocChoices[iLoc]); textJustChoices = newArray("auto", "left", "center", "right"); if (selectionExists == 1) { Dialog.addNumber("Original selection X start = ", orSelEX); Dialog.addNumber("Original selection Y start = ", orSelEY); Dialog.addNumber("Original selection width = ", orSelEWidth); Dialog.addNumber("Original selection height = ", orSelEHeight); Dialog.addCheckbox("Restore this selection at macro completion?", true); if (orSelEX < imageWidth * 0.4) just = "left"; else if (orSelEX > imageWidth * 0.6) just = "right"; else just = "center"; Dialog.addChoice("Text justification, Auto = " + just, textJustChoices, textJustChoices[0]); } else { restoreSelection = false; Dialog.addChoice("Text justification", textJustChoices, textJustChoices[0]); } Dialog.addNumber("Font size:", fontSize, 0, 4, "\(default fancy scalebar font size is " + defScaleBarFS + "\)"); fancyStyleEffectsOptions = newArray("No shadows", "No outline", "Raised", "Recessed"); fancyStyleEffectsDefaults = newArray(false, false, false, false); fancyStyleEffectsPrefs = call("ij.Prefs.get", "fancy.sliceLabels.fancyStyleEffects", "not found"); if (fancyStyleEffectsPrefs != "not found") { fancyStyleEffects = split(fancyStyleEffectsPrefs, "|"); if (fancyStyleEffects.length == fancyStyleEffectsOptions.length) fancyStyleEffectsDefaults = fancyStyleEffects; } Dialog.setInsets(10, 20, 0); Dialog.addCheckboxGroup(1, fancyStyleEffectsOptions.length, fancyStyleEffectsOptions, fancyStyleEffectsDefaults); if (fancyStyleEffectsPrefs == "|1|1|0|0") notFancy = true; else notFancy = false; Dialog.addCheckbox("No fancy text formatting \(overrides above\)", notFancy); grayChoices = newArray("white", "black", "off-white", "off-black", "lightGray", "gray", "darkGray"); colorChoicesStd = newArray("red", "green", "blue", "cyan", "magenta", "yellow", "pink", "orange", "violet"); colorChoicesMaterials = newArray("bronze", "antique_bronze", "brass", "dull_brass", "brick", "chrome", "copper", "aged_copper", "dusky_copper", "light_copper", "garnet", "burnished_gold", "gold", "slate_gray", "titanium", "vault_garnet", "plaza_brick", "vault_gold"); colorChoicesMod = newArray("aqua_modern", "blue_accent_modern", "blue_dark_modern", "blue_modern", "blue_honolulu", "gray_modern", "green_dark_modern", "green_modern", "green_modern_accent", "green_spring_accent", "orange_modern", "pink_modern", "purple_modern", "red_modern", "tan_modern", "violet_modern", "yellow_modern"); colorChoicesNeon = newArray("jazzberry_jam", "radical_red", "wild_watermelon", "outrageous_orange", "supernova_orange", "atomic_tangerine", "neon_carrot", "sunglow", "laser_lemon", "electric_lime", "screamin'_green", "magic_mint", "blizzard_blue", "dodger_blue", "shocking_pink", "razzle_dazzle_rose", "hot_magenta"); colorChoicesFSU = newArray("stadium_night", "westcott_water", "legacy_blue"); /* Materials colors moved to Materials set */ if (imageDepth == 24) { colorChoices = Array.concat(grayChoices, colorChoicesStd, colorChoicesMaterials, colorChoicesMod, colorChoicesNeon, colorChoicesFSU); iTC = indexOfArray(colorChoices, call("ij.Prefs.get", "fancy.sliceLabels.text.color", colorChoices[0]), 0); iOC = indexOfArray(colorChoices, call("ij.Prefs.get", "fancy.sliceLabels.outline.color", colorChoices[1]), 1); } else { colorChoices = grayChoices; iTC = indexOfArray(grayChoices, call("ij.Prefs.get", "fancy.sliceLabels.text.gray", grayChoices[0]), 0); iOC = indexOfArray(grayChoices, call("ij.Prefs.get", "fancy.sliceLabels.outline.gray", grayChoices[1]), 1); } Dialog.addChoice("Text color:", colorChoices, colorChoices[iTC]); fontNameChoices = getFontChoiceList(); Dialog.addChoice("Font name:", fontNameChoices, fontNameChoices[0]); Dialog.addChoice("Outline (background) color:", colorChoices, colorChoices[iOC]); Dialog.addNumber("Outline stroke:", outlineStrokePx, 0, 3, "pixels"); Dialog.addCheckbox("Tweak the Formatting?", false); Dialog.addCheckbox("Destructive overwrite \(ignored if only renaming slices\)?", false); if (emptyLabelN == 0) Dialog.addCheckbox("Auto-generate all labels as a sequence only? Existing labels will not be used", false); else if (emptyLabelN == slices) Dialog.addCheckbox("Auto-generate all labels as a sequence only?", true); else Dialog.addCheckbox("Auto-generate all labels as a sequence only \(no editing options given\)?", false); Dialog.addCheckbox("Diagnostic mode:", false); Dialog.show(); startSliceNumber = Dialog.getNumber(); endSliceNumber = Dialog.getNumber(); textLocChoice = Dialog.getChoice(); call("ij.Prefs.set", "fancy.sliceLabels.location", textLocChoice); if (selectionExists == 1) { selEX = Dialog.getNumber(); /* Allows user to tweak pre-selection using dialog boxes */ selEY = Dialog.getNumber(); selEWidth = Dialog.getNumber(); selEHeight = Dialog.getNumber(); restoreSelection = Dialog.getCheckbox(); } just = Dialog.getChoice(); fontSize = Dialog.getNumber(); /* fancy style effects checkbox group order: "No shadows", "Raised", "Recessed" */ fancyStyleEffectsString = ""; noShadow = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(noShadow, 0); noOutline = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(noOutline, 0); raised = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(raised, 0); recessed = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(recessed, 0); notFancy = Dialog.getCheckbox(); if (notFancy) fancyStyleEffectsString = "|1|1|0|0"; call("ij.Prefs.set", "fancy.sliceLabels.fancyStyleEffects", fancyStyleEffectsString); /* End of checkbox group */ textColor = Dialog.getChoice(); if (imageDepth == 24) call("ij.Prefs.set", "fancy.sliceLabels.text.color", textColor); else call("ij.Prefs.set", "fancy.sliceLabels.text.gray", textColor); fontName = Dialog.getChoice(); call("ij.Prefs.set", "fancy.sliceLabels.fontName", fontName); outlineColor = Dialog.getChoice(); if (imageDepth == 24) call("ij.Prefs.set", "fancy.sliceLabels.outline.color", outlineColor); else call("ij.Prefs.set", "fancy.sliceLabels.outline.gray", outlineColor); outlineStrokePx = Dialog.getNumber(); tweakFormat = Dialog.getCheckbox(); overWrite = Dialog.getCheckbox(); autoGenerate = Dialog.getCheckbox(); diagnostics = Dialog.getCheckbox(); if (!diagnostics) setBatchMode(true); else IJ.log("fancyStyleEffectsString: " + fancyStyleEffectsString + "\nFont Size: " + fontSize); setFont(fontName, fontSize); fontHeight = getValue("font.height"); if (diagnostics) IJ.log("font height: " + fontHeight); if (noOutline) outlineStrokePx = 0; insideMarginsLR = imageWidth - 2 * offsetX; longestStringWidth = 0; /* reset longest string width for modified versions */ if (autoGenerate) { Dialog.create("Label \(Autogenerated\) Options:"); Dialog.addMessage("\"^2\" & \"um\" etc. replaced by " + fromCharCode(178) + " & " + fromCharCode(181) + "m etc. if followed by a space."); labelChoices = newArray("Add labels only", "Rename slices only", "Label & rename"); // Dialog.addNumber("First slice to label:", getSliceNumber,0,4,""), // Dialog.addNumber("Last slice to label:", nSlices,0,4,""), Dialog.addChoice("Label and/or rename slices:", labelChoices, "Add labels only"); Dialog.addString("Prefix text", "", minOf(20, maxLabelString)); Dialog.addString("Suffix text", "", minOf(20, maxLabelString)); Dialog.addNumber("Counter start", 0, 0, 5, ""); Dialog.addToSameRow(); Dialog.addNumber("Counter increment", 1, 0, 7, ""); Dialog.addNumber("Counter label decimal places", 0, 0, 2, ""); Dialog.addToSameRow(); Dialog.addString("Counter separation symbols: ", "-", 3); countPosChoices = newArray("None", "Before prefix", "After prefix", "Before suffix", "After suffix"); Dialog.addRadioButtonGroup("Counter position: ", countPosChoices, 1, 4, countPosChoices[0]); Dialog.setInsets(5, 0, 10); Dialog.show(); // startSlice = Dialog.getNumber; // endSlice = Dialog.getNumber; // sliceCount = endSlice-startSlice+1; labelChoice = Dialog.getChoice(); prefix = Dialog.getString; suffix = Dialog.getString; startN = Dialog.getNumber; addN = Dialog.getNumber; decP = Dialog.getNumber; cSep = Dialog.getString; countPos = Dialog.getRadioButton; sliceTextLabels = newArray(); for (i = 0; i < endSliceNumber; i++) { if (countPos == "None") sliceTextLabels[i] = prefix + suffix; else { ctr = d2s(startN + i * addN, decP); if (countPos == "Before prefix") sliceTextLabels[i] = "" + ctr + cSep + prefix + suffix; else if (countPos == "After prefix") sliceTextLabels[i] = prefix + cSep + ctr + cSep + suffix; else if (countPos == "Before suffix") sliceTextLabels[i] = prefix + cSep + ctr + cSep + suffix; else sliceTextLabels[i] = prefix + suffix + cSep + ctr; } if (labelChoice != "Add labels only") { setSlice(startSliceNumber + i); newLabel = sliceTextLabels[i]; /* symbols are not converted for slice names */ run("Set Label...", "label=&newLabel"); } sliceTextLabels[i] = "" + toChar(sliceTextLabels[i]); /* Use degree symbol */ sliceTextLabels[i] = "" + cleanLabel(sliceTextLabels[i]); stringLength = getStringWidth(sliceTextLabels[i]); if (stringLength > longestStringWidth) longestStringWidth = minOf(insideMarginsLR, stringLength); } } else { Dialog.create("Label Text Options: " + macroL); sliceLabelDialogLimit = minOf(maxMenuSlices, remSlices + 1); Dialog.addMessage("\"^2\" & \"um\" etc. replaced by " + fromCharCode(178) + " & " + fromCharCode(181) + "m etc. if followed by a space.\nThe number of slices to be labeled is limited to " + maxMenuSlices + " by screen height.\nAdditional slices can be labeled by repeating this macro from first unlabeled slice."); labelChoices = newArray("Add labels only", "Rename slices only", "Label & rename"); Dialog.addChoice("Label and/or rename slices:", labelChoices, "Add labels only"); Dialog.addString("Prefix text", "", minOf(20, maxLabelString)); Dialog.addString("Suffix text", "", minOf(20, maxLabelString)); Dialog.addNumber("Counter start", 0, 0, 5, ""); Dialog.addToSameRow(); Dialog.addNumber("Counter increment", 1, 0, 7, ""); Dialog.addNumber("Counter label decimal places", 0, 0, 2, ""); Dialog.addToSameRow(); Dialog.addString("Counter separation symbols: ", "-", 3); countPosChoices = newArray("None", "Before prefix", "After prefix", "Before suffix", "After suffix"); Dialog.addRadioButtonGroup("Counter position: ", countPosChoices, 1, 4, countPosChoices[0]); if (maxLabelString > 0) { Dialog.addString("Replace this label text:", "", minOf(20, maxLabelString)); Dialog.addString(" . . . with \(escape regEx characters\):", "", minOf(20, maxLabelString)); } Dialog.setInsets(5, 0, 10); for (i = 0; i < (minOf(endSliceNumber, sliceLabelDialogLimit)); i++) Dialog.addString("Slice No. " + (i + startSliceNumber) + " input label:", allSliceLabels[i + startSliceNumber - 1], minOf(100, maxLabelString)); Dialog.show(); labelChoice = Dialog.getChoice(); prefix = Dialog.getString; suffix = Dialog.getString; startN = Dialog.getNumber; addN = Dialog.getNumber; decP = Dialog.getNumber; cSep = Dialog.getString; countPos = Dialog.getRadioButton; replaceString = false; if (maxLabelString > 0) { oldString = Dialog.getString; newString = Dialog.getString; if (lengthOf(oldString) > 0) replaceString = true; } sliceTextLabels = newArray(); longestStringWidth = 0; /* reset longest string width for modified versions */ for (i = 0; i < (minOf(endSliceNumber, sliceLabelDialogLimit)); i++) { sLabel = Dialog.getString(); if (replaceString) { sLabel = replace(sLabel, oldString, newString); } if (countPos == "None") sliceTextLabels[i] = prefix + sLabel + suffix; else { ctr = d2s(startN + i * addN, decP); if (countPos == "Before prefix") sliceTextLabels[i] = "" + ctr + cSep + prefix + sLabel + suffix; else if (countPos == "After prefix") sliceTextLabels[i] = prefix + cSep + ctr + cSep + sLabel + suffix; else if (countPos == "Before suffix") sliceTextLabels[i] = prefix + sLabel + cSep + ctr + cSep + suffix; else sliceTextLabels[i] = prefix + sLabel + suffix + cSep + ctr; } if (labelChoice != "Add labels only") { setSlice(startSliceNumber + i); newLabel = sliceTextLabels[i]; /* symbols are not converted for slice names */ run("Set Label...", "label=&newLabel"); } sliceTextLabels[i] = "" + toChar(sliceTextLabels[i]); /* Use degree symbol */ sliceTextLabels[i] = "" + cleanLabel(sliceTextLabels[i]); stringLength = getStringWidth(sliceTextLabels[i]); if (stringLength > longestStringWidth) longestStringWidth = stringLength; } } /* End of options dialog */ /* Begin labeling of slices */ if (labelChoice != "Rename slices only") { if (tweakFormat && (!noShadow && !noOutline)) { Dialog.create("Advanced Formatting Options"); if (!notOutline) { Dialog.addNumber("Outline stroke:", outlineStrokePx, 0, 3, "pixels"); Dialog.addChoice("Outline (background) color:", colorChoices, colorChoices[1]); } if (!noShadow) { Dialog.addNumber("Shadow Drop: ?", shadowDrop, 0, 3, "% of font size"); Dialog.addNumber("Shadow Displacement Right: ?", shadowDrop, 0, 3, "% of font size"); Dialog.addNumber("Shadow Gaussian blur:", floor(0.4 * shadowDrop), 0, 3, "% of font size"); Dialog.addNumber("Shadow Darkness:", 100, 0, 3, "%\(darkest = 100%\)"); } Dialog.show(); if (!notOutline) { outlineStrokePx = Dialog.getNumber(); outlineColor = Dialog.getChoice(); } if (!noShadow) { shadowDrop = Dialog.getNumber(); shadowDisp = Dialog.getNumber(); shadowBlur = Dialog.getNumber(); shadowDarkness = Dialog.getNumber(); } } textColorArray = getColorArrayFromColorName(textColor); Array.getStatistics(textColorArray, fontIntMean); fontInt = floor(fontIntMean); outlineColorArray = getColorArrayFromColorName(outlineColor); Array.getStatistics(outlineColorArray, outlineIntMean); outlineInt = floor(outlineIntMean); negAdj = 0.5; /* negative offsets appear exaggerated at full displacement */ if (!noShadow) { if (shadowDrop < 0) shadowDrop *= negAdj; if (shadowDisp < 0) shadowDisp *= negAdj; if (shadowBlur < 0) shadowBlur *= negAdj; } fontFactor = fontSize / 100; if (outlineStrokePx != 0) outlineStrokePx = maxOf(1, round(fontFactor * outlineStrokePx)); /* if some outline is desired set to at least one pixel */ if (!noShadow) { shadowDrop = round(fontFactor * shadowDrop); shadowDisp = round(fontFactor * shadowDisp); shadowBlur = round(fontFactor * shadowBlur); if (offsetX < (shadowDisp + shadowBlur + 1)) offsetX = (shadowDisp + shadowBlur + 1); /* make sure shadow does not run off edge of image */ if (offsetY < (shadowDrop + shadowBlur + 1)) offsetY = (shadowDrop + shadowBlur + 1); } if (textLocChoice == "Top Left") { selEX = offsetX; selEY = offsetY; if (just == "auto") just = "left"; } else if (textLocChoice == "Top Right") { selEX = imageWidth - longestStringWidth - offsetX; selEY = offsetY; if (just == "auto") just = "right"; } else if (textLocChoice == "Top Center") { selEX = round((imageWidth - longestStringWidth) / 2); selEY = offsetY; if (just == "auto") just = "center"; } else if (textLocChoice == "Center") { selEX = round((imageWidth - longestStringWidth) / 2); selEY = round(imageHeight / 2 + fontHeight); if (just == "auto") just = "center"; } else if (textLocChoice == "Bottom Left") { selEX = offsetX; selEY = imageHeight - offsetY; if (just == "auto") just = "left"; } else if (textLocChoice == "Bottom Right") { selEX = imageWidth - longestStringWidth - offsetX; selEY = imageHeight - offsetY; if (just == "auto") just = "right"; } else if (textLocChoice == "Bottom Center") { selEX = round((imageWidth - longestStringWidth) / 2); selEY = imageHeight - offsetY; if (just == "auto") just = "center"; } else if (textLocChoice == "Center of New Selection") { if (is("Batch Mode") == true) setBatchMode(false); /* Does not accept interaction while batch mode is on */ setTool("rectangle"); msgtitle = "Location for the text labels..."; msg = "Draw a box in the image where you want to center the text labels..."; waitForUser(msgtitle, msg); getSelectionBounds(orSelEX, orSelEY, orSelEWidth, orSelEHeight); /* this set for restore */ restoreSelection = getBoolean("Restore this selection at the end of the macro?"); if (is("Batch Mode") == false) setBatchMode(true); /* toggle batch mode back on */ getSelectionBounds(selEX, selEY, selEWidth, selEHeight); /* this set to change */ } if (endsWith(textLocChoice, "election")) { shrinkX = minOf(1, selEWidth / longestStringWidth); shrinkY = minOf(1, selEHeight / fontHeight); shrinkF = minOf(shrinkX, shrinkY); shrunkFont = shrinkF * fontSize; if (shrinkF < 1) { Dialog.create("Shrink Text"); Dialog.addCheckbox("Text will not fit inside selection; Reduce font size from " + fontSize + "?", true); Dialog.addNumber("Choose new font size; font size for fit =", round(shrunkFont)); Dialog.show; reduceFontSize = Dialog.getCheckbox(); shrunkFont = Dialog.getNumber(); shrinkF = shrunkFont / fontSize; } else reduceFontSize = false; if (reduceFontSize == true) { fontSize = shrunkFont; setFont("", shrunkFont, ""); fontHeight = getValue("font.height"); linesSpace = shrinkF * linesSpace; longestStringWidth = shrinkF * longestStringWidth; fontFactor = fontSize / 100; if (outlineStrokePx > 1) outlineStrokePx = maxOf(1, round(fontFactor * outlineStrokePx)); else if (outlineStrokePx > 0) outlineStrokePx = round(fontFactor * outlineStrokePx); if (!noShadow) { if (shadowDrop > 1) shadowDrop = maxOf(1, round(fontFactor * shadowDrop)); else shadowDrop = round(fontFactor * shadowDrop); if (shadowDisp > 1) shadowDisp = maxOf(1, round(fontFactor * shadowDisp)); else shadowDisp = round(fontFactor * shadowDisp); if (shadowBlur > 1) shadowBlur = maxOf(1, round(fontFactor * shadowBlur)); else shadowBlur = round(fontFactor * shadowBlur); } } selEX += round((selEWidth / 2) - longestStringWidth / 2); selEY += round((selEHeight / 2) + fontHeight); if (just == "auto") { if (selEX < imageWidth * 0.4) just = "left"; else if (selEX > imageWidth * 0.6) just = "right"; else just = "center"; } } run("Select None"); if (selEY <= 1.5 * fontHeight) selEY += fontHeight; selEX = maxOf(selEX, offsetX); endX = selEX + longestStringWidth; if ((endX + offsetX) > imageWidth) selEX = imageWidth - longestStringWidth - offsetX; roiManager("show none"); if (!overWrite) { if (nSlices == 1) run("Duplicate...", "title=" + getTitle() + "+text"); else run("Duplicate...", "title=" + getTitle() + "+text duplicate"); } workingImage = getTitle(); workingImageName = getInfo("window.title"); outlineColorRGBs = getColorArrayFromColorName(outlineColor); textColorRGBs = getColorArrayFromColorName(textColor); for (i = 0; i < lengthOf(sliceTextLabels); i++) { stringWidth = getStringWidth(sliceTextLabels[i]); overRun = stringWidth / insideMarginsLR; if (overRun > 1) fontSize /= overRun; stringWidth = getStringWidth(sliceTextLabels[i]); textLabelX = selEX; textLabelY = selEY; setSlice(startSliceNumber + i); if (sliceTextLabels[i] != "") { if (notFancy) writeLabel7(fontName, fontSize, textColor, sliceTextLabels[i], textLabelX, textLabelY, true); else { /* Create Label Mask */ newImage("label_mask", "8-bit black", imageWidth, imageHeight, 1); roiManager("deselect"); run("Select None"); if (sliceTextLabels[i] != "-blank-") { if (just == "right") textLabelX += longestStringWidth - stringWidth; else if (just != "left") textLabelX += (longestStringWidth - getStringWidth(sliceTextLabels[i])) / 2; if (indexOf(textLocChoice, "Center") > 0) selEX = textLabelX = round((imageWidth - stringWidth) / 2); else if (indexOf(textLocChoice, "Left") > 0) textLabelX = offsetX; else if (indexOf(textLocChoice, "Right") > 0 && (just == "right" || just == "auto")) textLabelX = imageWidth - (stringWidth + offsetX); writeLabel7(fontName, fontSize, "white", sliceTextLabels[i], textLabelX, textLabelY, false); } selectWindow("label_mask"); setThreshold(0, 128); setOption("BlackBackground", false); run("Convert to Mask"); run("Select None"); // selectWindow("label_mask"); /* Create drop shadow if desired */ if (!noShadow) { if (shadowDrop != 0 || shadowDisp != 0 || shadowBlur != 0) { showStatus("Creating drop shadow for labels . . . "); createShadowDropFromMask7Safe("label_mask", shadowDrop, shadowDisp, shadowBlur, shadowDarkness, outlineStrokePx); if (isOpen("shadow") && (shadowDarkness > 0)) imageCalculator("Subtract", workingImage, "shadow"); else if (isOpen("shadow") && (shadowDarkness < 0)) imageCalculator("Add", workingImage, "shadow"); run("Select None"); } } /* Create outline around text */ if (outlineStrokePx > 0) { selectWindow(workingImage); getSelectionFromMask("label_mask"); run("Enlarge...", "enlarge=&outlineStrokePx pixel"); safeColornameFillClear(outlineColor); run("Enlarge...", "enlarge=1 pixel"); run("Gaussian Blur...", "sigma=0.85"); run("Convolve...", "text1=[-0.0556 -0.0556 -0.0556 \n-0.0556 1.4448 -0.0556 \n-0.0556 -0.0556 -0.0556]"); /* moderate sharpen */ run("Select None"); } /* Create text */ if (sliceTextLabels[i] == "-blank-") { getSelectionFromMask("label_mask"); safeColornameFillClear(textColor); run("Select None"); } else writeLabel7(fontName, fontSize, textColor, sliceTextLabels[i], textLabelX, textLabelY, true); /* Now restore antialiased text */ getSelectionFromMask("label_mask"); run("Enlarge...", "enlarge=1 pixel"); run("Gaussian Blur...", "sigma=0.75"); run("Unsharp Mask...", "radius=1 mask=0.75"); if (raised || recessed) { outlineRGBs = getColorArrayFromColorName(outlineColor); Array.getStatistics(outlineRGBs, null, null, outlineColorMean, null); textRGBs = getColorArrayFromColorName(textColor); Array.getStatistics(textRGBs, null, null, textColorMean, null); if (outlineColorMean > textColorMean) { if (raised && !recessed) { raised = false; recessed = true; } else if (!raised && recessed) { raised = true; recessed = false; } } fontLineWidth = getStringWidth("!"); rAlpha = fontLineWidth / 40; if (raised) { getSelectionFromMask("label_mask"); if (!noOutline) run("Enlarge...", "enlarge=1 pixel"); run("Convolve...", "text1=[ " + createConvolverMatrix("raised", fontLineWidth) + " ]"); if (rAlpha > 0.33) run("Gaussian Blur...", "sigma=&rAlpha"); run("Select None"); } if (recessed) { getSelectionFromMask("label_mask"); if (!noOutline && !raised) run("Enlarge...", "enlarge=1 pixel"); run("Enlarge...", "enlarge=1 pixel"); run("Convolve...", "text1=[ " + createConvolverMatrix("recessed", fontLineWidth) + " ]"); if (rAlpha > 0.33) run("Gaussian Blur...", "sigma=rAlpha"); run("Select None"); } } closeImageByTitle("shadow"); closeImageByTitle("label_mask"); } } } selectWindow(workingImage); if (startsWith(overWrite, "New")) { suffixLoc = lastIndexOf(workingImageName, "."); if (suffixLoc > 0) workingImageNameWOExt = unCleanLabel(substring(workingImageName, 0, suffixLoc)); else workingImageNameWOExt = unCleanLabel(workingImage); rename(workingImageNameWOExt + "+text"); } } restoreSettings; setBatchMode("exit & display"); if (endsWith(textLocChoice, "election") && (restoreSelection == true)) makeRectangle(orSelEX, orSelEY, orSelEWidth, orSelEHeight); else run("Select None"); setSlice(startSliceNumber); showStatus("Fancy Text Labels Finished"); call("java.lang.System.gc"); } macro "Images to Stack Plus" { /* Creates a stack from open images - gives you more control than built-in command. v231024: 1st version, Peter J. Lee Applied Superconductivity Center, Florida State University v231025-6: Added filters, stack alignment and Z-projection, smarter slice names. b: Added morphological filters. v231101: More filters tested and allowed with color stack. Simplified crop but added manual option. Window closing options added. Menu optimized for height. Some Prefs saved. v231102: Default common name added. For images with different sizes the gaps will set to the background color. v231103: Fixed prefs error for sort order. b: Adds manual alignment to line (scale and rotate). Better common name detection. v231106: Added a dialog to allow selection of the rotation and scale options in manual alignment. v231107: Option added to ignore black backgrounds in z-projection average. Auto-alignment now always aligns to top slice correctly. v231107b-11: Added margins option for manual align. v231113: Ignores crop if there is no selection. v231115: Fixed error on common name loop and other issues for <3 tiles. v231116: Manual alignment significantly reworked to incorporate saved lines option. v231117: Auto-alignment of pairs now working with manual ROI lines. Better auto crop. v231127f: Removed "!" from intermediate showStatus commands to allow getLine, makeLine and run("SelectNone") selectWindow to work reliably. v231128c: Simplified by removing non z-stack options. v240130: Added margin option for non-manual alignments. Removed NaN background option for RGB images. */ macroL = "Images_to_Stack_Plus_v240130.ijm"; ascPrefsKey = "asc.ImagesToStackPlus.Prefs."; close("temp_*"); /* Cleanup if crashed last time */ saveSettings(); if (nImages < 2) exit("Need more than one open image to create a stack"); imageList = removeDuplicatesInArray(getList("image.titles"), true); imageN = lengthOf(imageList); minTL = imageList[0].length; for (i = 1; i < imageN; i++) minTL = minOf(minTL, imageList[0].length); /* Determine the minimum title length */ for (i = 1, isCommon = true; i < minTL - 1 && isCommon; i++) { commonName = substring(imageList[0], 0, i + 1); for (j = 0; j < imageN - 1 && isCommon; j++) if (commonName != substring(imageList[j + 1], 0, i + 1)) isCommon = false; } commonName = substring(commonName, 0, commonName.length - 1); commonL = commonName.length; imageShortList = Array.copy(imageList); for (i = 0; i < imageN; i++) { if (indexOf(imageList[i], commonName) == 0) imageShortList[i] = substring(imageList[i], commonL); imageShortList[i] = stripKnownExtensionFromString(imageShortList[i]); } bitD = bitDepth; if (!endsWith(commonName, "_") && !endsWith(commonName, "_")) stackName = commonName + "_Stack"; else stackName = commonName + "Stack"; /* If images are of different sizes the background color will be used to fill in the gaps, so for consistency the background is set to black */ preBackground = Color.background; setBackgroundColor("black"); preForeground = Color.foreground; setForegroundColor("white"); marginPx = 0; autoCrop = false; /* ASC Dialog style */ infoColor = "#006db0"; /* Honolulu blue */ instructionColor = "#798541"; /* green_dark_modern (121, 133, 65) AKA Wasabi */ infoWarningColor = "#ff69b4"; /* pink_modern AKA hot pink */ infoFontSize = 12; Dialog.create("Select stack image order \(" + macroL + "\)"); Dialog.addNumber("Limit to the first", imageN, 0, 5, "slices"); Dialog.addString("Stack name", stackName, stackName.length + 2); shortCommon = substring(commonName, 0, minOf(5, commonName.length)) + "..."; if (imageN < 30) { Dialog.addMessage("Choose slice order, '1' will be the top slice. Alignment will be to the top slice", infoFontSize + 1, instructionColor); for (i = 0, addRow = 0; i < imageN; i++, addRow++) { if (addRow > 0 && imageN > 4) { Dialog.addToSameRow(); addRow = -1; } Dialog.addChoice("slice " + i + 1 + " \(" + shortCommon + "\)", imageShortList, imageShortList[i]); } } else { Dialog.addChoice("Choose top slice \(1\)", imageList, imageList[0]); Dialog.addCheckbox("Set slice label as unique name as listed above", true); } Dialog.setInsets(0, 120, 10); sliceOptions = newArray("Reverse this order", "Close originals after stacking", "Close intermediate stacks", "Apply names above as slice names", "Manually crop stack", "Diagnostic mode"); sliceOptionChecks = newArray(); sliceOptionChecks = Array.concat(sliceOptionChecks, call("ij.Prefs.get", ascPrefsKey + "reverseOrder", false)); sliceOptionChecks = Array.concat(sliceOptionChecks, call("ij.Prefs.get", ascPrefsKey + "closeSourceImages", false)); sliceOptionChecks = Array.concat(sliceOptionChecks, call("ij.Prefs.get", ascPrefsKey + "closeIntermediateStacks", false)); sliceOptionChecks = Array.concat(sliceOptionChecks, call("ij.Prefs.get", ascPrefsKey + "setSliceName", true)); sliceOptionChecks = Array.concat(sliceOptionChecks, call("ij.Prefs.get", ascPrefsKey + "manCrop", false), false); /* Extra 'false' for diagnostics default */ Dialog.addCheckboxGroup(3, 2, sliceOptions, sliceOptionChecks); barCropH = parseInt(call("ij.Prefs.get", ascPrefsKey + "barCropH", NaN)); lastImageStartWidth = parseInt(call("ij.Prefs.get", ascPrefsKey + "lastImageStartWidth", Image.width)); lastImageStartHeight = parseInt(call("ij.Prefs.get", ascPrefsKey + "lastImageStartHeight", Image.height)); if (lastImageStartWidth != Image.width || lastImageStartHeight != Image.height) barCropH = NaN; cols = maxOf(3, lengthOf(d2s(maxOf(Image.width, Image.height), 0))); if (barCropH > 0) Dialog.addMessage("The crop of " + barCropH + " pixels from the last run has been applied below:", infoFontSize, infoWarningColor); Dialog.setInsets(5, 10, 0); Dialog.addNumber("Crop off bottom lines", barCropH, 0, cols, "pixels"); Dialog.addToSameRow(); Dialog.addMessage("Use this option to remove an annotation bar", infoFontSize, instructionColor); Dialog.setInsets(5, 10, 0); Dialog.addNumber("Linear contrast stretch", NaN, 0, cols, "% saturated"); contrastOptions = newArray("Maximize bit range", "Equalize histogram"); contrastChecks = newArray(false, false); if (bitD == 8 || bitD == 16) { /* Bleach correction by histogram matching \n\(Miura, K. doi:10.12688/f1000research.27171.1 */ contrastOptions = Array.concat(contrastOptions, "Histogram matching"); contrastChecks = Array.concat(contrastChecks, false); } Dialog.setInsets(10, 10, 10); Dialog.addCheckboxGroup(1, contrastOptions.length, contrastOptions, contrastChecks); Dialog.addNumber("Median filter radius", NaN, 1, 3, "pixels"); Dialog.addToSameRow(); Dialog.addMessage("Filters will be ignored if settings are blank", infoFontSize, instructionColor); Dialog.addNumber("Unsharp mask radius", NaN, 1, 3, "pixels"); Dialog.addToSameRow(); unsharpW = parseFloat(call("ij.Prefs.get", ascPrefsKey + "unsharpW", NaN)); Dialog.addNumber("Unsharp mask weight", unsharpW, 1, 3, "0.1-0.9"); if (bitD == 8 || bitD == 16) { morphologicalFilters = newArray("None", "Gradient", "Internal Gradient", "External Gradient", "Dilation", "Erosion", "Opening", "Closing", "White Top Hot", "Black Top Hat", "White Top Hat", "Laplacian"); Dialog.addChoice("Morphological Filters", morphologicalFilters, "None"); /* https://imagej.net/plugins/morpholibj#Morphological_filters */ morphologicalElements = newArray("Square", "Cube", "Diamond", "Octagon", "Horizontal Line", "Vertical Line", "Z-Line", "Line 45 Degrees", "Line 135 Degrees"); Dialog.addToSameRow(); morphologicalElement = call("ij.Prefs.get", ascPrefsKey + "morphologicalElement", "Square"); Dialog.addChoice("Morphological Elements", morphologicalElements, morphologicalElement); morphologicalX = parseInt(call("ij.Prefs.get", ascPrefsKey + "morphologicalX", 2)); Dialog.addNumber("X-Radius", morphologicalX, 0, 5, "voxels"); Dialog.addToSameRow(); Dialog.addMessage("Morphological Filters are 3D", infoFontSize, instructionColor); morphologicalY = parseInt(call("ij.Prefs.get", ascPrefsKey + "morphologicalY", 2)); Dialog.addNumber("Y-Radius", morphologicalY, 0, 5, "voxels"); morphologicalZ = parseInt(call("ij.Prefs.get", ascPrefsKey + "morphologicalZ", 0)); Dialog.addToSameRow(); Dialog.addNumber("Z-Radius", morphologicalZ, 0, 5, "voxels"); Dialog.addCheckbox("Find edges \(Sobel\)", false); if (imageN >= 3) Dialog.addCheckbox("Create RGB image from 3-stack", false); } else Dialog.addCheckbox("Find edges \(Sobel\)", false); alignOptions = newArray("None", "Manual", "Translation", "Rigid Body", "Scaled Rotation", "Affine"); Dialog.addRadioButtonGroup("Slice alignment:_______________(manual alignment is by line selection between 2 corresponding points\)", alignOptions, 1, 5, "None"); zProjections = newArray("None", "Average Intensity", "Max Intensity", "Min Intensity", "Median"); if (bitD != 24) zProjections = Array.concat(zProjections, "Average-Ignore Black"); Dialog.addRadioButtonGroup("z-Projection:___________________", zProjections, 2, 3, "None");; Dialog.show(); tileN = Dialog.getNumber(); stackName = Dialog.getString(); tilesOrdered = newArray(); tilesShortOrdered = newArray(); if (imageN < 30) { for (i = 0; i < tileN; i++) { shortTileName = Dialog.getChoice(); tilesShortOrdered = Array.concat(tilesShortOrdered, shortTileName); tilesOrdered = Array.concat(tilesOrdered, imageList[indexOfArray(imageShortList, shortTileName, -1)]); } } else { sliceZero = Dialog.getChoice(); if (sliceZero == imageList[0]) tilesOrdered = imageList; else if (sliceZero == imageList[imageN - 1]) tilesOrdered = Array.reverse(imageList); else { tilesOrdered = Array.concat(sliceZero, imageList); tilesOrdered = removeDuplicatesInArray(tilesOrdered, false); } tilesOrdered = Array.trim(tilesOrdered, tileN); for (i = 0; i < tilesOrdered.length; i++) tilesShortOrdered[i] = substring(tilesOrdered[i], commonL); } if (Dialog.getCheckbox()) { tilesOrdered = Array.reverse(tilesOrdered); tilesShortOrdered = Array.reverse(tilesShortOrdered); reverseOrder = true; } else reverseOrder = false; call("ij.Prefs.set", ascPrefsKey + "reverseOrder", reverseOrder); closeSourceImages = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "closeSourceImages", closeSourceImages); closeIntermediateStacks = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "closeIntermediateStacks", closeIntermediateStacks); setSliceName = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "setSliceName", setSliceName); manCrop = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "manCrop", manCrop); diagnostics = Dialog.getCheckbox(); barCropH = Dialog.getNumber(); call("ij.Prefs.set", ascPrefsKey + "barCropH", barCropH); if (isNaN(barCropH)) barCropH = 0; saturation = Dialog.getNumber(); equalization = Dialog.getCheckbox(); normalization = Dialog.getCheckbox(); if (bitD == 8 || bitD == 16) histMatch = Dialog.getCheckbox(); medianR = Dialog.getNumber(); unsharpR = Dialog.getNumber(); unsharpW = Dialog.getNumber(); if (unsharpW < 0.1 || unsharpW > 0.9) unsharpW = NaN; call("ij.Prefs.set", ascPrefsKey + "unsharpW", unsharpW); morphologicalFilter = "None"; if (bitD == 8 || bitD == 16) { morphologicalFilter = Dialog.getChoice(); morphologicalElement = Dialog.getChoice(); morphologicalX = Dialog.getNumber; morphologicalY = Dialog.getNumber; morphologicalZ = Dialog.getNumber; if (morphologicalFilter != "None") { call("ij.Prefs.set", ascPrefsKey + "morphologicalElement", morphologicalElement); call("ij.Prefs.set", ascPrefsKey + "morphologicalX", morphologicalX); call("ij.Prefs.set", ascPrefsKey + "morphologicalY", morphologicalY); call("ij.Prefs.set", ascPrefsKey + "morphologicalZ", morphologicalZ); } } findEdges = Dialog.getCheckbox(); if ((bitD == 8 || bitD == 16) && imageN >= 3) { convertToRGB = Dialog.getCheckbox(); if (tileN != 3) convertToRGB = false; } else { convertToRGB = false; histMatch = false; } alignOption = Dialog.getRadioButton(); zProjection = Dialog.getRadioButton(); /* End of main dialog */ call("ij.Prefs.set", ascPrefsKey + "lastImageStartWidth", Image.width); call("ij.Prefs.set", ascPrefsKey + "lastImageStartHeight", Image.height); tileNames = newArray; for (i = 0; i < tileN; i++) { tWOE = stripKnownExtensionFromString(tilesOrdered[i]); if (commonL > 0 && commonL < tWOE.length) tWOE = substring(tWOE, commonL, tWOE.length); tileNames[i] = tWOE; } if (isOpen(stackName)) close(stackName); if (alignOption == "Manual") { slicesAligned = newArray(""); Dialog.create("Manual alignment options \(" + macroL + "\)"); manualAlignInfo = "If no overlap is entered the series is assumed to be 100% overlapping \(z-stack)" + "\nIf a horizontal overlap is entered the series is assumed to be a horizontal tile set" + "\nIf a vertical overlap is entered the series is assumed to be a vertical tile set"; Dialog.addMessage(manualAlignInfo, infoFontSize, instructionColor); /* Note that margins work for this manual alignment but IJ auto-alignment thinks that margins (any value including NaN) are dominant alignment features. */ Dialog.addNumber("Margins \(each side\)", parseInt(call("ij.Prefs.get", ascPrefsKey + "marginPx", 0.1 * (Image.width + Image.height))), 0, 7, "pixels"); Dialog.addMessage("Some margin is recommended as otherwise parts of images\nthat are aligned out of frame will be cut off", infoFontSize, infoColor); manualAlignOptions = newArray("Allow scaling", "Allow rotation", "Auto-Crop"); manualAlignChecks = newArray(call("ij.Prefs.get", ascPrefsKey + "manScale", true), call("ij.Prefs.get", ascPrefsKey + "manRot", true), call("ij.Prefs.get", ascPrefsKey + "autoCrop", true)); Dialog.setInsets(10, 20, -5); Dialog.addCheckboxGroup(1, 3, manualAlignOptions, manualAlignChecks); Dialog.addMessage("Leave both unchecked for simple translation", infoFontSize, instructionColor); lastLinesSaved = call("ij.Prefs.get", ascPrefsKey + "lastLinesLoaded", ""); if (File.exists(lastLinesSaved)) { lastLinesLoaded = lastLinesSaved; abbLastLinesLoaded = substring(lastLinesLoaded, maxOf(0, lastLinesLoaded.length - 50)); } else lastLinesLoaded = ""; if (lastLinesLoaded != "") Dialog.addCheckbox("Load points from: ..." + abbLastLinesLoaded, false); Dialog.addFile("Or load previously saved line set", ""); Dialog.show; marginPx = parseInt(Dialog.getNumber()); call("ij.Prefs.set", ascPrefsKey + "marginPx", marginPx); if (marginPx == NaN) marginPx = 0; manualAlignOptionString = ""; manScale = Dialog.getCheckbox(); if (manScale) manualAlignOptionString += " scale"; call("ij.Prefs.set", ascPrefsKey + "manScale", manScale); manRot = Dialog.getCheckbox(); if (manRot) manualAlignOptionString += " rotate"; call("ij.Prefs.set", ascPrefsKey + "manRot", manRot); autoCrop = Dialog.getCheckbox(); if (autoCrop) manualAlignOptionString += " rotate"; call("ij.Prefs.set", ascPrefsKey + "autoCrop", autoCrop); usePrevious = false; if (lastLinesLoaded != "") usePrevious = Dialog.getCheckbox(); if (usePrevious) savedLinesFile = lastLinesLoaded; else savedLinesFile = Dialog.getString(); /* End of Manual Alignment Dialog */ gotLines = false; if (lastLinesLoaded != "") Dialog.addCheckbox("Load points from: " + lastLinesLoaded, false); if (savedLinesFile != "") { if (File.exists(savedLinesFile)) { call("ij.Prefs.set", ascPrefsKey + "lastLinesLoaded", savedLinesFile); savedLineSet = File.openAsString(savedLinesFile); savedLines = split(savedLineSet, "\n"); linesNeeded = 2 * (tileN - 1); if (lengthOf(savedLines) == linesNeeded) gotLines = true; else IJ.log("Saved lines not used because " + lengthOf(savedLines) + " were found but " + linesNeeded + " are needed"); } } call("ij.Prefs.set", ascPrefsKey + "lastImageStartWidth", Image.width); call("ij.Prefs.set", ascPrefsKey + "lastImageStartHeight", Image.height); tileNames = newArray; for (i = 0; i < tileN; i++) { tWOE = stripKnownExtensionFromString(tilesOrdered[i]); if (commonL > 0 && commonL < tWOE.length) tWOE = substring(tWOE, commonL, tWOE.length); tileNames[i] = tWOE; } preX = 0; preY = 0; xImageOffsets = newArray; yImageOffsets = newArray; x1s = newArray(2 * (tileN - 1)); y1s = newArray(2 * (tileN - 1)); x2s = newArray(2 * (tileN - 1)); y2s = newArray(2 * (tileN - 1)); for (i = 0, sLineC = 0, wLineC = 0; i < tileN; i++) { run("Tile"); run("Cascade"); setTool("hand"); imWidth = Image.width; imHt = Image.height; maxImWidth = imWidth; maxImHt = imHt - barCropH; if (i == 0) { dir = getInfo("image.directory"); offsetX = 0; offsetY = 0; lineCoordSet = ""; xImageOffsets = newArray(0, 0); yImageOffsets = newArray(0, 0); linePairs = newArray(); } else { selectWindow(tilesOrdered[0]); tName = tilesShortOrdered[0]; run("Main Window [enter]"); run("View 100%"); setLocation(0.05 * screenWidth, 0.15 * screenHeight, 0.4 * screenWidth, 0.8 * screenHeight); run("Scale to Fit"); showStatus("Target tile for alignment", "flash image #ff0000 500ms"); if (gotLines) { restoredCoords = split(savedLines[sLineC], ", "); makeLine(parseInt(restoredCoords[0]), parseInt(restoredCoords[1]), parseInt(restoredCoords[2]), parseInt(restoredCoords[3]), 3); lTxt = i + ": Drawing line from file " + sLineC; sLineC++; } else { if (i > 1) { makeLine(x1s[wLineC], y1s[wLineC], x2s[wLineC], y2s[wLineC], 1); lTxt = i + ": Drawing previous line " + wLineC + " on target " + tName; } else { makeLine(0.1 * imWidth, 0.1 * imHt, 0.9 * imWidth, 0.9 * imHt, 1); lTxt = i + ": Drawing default diagonal line on target " + tName; } } Roi.setStrokeColor("#30ff0000"); Roi.setStrokeWidth(maxOf(3, Image.width / 200)); updateDisplay(); if (diagnostics) IJ.log(lTxt); showStatus(lTxt, "flash image #00ffff 50ms"); selectWindow(tilesOrdered[i]); run("Main Window [enter]"); sName = tilesShortOrdered[i]; run("View 100%"); run("Scale to Fit"); setLocation(0.5 * screenWidth, 0.15 * screenHeight, 0.4 * screenWidth, 0.8 * screenHeight); showStatus("Source tile for alignment", "flash image #00ffff 500ms"); if (gotLines) { restoredCoords = split(savedLines[sLineC], ", "); makeLine(parseInt(restoredCoords[0]), parseInt(restoredCoords[1]), parseInt(restoredCoords[2]), parseInt(restoredCoords[3]), 3); lTxt = i + ": Drawing line from file " + sLineC + " on source " + sName; sLineC++; } else { if (i > 1) { makeLine(x1s[wLineC - 1], y1s[wLineC - 1], x2s[wLineC - 1], y2s[wLineC - 1], 1); lTxt = i + ": Drawing previous line " + wLineC + " on source " + sName; } else { makeLine(0.1 * imWidth, 0.1 * imHt, 0.9 * imWidth, 0.9 * imHt, 1); lTxt = i + ": Drawing default diagonal line on source " + sName; } } Roi.setStrokeColor("#300000ff"); Roi.setStrokeWidth(maxOf(3, Image.width / 200)); updateDisplay(); if (diagnostics) IJ.log(lTxt); hints = "\n \nHint 1: Use the '+' and '-' keys to zoom in and out over the cursor for better accuracy\" + "\nHint 2: You can quickly move the entire line by grabbing the center selection point" + "\nHint 3: Switch between images using Window menu on ImageJ bar"; lineInstructions = "Adjust lines on target: " + tName + "\nand source: " + sName + "\nbetween the two well-spaced features" + hints; setTool("line"); showStatus(lTxt, "flash image #00ffff 50ms"); waitForUser("Set #" + i + ": Line Align Instructions", lineInstructions); /* get target coords */ selectWindow(tilesOrdered[0]); getLine(x1s[wLineC], y1s[wLineC], x2s[wLineC], y2s[wLineC], lineWidth); run("Select None"); lineCoordString = "" + x1s[wLineC] + ", " + y1s[wLineC] + ", " + x2s[wLineC] + ", " + y2s[wLineC]; lineCoordSet += "" + lineCoordString + ", " + getTitle() + "\n"; if (diagnostics) IJ.log("Line coords for target: " + lineCoordSet); linePairs[wLineC] = lineCoordString; cXT = (x1s[wLineC] + x2s[wLineC]) / 2; cYT = (y1s[wLineC] + y2s[wLineC]) / 2; wLineC++; run("Main Window [enter]"); /* get source coords */ selectWindow(tilesOrdered[i]); getLine(x1s[wLineC], y1s[wLineC], x2s[wLineC], y2s[wLineC], lineWidth); run("Select None"); lineCoordString = "" + x1s[wLineC] + ", " + y1s[wLineC] + ", " + x2s[wLineC] + ", " + y2s[wLineC]; lineCoordSet += "" + lineCoordString + ", " + getTitle() + "\n"; if (diagnostics) IJ.log("Line coords for source: " + lineCoordSet); linePairs[wLineC] = lineCoordString; cXS = (x1s[wLineC] + x2s[wLineC]) / 2; cYS = (y1s[wLineC] + y2s[wLineC]) / 2; wLineC++; offsetX = cXT - cXS; offsetY = cYT - cYS; xImageOffsets[i] = offsetX; yImageOffsets[i] = offsetY; maxImWidth = maxOf(maxImWidth, imWidth); maxImHt = maxOf(maxImHt, imHt) - barCropH; if (diagnostics) IJ.log("Alignment " + i + ": source=" + sName + " target=" + tName + manualAlignOptionString); } } /* save line coordinates for future use */ while (endsWith(lineCoordSet, "\n")) lineCoordSet = substring(lineCoordSet, 0, lineCoordSet.length - 1); title1LineCoordSet = commonName + "_LineCoordSet"; title2LineCoordSet = "[" + title1LineCoordSet + "]"; f = title2LineCoordSet; if (isOpen(title1LineCoordSet)) print(f, "\\Update:"); // clears the window else run("Text Window...", "name=" + title2LineCoordSet + " width=72 height=8 menu"); if (diagnostics) IJ.log("Full path tile configuration:\n" + lineCoordSet); print(f, lineCoordSet); lineCoordSetPath = dir + title1LineCoordSet + ".txt"; saveAs("Text", lineCoordSetPath); /* Saved for compatibility with older versions */ run("Close"); /* Need to close window and start fresh as there is no rename function for text windows */ /* end line saves */ /* End for alignment iterations */ call("ij.Prefs.set", ascPrefsKey + "lastLinesLoaded", lineCoordSetPath); Array.getStatistics(xImageOffsets, minX, maxX, null, null); Array.getStatistics(yImageOffsets, minY, maxY, null, null); if (diagnostics) { IJ.log("Offset arrays:"); Array.print(xImageOffsets); Array.print(yImageOffsets); } } /* End for manual alignment */ if (!diagnostics) setBatchMode(true); for (i = 0; i < tileN; i++) { selectWindow(tilesOrdered[i]); if (i == 0) { maxWidth = Image.width; maxHeight = Image.height; } else { maxWidth = maxOf(maxWidth, Image.width); maxHeight = maxOf(maxHeight, Image.height); } } if (alignOption != "Manual") { marginFill = ""; // Dialog.create("Hello"); Dialog.create("Add margins \(" + macroL + "\)"); marginPx = parseInt(call("ij.Prefs.get", ascPrefsKey + "marginPx", 0.1 * (maxWidth + maxHeight))); Dialog.addNumber("Margins \(each side\)", marginPx, 0, 7, "pixels"); Dialog.addCheckbox("Zero fill margin?", false); Dialog.show; marginPx = parseInt(Dialog.getNumber()); call("ij.Prefs.set", ascPrefsKey + "marginPx", marginPx); if (marginPx == NaN) marginPx = 0; if (Dialog.getCheckbox()) marginFill = " zero"; /* End of margin dialog */ } for (i = 0, wLineC = 1; i < tileN; i++) { selectWindow(tilesOrdered[i]); startW = Image.width; startH = Image.height - barCropH; if (i == 0) { makeRectangle(0, 0, startW, startH); run("Duplicate...", "title=" + stackName); run("Select None"); if (alignOption != "Manual") { if (startW < maxWidth || startH < maxHeight) run("Canvas Size...", "width=" + maxWidth + " height=" + maxHeight + " position=Top-Left" + marginFill); if (marginPx > 0) run("Canvas Size...", "width=" + maxWidth + marginPx * 2 + " height=" + maxHeight + marginPx * 2 + " position=Center" + marginFill); } else { if (minX < 0) run("Canvas Size...", "width=" + (startW - minX) + " height=" + startH + " position=Top-Right"); if (minY < 0) run("Canvas Size...", "width=" + startW + " height=" + (startH) - minY + " position=Bottom-Left"); run("Canvas Size...", "width=" + (maxX + maxWidth - minX) + " height=" + (maxY + maxHeight - minY) + " position=Top-Left"); if (manRot || manScale) run("Duplicate...", "title=temp_Target"); makeLine(x1s[0] - minX, y1s[0] - minY, x2s[0] - minX, y2s[0] - minY, 3); if (diagnostics) waitForUser(i + " line " + 0); } } else { selectWindow(tilesOrdered[i]); makeRectangle(0, 0, startW, startH); run("Duplicate...", "title=temp_Source"); run("Select None"); if (alignOption != "Manual") addImageToStack3(stackName, 0, 0); else { if (manRot || manScale) { makeLine(x1s[wLineC], y1s[wLineC], x2s[wLineC], y2s[wLineC], wLineC + 4); if (diagnostics) waitForUser(i + " line " + wLineC); run("Align Image by line ROI", "source=temp_Source target=temp_Target" + manualAlignOptionString); wLineC += 2; rename("temp_Aligned"); addImageToStack3(stackName, 0, 0); if (diagnostics) IJ.log("ROI-aligned tempSource added to 0, 0 of stack: " + stackName); close("temp_Aligned*"); } else { addImageToStack3(stackName, xImageOffsets[i] - minX, yImageOffsets[i] - minY); if (diagnostics) IJ.log("Image addition to stack: " + stackName, xImageOffsets[i] - minX, yImageOffsets[i] - minY); } } close("temp_Sourc*"); } } if (manCrop) { setBatchMode("exit and display"); run("Select None"); run("Select Bounding Box (guess background color)"); setTool("rectangle"); title = "Crop Stack"; msg = "1. Select the area that you want to crop to. 2. Click on OK"; waitForUser(title, msg); setBatchMode(true); if (selectionType >= 0) { run("Crop"); run("Select None"); } setBatchMode(true); } if (setSliceName) { for (i = 0; i < tileN; i++) { run("Select None"); setSlice(i + 1); run("Set Label...", "label=" + tileNames[i]); } } if (!isNaN(saturation) || equalization || normalization) { enhanceSettings = "update process_all"; if (equalization) enhanceSettings = "equalize " + enhanceSettings; if (normalization) enhanceSettings = "normalize " + enhanceSettings; if (!isNaN(saturation)) enhanceSettings = "saturated=" + d2s(saturation, 2) + " " + enhanceSettings; /* select 90% of image to ignore edges */ makeRectangle(0.05 * Image.width, 0.05 * Image.height, 0.9 * Image.width, 0.9 * Image.height); run("Enhance Contrast...", enhanceSettings); IJ.log("Linear contrast stretch applied: " + enhanceSettings); run("Select None"); stackName += "\+cStretch"; rename(stackName); } if (histMatch) { if (bitDepth == 32) run("16-bit"); /* Bleach correction requires 8 or 16-bit images */ intermID = getImageID(); IJ.log("IJ Bleach correction citation: Miura, K. (2020). Bleach correction ImageJ plugin for compensating the photobleaching of time-lapse sequences." + "\nF1000Research, 9, 1494. doi:10.12688/f1000research.27171.1"); run("Bleach Correction", "correction=[Histogram Matching]"); IJ.log("Bleach correction with Histogram Matching applied"); stackName += "\+histMatch"; rename(stackName); if (closeIntermediateStacks) { selectImage(intermID); close(); selectWindow(stackName); } } if (alignOption != "None" && alignOption != "Manual") { IJ.log("IJ Stack alignment is based on the following paper:" + "\nP. Thévenaz, U.E. Ruttimann, M. Unser A Pyramid Approach to Subpixel Registration Based on Intensity" + "\nIEEE Transactions on Image Processing vol. 7, no. 1, pp. 27-41, January 1998." + "\nThis paper is available on-line at http://bigwww.epfl.ch/publications/thevenaz9801.html"); if (bitDepth == 32) run("16-bit"); /* Weird results with 32-bit */ if (isOpen(stackName) && nSlices > 0) selectWindow(stackName); else { setBatchMode("exit and display"); if (!isOpen(stackName)) exit("Stack alignment issue: " + stackName + " not found"); if (nSlices < 1) exit("Stack alignment issue: " + stackName + " not a stack"); } setSlice(1); showStatus("Stack alignment underway, this could be slow. Stack will flash green on completion", "flash image red 200ms"); run("StackReg", "transformation=[" + alignOption + "]"); IJ.log("Stack alignment applied: " + alignOption); stackName += "\+aligned" + substring(alignOption, 0, 3); rename(stackName); showStatus("Stack alignment complete", "flash image green 200ms"); } if (!isNaN(medianR)) { run("Median...", "radius=" + medianR + " stack"); IJ.log("Median filter applied: radius=" + medianR); stackName += "\+Medn" + medianR; rename(stackName); } if (!isNaN(unsharpR) && !isNaN(unsharpW)) { run("Unsharp Mask...", "radius=" + unsharpR + " mask=" + unsharpW + " stack"); stackName += "\+Unsh" + unsharpR; rename(stackName); IJ.log("Unsharp Mask applied: radius=" + unsharpR + " mask=" + unsharpW); } if (morphologicalFilter != "None") { if (isOpen(stackName) && nSlices > 0) selectWindow(stackName); else { setBatchMode("exit and display"); if (!isOpen(stackName)) exit("Morphological filter issue: " + stackName + " not found"); if (nSlices < 1) exit("Morphological filter issue: " + stackName + " not a stack"); } if (bitDepth == 32) run("16-bit"); /* Weird results with 32-bit */ intermID = getImageID(); morphName = stackName + "\+" + substring(morphologicalFilter, 0, 4) + "X" + morphologicalX + "Y" + morphologicalY + "Z" + morphologicalZ; run("Morphological Filters (3D)", "operation=" + morphologicalFilter + " element=" + morphologicalElement + " x-radius=" + morphologicalX + " y-radius=" + morphologicalX + " z-radius=" + morphologicalZ); rename(morphName); if (isOpen(morphName)) { stackName = morphName; IJ.log("Morphological filter applied: " + morphologicalFilter + " element=" + morphologicalElement + " x-radius=" + morphologicalX + " y-radius=" + morphologicalY + " z-radius=" + morphologicalZ + "\nhttps://imagej.net/plugins/morpholibj#Morphological_filters"); if (closeIntermediateStacks) { selectImage(intermID); close(); selectWindow(stackName); } } } if (findEdges) { if (isOpen(stackName) && nSlices > 0) selectWindow(stackName); else { setBatchMode("exit and display"); if (!isOpen(stackName)) exit("Find edges issue: " + stackName + " not found"); if (nSlices < 1) exit("Find edges issue: " + stackName + " not a stack"); } intermID = getImageID(); run("Select None"); run("Duplicate...", "duplicate"); stackName += "\+Sobel"; rename(stackName); run("Find Edges", "stack"); IJ.log("Find edges \(Sobel filter\) applied"); if (closeIntermediateStacks) { selectImage(intermID); close(); selectWindow(stackName); } } if (convertToRGB) { /* Retains original stack */ selectWindow(stackName); run("Stack to RGB"); rename(stackName + "_RGB"); rgbID = getImageID; } if (zProjection != "None") { /* Retains original stack */ if (isOpen(stackName) && nSlices > 0) selectWindow(stackName); else { setBatchMode("exit and display"); if (!isOpen(stackName)) exit("Stack projection issue: " + stackName + " not found"); if (nSlices < 1) exit("Stack projection issue: " + stackName + " not a stack"); } preProjectBD = bitDepth; if (zProjection == "Average-Ignore Black") { /* NaN trick suggested by Tiago Ferreira: https://forum.image.sc/t/create-an-average-z-stack-projection-using-only-values-above-a-certain-level/74238/2 Nov 2022 */ setBatchMode("show"); setSlice(1); if (preProjectBD != 32 && preProjectBD != 24) run("32-bit"); setThreshold(-1, 10e30); waitForUser("Instructions for the Threshold window that pops up next to create the NaN background for averaging", "Read these instructions and then click 'OK' to continue \(do not start any of the steps before then\)\n" + "Don't worry about remembering all the steps, there will be a reminder pop-up.\n" + "1:\t Set top slider to one \(or two\) click\(s\) from full left.\n" + "2:\t Set bottom slider to full right. No other options should be necessary.\n" + " \tThe background should be black, the aligned images should be red \(or you preferred threshold color\).\n" + " \tYou may need to check all slices.\n" + "3:\t Then click on 'Apply.'\n" + "4:\t In the 'Thresholder' pop-up...click on 'Set to NaN' \(this should set all background pixels to NaN\).\n" + "5:\t In the 'Process Stack?' pop-up... click on 'Yes' to apply to all slices."); /* The full instruction are here because if shown after the Threshold window opens it will obscure the pop-ups */ run("Threshold..."); waitForUser("5th step completed?", "Hints:\n1:\t Slider 1\n2:\t Slider 2\n3:\t Apply.\n4:\t 'NaN'\n5:\t All slices \(Yes\)\n6:\t 'OK' below"); /* Very short wait-ForUser to avoid completely obscuring the pop-up requesters */ resetThreshold; close("Threshold"); run("Z Project...", "projection=[Average Intensity]"); if (preProjectBD != 32 && preProjectBD != 24) run("" + preProjectBD + "-bit"); run("Select None"); } else run("Z Project...", "projection=[" + zProjection + "]"); IJ.log("Z Projection applied: " + zProjection); zProjID = getImageID(); if (marginPx > 0) { getBB = true; gotBB = false; run("Select None"); if (selectionType >= 0) getBB = false; else { run("Select Bounding Box (guess background color)"); getSelectionBounds(xBB, yBB, widthBB, heightBB); if (widthBB > 10) gotBB = true; } if (gotBB) { if (autoCrop) run("Crop"); if (isOpen(stackName)) selectWindow(stackName); else exit(stackName + " was expected by was not found"); setBatchMode("show"); makeRectangle(xBB, yBB, widthBB, heightBB); setBatchMode("show"); updateDisplay(); if (autoCrop) run("Crop"); selectImage(zProjID); if (convertToRGB && autoCrop) { selectImage(rgbID); makeRectangle(xBB, yBB, widthBB, heightBB); setBatchMode("show"); updateDisplay(); if (autoCrop) run("Crop"); } } } } if (closeSourceImages) { for (cSi = 0; cSi < tileN; cSi++) { if (isOpen(tilesOrdered[cSi])) { selectWindow(tilesOrdered[cSi]); close(); } } } close("temp_*"); activeID = getImageID(); for (i = 0; i < tileN; i++) { if (isOpen(tilesOrdered[i])) { selectWindow(tilesOrdered[i]); run("Select None"); } } if (isOpen(activeID)) { selectImage(activeID); run("Scale to Fit"); } else IJ.log(activeID + " was expected but not found"); setBatchMode("exit and display"); Color.setBackground(preBackground); Color.setForeground(preForeground); restoreSettings(); IJ.log("Completed stack:\n" + stackName); showStatus("Images to Stack Plus completed", "flash green"); } /* !!!! Note the macro title in the startup includes the shortcut [F11] !!!! */ macro "Save: LZW-TIF PNG JPG Anims etc. [F11]" { /* Changes after v240905: v250117: gsAnim comment restored (not necessarily working). v251031: Fixed saveDir issue (PNG export) by adding default value. v260226: Corrected GIF anim export so that delays are now integer. v260414: Fixed an issue with images with overlays. v260417: When exporting ROIs you can now expand the regions around the ROIs. v260424: Adds some underscores to the main dialog. */ requires("1.53d"); /* Uses RoiManager.multiCrop for ROI export and compact montage creation */ macroL = "Save_Ani_PNG_or_GIF_or_Base64_or_compressed_Tiff_v260424.ijm"; diagnostics = false; /* this can be activated for debugging */ if (nImages == 0) exit("Sorry this macro needs an image to save, goodbye"); fS = File.separator; recentDir = getDir("file"); activeDir = getDirectory("image"); /* Returns the path to the directory that the active image was loaded from */ currentDir = getInfo("image.directory"); /* Returns the directory that the current image was loaded from, or an empty string if the directory is not available */ openedDir = File.directory; /* The directory path of the last file opened using a file open dialog, a file save dialog, drag and drop, open(path) or runMacro(path). */ workingDir = getDir("cwd"); saveDirs = newArray(); iJPath = getDirectory("imagej"); if (activeDir != "" && indexOf(activeDir, iJPath) < 0) saveDirs = Array.concat(saveDirs, "Active Directory: " + activeDir); if (currentDir != "" && currentDir != activeDir && indexOf(currentDir, iJPath) < 0) saveDirs = Array.concat(saveDirs, "Current Directory: " + currentDir); if (openedDir != "" && openedDir != activeDir && indexOf(openedDir, iJPath) < 0) saveDirs = Array.concat(saveDirs, "Last Opened Directory: " + openedDir); if (recentDir != "" && indexOf(recentDir, iJPath) < 0) saveDirs = Array.concat(saveDirs, "Recent Directory: " + recentDir); if (workingDir != "" && indexOf(workingDir, iJPath) < 0 && workingDir != activeDir) saveDirs = Array.concat(saveDirs, "Working Directory: " + workingDir); for (i = 0, maxPath = 0; i < saveDirs.length; i++) maxPath = maxOf(maxPath, saveDirs[i].length); saveDir = workingDir; /* Default export directlry */ xChar = fromCharCode(0x00D7); xCharSp = " " + xChar + " "; originalImage = stripKnownExtensionFromString(getTitle()); rename(originalImage); imageDepth = bitDepth(); getDimensions(imageWidth, imageHeight, null, null, null); getVoxelSize(pixelWidth, pixelHeight, pixelDepth, unit); binaryI = is("binary"); sWidth = screenWidth(); sHeight = screenHeight(); nOverlays = Overlay.size; if (selectionType >= 0) { selTypes = newArray("rectangle", "oval", "polygon", "freehand", "traced", "straight line", "segmented line", "freehand line", "angle", "composite", "point"); selType = selectionType(); getSelectionBounds(null, null, selWidth, selHeight); selToDo = getBoolean("There is currently a " + selWidth + xCharSp + selHeight + " " + selTypes[selType] + " selection, do you want to . . .", "Ignore this selection and save all", "Just save the selected area"); if (selToDo) run("Select None"); } bitChange = ""; compression = ""; tempDir = getDirectory("temp"); if (!endsWith(tempDir, fS)) tempDir += fS; jpegQual = parseInt(call("ij.Prefs.get", "asc.lastsaved.jpeg.qual", 85)); jp2Qual = parseInt(call("ij.Prefs.get", "asc.lastsaved.jpeg.qual", 0)); jpegToo = false; jp2Too = false; colorTableToo = false; iconToo = false; pngQual = parseInt(call("ij.Prefs.get", "asc.lastsaved.png.qual", 75)); /* ImageMagick PNG quality */ pngToo = false; grayscale = is("grayscale"); qMark = "\""; " qMarkSp = qMark + " "; spQMark = " " + qMark; reduceDepth = false; restoreTranspIndexGIF = 255; setTranspIndexGIF = 255; palColors = 256; /* Default palette color count */ substack = false; tempImage = false; moreDelays = false; tempImageTitle = ""; getDimensions(width, height, channels, slices, frames); if (isOpen("ROI Manager")) { nROIs = roiManager("count"); if (nROIs == 0) close("ROI Manager"); } else nROIs = 0; defTransp = "None"; medianBGIs = guessBGMedianIntensity(); cornerIs = newArray(getValue(0, 0), getValue(imageWidth - 1, 0), getValue(imageWidth - 1, imageHeight - 1), getValue(imageWidth - 1, imageHeight - 1)); Array.getStatistics(cornerIs, cornerIsMin, cornerIsMax, null, null); medianBGI = round((medianBGIs[0] + medianBGIs[1] + medianBGIs[2]) / 3); medianBGIHexName = "#" + "" + padIN(toHex(medianBGIs[0]), 2) + "" + padIN(toHex(medianBGIs[1]), 2) + "" + padIN(toHex(medianBGIs[2]), 2); if (medianBGI == 255 && (imageDepth != 16 || imageDepth != 32)) { if (cornerIsMin == cornerIsMax) { defTransp = "White"; defTranspN = 1; } medianBGIHexName = "#ffffff"; } if (medianBGI == 0) { if (cornerIsMin == cornerIsMax) { defTransp = "Black"; defTranspN = 0; } medianBGIHexName = "#000000"; } /* check for macro enhancing external apps */ iMMagickPath = findAppPath("ImageMagick", "magick.exe", "not found"); if (iMMagickPath != "not found") iMMagick = true; else iMMagick = false; gSPath = findAppPath("Gifsicle", "gifsicle.exe", "not found"); if (File.exists(gSPath)) { gifsicleGo = true; gSSep = qMark + " -o " + qMark; /* !!! Important to make sure final line includes balanced quotation marks so the gifsicle command line can handle paths with spaces *** */ } else gifsicleGo = false; /* Main options menu start */ fileTypeChoices = newArray("TIFF-ZIP"); fileTypeChoice = "TIFF-ZIP"; /* ASC message theme */ infoColor = "#006db0"; /* Honolulu blue */ instructionColor = "#798541"; /* green_dark_modern (121,133,65) AKA Wasabi */ infoWarningColor = "#ff69b4"; /* pink_modern AKA hot pink */ dividerColor = "#50bfe6"; /* blizzard blue */ infoFontSize = 12; Dialog.create("Basic export options \(" + macroL + "\)"); if (nSlices == 1) { fileTypeChoices = Array.concat("GIF", "PNG", fileTypeChoices); if (imageDepth != 32) fileTypeChoice = "PNG"; } else { fileTypeChoices = Array.concat("animGIF", "APNG", fileTypeChoices, "Image_Seq."); if (imageDepth != 32) fileTypeChoice = "animGIF"; } if (nROIs > 0) fileTypeChoices = Array.concat("ROIs_only", fileTypeChoices); if (iMMagick) { fileTypeChoices = Array.concat(fileTypeChoices, "TIFF-LZW"); if (imageDepth != 32) fileTypeChoice = "TIFF-LZW"; if (imageWidth * imageHeight < 50E6) fileTypeChoices = Array.concat(fileTypeChoices, "base64"); } Dialog.setInsets(0, 10, -5); Dialog.addMessage("Image: " + originalImage, infoFontSize * minOf(1, 70 / originalImage.length), infoColor); Dialog.setInsets(-5, 10, 0); Dialog.addRadioButtonGroup("Choose_File_Format:_________________________", fileTypeChoices, 1, 2, fileTypeChoice); typeMessage = ""; if (nROIs > 0) typeMessage += " If 'ROIs_only' selected the options below will be ignored and a new menu opened\n"; if (iMMagick) typeMessage += " TIFF-LZW retains the scale calibration in the header but NO overlays or other metadata\n"; if (imageDepth == 32 || imageDepth == 16) typeMessage += " Note: LZW expands " + imageDepth + "-bit images. Reduce image depth with check box below\n"; if (endsWith(typeMessage, "\n")) typeMessage = substring(typeMessage, 0, typeMessage.length - 1); if (typeMessage != "") { Dialog.setInsets(0, 5, 0); Dialog.addMessage(typeMessage, infoFontSize, infoColor); } if (imageDepth != 8) { bitChanges = newArray("No-change \(" + imageDepth + "-bit\)", "8-bit \(gray\)"); if (!is("grayscale")) bitChanges = Array.concat(bitChanges, "PAL_\(color\)"); labelTxt = "Reduce bit depth \(currently " + imageDepth + " bit\) of saved ZIP/LZW or Image Sequence to 8-bit?"; labelTxt += "\nGIF and APNG images will default to PAL if not grayscale \(overrides 'No-change'\)"; Dialog.addRadioButtonGroup(labelTxt, bitChanges, 1, 3, bitChanges[0]); if (!is("grayscale")) Dialog.addNumber("Number of colors for PAL", 256, 0, 3, "colors"); } Dialog.setInsets(5, 5, 0); Dialog.addMessage("PNG and TIFF resize options \(GIF resizing in GIF options\) from original " + width + xChar + height + " pixels:", infoFontSize, infoColor); Dialog.addChoice("Base resize on width or height dimension", newArray("width", "height"), "width"); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Resize image dimension above to fit:", NaN, 0, 7, "pixels, \(aspect ratio retained\)"); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Scale image dimensions to:", NaN, 0, 7, "% of original"); interpolations = newArray("None", "Bilinear", "Bicubic"); interpIJ = call("ij.Prefs.get", "asc.lastsaved.imagej.interpolation", interpolations[2]); if (binaryI) interpIJ = interpolations[0]; Dialog.setInsets(0, 0, 0); Dialog.addChoice("Interpolation for resizing:", interpolations, interpIJ); /* Bicubic is Catmull-Rom: https://imagej.net/ij/docs/guide/146-28.html)*/ interpAvgIJ = call("ij.Prefs.get", "asc.lastsaved.imagej.interpAvg", true); Dialog.setInsets(0, 100, 5); Dialog.addCheckbox("Average when downsizing to reduce resizing artifacts", interpAvgIJ); if (iMMagick) { Dialog.addString("PNG quality \(ImageMagick\)", pngQual, 2); Dialog.setInsets(-7, 50, 0); Dialog.addMessage("Default IM png quality is 75, '7' for compression level 7, '5' for adaptive filtering,\n\(if the image has a color map a compression level 7 with no PNG filtering\)", infoFontSize, infoColor); transpChoices = newArray("None", "Corner_median", "White", "Black", "Hex code:"); bgColStr = "PNG transparency options \(median corner-background "; if (imageDepth == 24) bgColStr += "color is " + medianBGIHexName + "\)"; else bgColStr += "intensity is " + medianBGI + "\)"; if (nSlices > 1) bgColStr += "\n***No transparency available for APNG***"; else bgColStr += ":____________"; Dialog.setInsets(0, 0, 0); Dialog.addRadioButtonGroup(bgColStr, transpChoices, 1, transpChoices.length, defTransp); Dialog.setInsets(0, 0, 0); Dialog.addString("Hex code for transparency", toUpperCase(medianBGIHexName), 8); Dialog.setInsets(0, 0, 0); Dialog.addString("Hex code or std color for matte", "---If different from transparent color---", 30); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Fuzz", NaN, 0, 3, "% expansion of transparency range"); } if (nSlices > 1) { Dialog.addMessage("Save substack \(leave defaults for full stack\):_________________________", infoFontSize - 1, dividerColor); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Sub-stack start frame", 1); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Sub-stack end frame", nSlices); Dialog.addNumber("Sub-stack sampling", 1); } if (nOverlays != 0) { flattenOptions = newArray("Flatten image", "Hide overlays", "Ignore overlays"); iDefFO = 0; if (nSlices > 1 || nOverlays == 1) iDefFO = 1; else iDefFO = 0; overlayMessage = d2s(nOverlays, 0) + " overlays. Flattening image will not change original. Overlays are removed for LZW saves"; Dialog.addRadioButtonGroup(overlayMessage, flattenOptions, 1, 3, flattenOptions[iDefFO]); } if (is("composite")) Dialog.addCheckbox("This is a composite image; convert to RGB first?", true); maxPathL = call("ij.Prefs.get", "asc.path.maxLength", 256); Dialog.addNumber("Maximum path length before warning:", maxPathL, 0, 4, "Use 256 for compatibility"); Dialog.show(); fileType = Dialog.getRadioButton(); if (fileType == "Image_Seq.") fileType = "Image Seq."; if (fileType != "ROIs_only") { /* No further operations are applied to the ROI saves */ if (imageDepth != 8) { bitChoice = Dialog.getRadioButton(); if (startsWith(bitChoice, "8-bit")) bitChoice = "8-bit"; else if (startsWith(bitChoice, "PAL")) bitChoice = "PAL"; if (!startsWith(bitChoice, "No")) reduceDepth = true; if (!is("grayscale")) palColors = Math.constrain(Dialog.getNumber(), 0, 256); } if (imageDepth > 8 && (fileType == "GIF" || fileType == "animGIF" || fileType == "APNG")) { reduceDepth = true; if (startsWith(bitChoice, "No")) { if (is("grayscale")) bitChoice = "8-bit"; else bitChoice = "PAL"; } } dimensionChoice = Dialog.getChoice(); newDim = Dialog.getNumber(); if (isNaN(newDim)) newDim = 0; scaleImage = Dialog.getNumber(); if (isNaN(scaleImage)) scaleImage = 1; else scaleImage /= 100; interpIJ = Dialog.getChoice(); call("ij.Prefs.set", "asc.lastsaved.imagej.interpolation", interpIJ); if (Dialog.getCheckbox()) { interpIJ += " average"; call("ij.Prefs.set", "asc.lastsaved.imagej.interpAvg", true); } else call("ij.Prefs.set", "asc.lastsaved.imagej.interpAvg", false); if (newDim != 0) { if (dimensionChoice == "width") scaleImage = newDim / width; else scaleImage = newDim / height; } sPCLab = "_" + d2s(scaleImage * 100, 1) + "pc"; if (scaleImage != 1) { if (fileType != "animGIF" && fileType != "APNG" && !startsWith(fileType, "TIFF")) { run("Scale...", "x=" + scaleImage + " y=" + scaleImage + " interpolation=" + interpIJ + " create"); getVoxelSize(pixelWidthS, pixelHeightS, pixelDepthS, unitS); newTitle = originalImage + sPCLab; if (newDim != 0) { if (dimensionChoice == "width") newTitle += "_" + d2s(newDim, 0) + "w"; else newTitle += "_" + d2s(newDim, 0) + "h"; } rename(newTitle); if (tempImage == false) tempImage = true; else close(tempImageTitle); tempImageTitle = getTitle(); } } savePNGTransp = false; if (iMMagick) { pngQual = Dialog.getString(); saveTranspPNGChoice = Dialog.getRadioButton(); pngTranspHex = "" + Dialog.getString(); pngMatteCol = "" + Dialog.getString(); if (startsWith(pngMatteCol, "If")) pngMatteCol = pngTranspHex; fuzz = Dialog.getNumber(); if (isNaN(fuzz)) fuzz = 0; if (!startsWith(saveTranspPNGChoice, "No")) { savePNGTransp = true; if (startsWith(saveTranspPNGChoice, "Hex")) { if (pngTranspHex != "" && pngTranspHex != "#") { if (!startsWith(pngTranspHex, "#")) pngTranspSet = "#" + pngTranspHex; pngTranspSet = qMark + pngTranspHex + qMark; pngMatteSet = qMark + pngMatteCol + qMark; } else savePNGTransp = false; } else if (saveTranspPNGChoice == "Corner_median") { pngTranspSet = qMark + medianBGIHexName + qMark; if (pngMatteCol != pngTranspHex) pngMatteSet = qMark + pngMatteCol + qMark; else pngMatteSet = qMark + medianBGIHexName + qMark; } else { pngTranspSet = saveTranspPNGChoice; if (pngMatteCol == pngTranspHex) pngMatteSet = qMark + saveTranspPNGChoice + qMark; else pngMatteSet = qMark + pngMatteCol + qMark; } } iMPNGSettings = " -quality " + pngQual + " "; if (savePNGTransp) { iMPNGSettings += " -transparent " + pngTranspSet + " -background " + pngMatteSet; if (fuzz > 0) iMPNGSettings += " -fuzz " + fuzz + "% "; } } if (nSlices > 1) { startF = Dialog.getNumber(); endF = Dialog.getNumber(); sampling = Dialog.getNumber(); if (startF != 1 || endF != nSlices || sampling != 1) { run("Make Substack...", " slices=" + startF + "-" + endF + "-" + sampling); rename(originalImage + "_substack"); if (tempImage) closeImageByTitle(tempImageTitle); tempImageTitle = getTitle(); } } if (Overlay.size != 0) { flattenOption = Dialog.getRadioButton(); if (startsWith(flattenOption, "Flatten")) { run("Flatten"); bitChange = "8-bit_flat"; rename(originalImage + bitChange); tempImage = true; tempImageTitle = getTitle(); reduceDepth = false; } else if (startsWith(flattenOption, "Hide")) Overlay.hide; } if (is("composite")) { if (Dialog.getCheckbox()) { tTemp = getTitle(); run("Stack to RGB"); rename(tTemp + "_RGB"); tempImageTitle = getTitle(); } } maxPathL = Dialog.getNumber(); call("ij.Prefs.set", "asc.path.maxLength", maxPathL); /* End of Main Dialog if not ROIs_only otherwise skips to new dialog . . . */ } else { Dialog.create("ROI save options \(" + macroL + "\)"); fileName = unCleanLabel(stripKnownExtensionFromString(getTitle)); roiFileTypes = newArray("tiff", "png", "jpg"); Dialog.addRadioButtonGroup("Output file type:", roiFileTypes, 1, 3, roiFileTypes[0]); Dialog.addNumber("Expand region around ROI by", 0, 0, 3, "pixels"); Dialog.addCheckbox("Also create stack", true); if (nOverlays != nROIs) Dialog.addMessage(nROIs + " ROIs and " + nOverlays + " overlays: Montage creation currently requires an overlay for each ROI", infoFontSize, infoWarningColor); Dialog.addCheckbox("Save ROI list \(not saved if 'stack-only' selected\)", true); Dialog.addString("ROI list prefix \(suffix is '_RoiSet.zip'\)", originalImage, originalImage.length + 5); lastSavedROIPath = call("ij.Prefs.get", "asc.lastsaved.roi.path", "not found"); if (lastSavedROIPath != "not found" && indexOf(lastSavedROIPath, iJPath) < 0) saveDirs = Array.concat("Last ROIs Saved: " + lastSavedROIPath, saveDirs); if (saveDirs.length > 0) { Dialog.addRadioButtonGroup("Set output directory or... :", saveDirs, saveDirs.length, 1, saveDirs[0]); Dialog.addDirectory("or...Specify directory \(leave blank if using selection above\)", ""); } else Dialog.addDirectory("Enter or choose directory \(leave blank if using selection above\)", ""); Dialog.addString("Create sub-directory \(leave blank if not desired\):", "", minOf(sWidth - 50, lengthOf(fileName) + 10)); if (nOverlays == slices) { roiStackOptions = newArray("Create compact montage of ROIs", "Unselect selections from stack", "Hide overlays in stack", "White background"); roiStackOptionChecks = newArray(true, true, true, true); Dialog.addCheckboxGroup(2, 2, roiStackOptions, roiStackOptionChecks); Dialog.addNumber("There are " + nROIs + " ROIs; how many montage columns do you want?", round(sqrt(nROIs)), 0, 5, ""); Dialog.addNumber("Border width for montage:", 1, 0, 5, "pixels"); } else makeMontage = false; Dialog.show; roiTypeChoice = Dialog.getRadioButton(); expandN = Dialog.getNumber(); alsoStack = Dialog.getCheckbox(); saveROIList = Dialog.getCheckbox(); roiPrefix = Dialog.getString(); if (saveDirs.length > 0) dirChoice = Dialog.getRadioButton(); selDir = Dialog.getString(); if (selDir != "") saveDir = selDir; else if (saveDirs.length < 1) exit("No output directory selected"); else if (startsWith(dirChoice, "Active")) saveDir = activeDir; else if (startsWith(dirChoice, "Current")) saveDir = currentDir; else if (startsWith(dirChoice, "Last ROIs Saved")) saveDir = lastSavedROIPath; else if (startsWith(dirChoice, "Last Opened")) saveDir = openedDir; if (!endsWith(saveDir, fS)) saveDir += fS; if (!File.isDirectory(saveDir)) { if (File.exists(saveDir)) exit("Selected directory is a file not a directory"); else File.makeDirectory(saveDir); } subDir = Dialog.getString(); if (nOverlays == slices) { makeMontage = Dialog.getCheckbox(); remSelections = Dialog.getCheckbox(); hideOverlays = Dialog.getCheckbox(); whiteBG = Dialog.getCheckbox(); columns = Dialog.getNumber(); rows = round(slices / columns); if (rows * columns < slices) rows += 1; border = Dialog.getNumber(); alsoStack = true; } /* end main options menu */ roiOptions = "save "; if (subDir != "") { if (!endsWith(saveDir, fS)) saveDir += fS; saveDir += subDir + fS; } if (!File.isDirectory(saveDir)) { if (File.exists(saveDir)) exit("Selected directory is a file not a directory"); else File.makeDirectory(saveDir); } call("ij.Prefs.set", "asc.lastsaved.roi.path", saveDir); if (roiTypeChoice != "tiff") roiOptions += roiTypeChoice; if (alsoStack) roiOptions += " show"; roiManager("Deselect"); if (saveROIList) roiManager("Save", saveDir + roiPrefix + "_RoiSet.zip"); if (expandN > 0) { stackList = ""; if (!diagnostics) setBatchMode("true"); for (i = 0; i < nROIs; i++){ showProgress(i, nROIs); roiManager("Select", i); run("Enlarge...", "enlarge=&expandN pixel"); run("Duplicate...", "title=temp"); run("Select None"); filePath = saveDir + RoiManager.getName(i) + "." + substring(roiTypeChoice, 0, 3); saveAs(roiTypeChoice, filePath); stackList += filePath + "\n"; close; roiManager("Deselect"); } if (alsoStack){ stackListPath = saveDir + RoiManager.getName(0) + "-" + RoiManager.getName(nROIs - 1) + ".txt"; File.saveString(stackList, stackListPath); wait(100); run("Stack From List...", "open=[" + stackListPath + "]"); roiStackPath = saveDir + originalImage + "_ROI-Stack"; saveAs("Tiff", roiStackPath); /* Use IJ tiff to retain selections */ } roiManager("Deselect"); IJ.log("ROIs saved in " + saveDir); } else { RoiManager.multiCrop(saveDir, roiOptions); if (alsoStack){ /* A stack named "CROPPED_ROI Manager" will now be available containing all the ROIs */ if (isOpen("CROPPED_ROI Manager")) selectWindow("CROPPED_ROI Manager"); else exit("RoiManager.multiCrop did not create stack as expected, currrent image is " + getTitle()); getVoxelSize(pixelWidthN, pixelHeightN, pixelDepthN, unitN); /* No scaling operations should be applied to the ROI saves */ if (unitN == "pixels") setVoxelSize(pixelWidth, pixelHeight, pixelDepth, unit); if (nOverlays == slices) { oROICropStackID = getImageID(); if (makeMontage) { rowCount = 1; montageTitle = originalImage + "_" + columns + "x" + rows + "Montage"; for (i = 0; inOverlays; i++) { selectImage(oROICropStackID); Overlay.activateSelection(i); if (i == 0) { getSelectionBounds(x, y, canvasWidth, canvasHeight); rowHeight = canvasHeight; yStart = 0; xStart = canvasWidth + border; run("Duplicate...", "title=&montageTitle"); } else { getSelectionBounds(x, y, sliceWidth, sliceHeight); if (i >= columns && xStart + border + sliceWidth > canvasWidth) { yStart = canvasHeight + border; rowHeight = 0; xStart = 0; rowCount++; } rowHeight = maxOf(rowHeight, sliceHeight); canvasHeight = yStart + rowHeight; canvasWidth = maxOf(canvasWidth, xStart + sliceWidth); Image.copy; selectWindow(montageTitle); run("Canvas Size...", "width=&canvasWidth height=&canvasHeight position=Top-Left"); wait(1); Image.paste(xStart, yStart); wait(1); xStart += border + sliceWidth; } } selectWindow(montageTitle); montageTitle = originalImage + "_" + columns + "\(ish\)x" + rowCount + "-Montage"; rename(montageTitle); if (hideOverlays) Overlay.hide; if (remSelections) run("Select None"); } if (whiteBG) { oForegroundColor = Color.foreground; selectImage(oROICropStackID); if (Overlay.size > 0) { for (i = 0; i < slices; i++) { Overlay.activateSelection(i); run("Make Inverse"); if (selectionType >= 0) { Color.setForeground("white"); run("Fill", "slice"); } } } else IJ.log("The stack backgrounds were not set to white because the expected overlays were not found"); Color.setForeground(oForegroundColor); } if (hideOverlays) Overlay.hide; if (remSelections) run("Select None"); } roiStackPath = saveDir + originalImage + "_ROI-Stack"; saveAs("Tiff", roiStackPath); /* Use IJ tiff to retain selections */ } else { if (isOpen("CROPPED_ROI Manager")){ selectWindow("CROPPED_ROI Manager"); close(); } } } IJ.log("ROIs saved in " + saveDir); setBatchMode("exit and display"); beep(); wait(100); beep(); wait(300); beep(); call("java.lang.System.gc"); showStatus("ROI export finished"); exit; } /* End of ROIs-only Dialog */ if (reduceDepth) { bitSuffix = "_" + bitChoice; if (bitChoice == "PAL") bitSuffix += d2s(palColors, 0); if (indexOf(originalImage, bitSuffix) < 0) nTitle = originalImage + bitSuffix; else nTitle = originalImage + "\(2\)"; run("Duplicate...", "duplicate"); /* format required to duplicate both stacks and single frame images */ rename(nTitle); tempImage = true; tempImageTitle = getTitle(); if (imageDepth == 16 || imageDepth == 32) { run("8-bit"); bitChange = "8-bit"; } else if (imageDepth == 24) { clipFound = false; if (iMMagick && bitChoice == "PAL" && slices == 1 && IJ.getFullVersion < "1.54h99") { /* Michael Schmid bit conversion improvement in 1.54h means that IM is only used for older versions */ for (i = 1; i < slices + 1; i++) { selectWindow(nTitle); setSlice(i); run("Copy to System"); dimWait = round(minOf(6000, maxOf(10, imageWidth * imageHeight * 10E-6))); showStatus("Waiting " + dimWait / 1000 + " secs for clipboard"); wait(dimWait); tempRecColPath = pathLengthCheck(tempDir + "tempRecCol.png", maxPathL); iMPNGSetPAL = " +dither -colors " + palColors; clipToPAL = "clipboard: " + iMPNGSetPAL; execString = qMark + iMMagickPath + qMarkSp + clipToPAL + spQMark + tempRecColPath + qMark; showStatus("Executing ImageMagick script"); exec(execString); showStatus("Waiting " + dimWait / 1000 + " secs for clip save"); wait(dimWait); clipFound = File.exists(tempRecColPath); if (clipFound) { if (tempImageTitle != "") closeImageByTitle(tempImageTitle); else tempImageTitle = originalImage + "_temp"; open(tempRecColPath); if (File.exists(tempRecColPath)) null = File.delete(tempRecColPath); } else { IJ.log("Failure in ImageMagick clipboard method, test with:\n" + execString); run("System Clipboard"); } rename(tempImageTitle); } String.copy(""); /* clear clipboard */ } if (!clipFound) { if (bitChoice == "PAL") run("8-bit Color", "number=" + palColors); else run("8-bit"); } bitChange = bitChoice; /* if this changes the background try expanding the canvas first so that the background dominates, then change the bit depth, then crop */ } } /* end Reduce depth section */ if (fileType == "APNG") run("APNG..."); else if (startsWith(fileType, "Image Seq")) run("Image Sequence... "); else if (fileType == "PNG" && !iMMagick) run("PNG..."); else if (fileType == "base64"){ lastSavedbase64Path = call("ij.Prefs.get", "asc.lastsaved.base64.path", "not found"); if (lastSavedbase64Path != "not found" && indexOf(lastSavedbase64Path, iJPath) < 0) { if (File.isFile(lastSavedbase64Path)) { sPath = File.getParent(lastSavedbase64Path); if (!endsWith(sPath, fS)) sPath += fS; saveDirs = Array.concat(saveDirs, "Last Saved base64 Directory: " + sPath); } } Dialog.create("Select base64 file save location"); fileName = replace(unCleanLabel(stripKnownExtensionFromString(getTitle)), "_temp", ""); base64Suffix = ""; if (scaleImage != 1) { base64Suffix += sPCLab; if (newDim != 0) { if (dimensionChoice == "width") base64Suffix += "_" + d2s(newDim, 0) + "w"; else base64Suffix += "_" + d2s(newDim, 0) + "h"; } if (indexOf(fileName, base64Suffix) >= 0) base64Suffix = ""; } if (base64Suffix != "") { if (!endsWith(fileName, base64Suffix)) fileName += base64Suffix; else fileName += "\(2\)"; } Dialog.addRadioButtonGroup("Pick base64 output directory...:", saveDirs, saveDirs.length, 1, saveDirs[0]); Dialog.addDirectory("or...Enter base64 output directory here:", ""); Dialog.addString("Filename \(prefix\):", fileName, Math.constrain(fileName.length, 40, 70)); Dialog.show(); dirChoice = Dialog.getRadioButton(); saveDir = Dialog.getString(); if (saveDir == ""){ if (startsWith(dirChoice, "Active")) saveDir = activeDir; else if (startsWith(dirChoice, "Current")) saveDir = currentDir; else if (startsWith(dirChoice, "Last Saved base64")) saveDir = sPath; else if (startsWith(dirChoice, "Last Opened")) saveDir = openedDir; } if (!File.isDirectory(saveDir)) { if (File.exists(saveDir)) exit("Selected directory \(" + saveDir + "\) is a file not a directory"); else { File.makeDirectory(saveDir); if (diagnostics) IJ.log("New directory created:" + saveDir); } } fileName = Dialog.getString; /* End for base64 Save Dialog */ if (!endsWith(saveDir, fS)) saveDir += fS; savebase64Path = pathLengthCheck(saveDir + fileName + ".b64", maxPathL); pixelCount = Image.height * Image.width; delayTime = 500 + pixelCount / 4E4; sTitle = getTitle(); for (i = 1; i < slices + 1; i++) { selectWindow(sTitle); setSlice(i); run("Copy to System"); dimWait = round(minOf(6000, maxOf(10, imageWidth * imageHeight * 10E-6))); showStatus("Waiting " + dimWait / 1000 + " secs for clipboard"); if (nSlices>1) savebase64Path = replace(savebase64Path, ".b64", "_" + i + ".b64"); wait(dimWait); execString = qMark + iMMagickPath + "\" clipboard: INLINE:PNG:\"" + savebase64Path + qMark; showStatus("Executing ImageMagick script: " + execString); exec(execString); if (nSlices>1){ showStatus("Waiting " + dimWait / 1000 + " secs for clip save before converting next slice"); wait(dimWait); } } } /* End of base64 output */ else if (fileType == "PNG" && iMMagick) { if (diagnostics) IJ.log("IM PNG initial options:" + iMPNGSettings); lastSavedPNGPath = call("ij.Prefs.get", "asc.lastsaved.png.path", "not found"); if (lastSavedPNGPath != "not found" && indexOf(lastSavedPNGPath, iJPath) < 0) { if (File.isFile(lastSavedPNGPath)) { pPath = File.getParent(lastSavedPNGPath); if (!endsWith(pPath, fS)) pPath += fS; saveDirs = Array.concat(saveDirs, "Last Saved PNG Directory: " + pPath); } } Dialog.create("Select PNG file save location"); fileName = replace(unCleanLabel(stripKnownExtensionFromString(getTitle)), "_temp", ""); pngSuffix = ""; if (scaleImage != 1) { pngSuffix += sPCLab; if (newDim != 0) { if (dimensionChoice == "width") pngSuffix += "_" + d2s(newDim, 0) + "w"; else pngSuffix += "_" + d2s(newDim, 0) + "h"; } if (indexOf(fileName, pngSuffix) >= 0) pngSuffix = ""; } if (bitChange != "") { bitSuffix = "_" + bitChange; if (indexOf(fileName, bitSuffix) < 0) pngSuffix += bitSuffix; if (endsWith(pngSuffix, "PAL")) pngSuffix += d2s(palColors, 0); } if (savePNGTransp) { transpSuffix = "_transp"; if (indexOf(fileName, transpSuffix) < 0) pngSuffix += transpSuffix; } if (pngSuffix != "") { if (!endsWith(fileName, pngSuffix)) fileName += pngSuffix; else fileName += "\(2\)"; } Dialog.addRadioButtonGroup("Pick output directory...:", saveDirs, saveDirs.length, 1, saveDirs[0]); Dialog.addDirectory("or...Enter output directory here:", ""); Dialog.addString("Filename \(prefix\):", fileName, Math.constrain(fileName.length, 40, 70)); if (imageWidth > 240) { /* Thumbnail section */ tPrefs = "asc.lastsaved.thumbs."; aR = imageWidth / imageHeight; thumbWidths = newArray(64, 128, 256, 512, 1024, 1280, minOf(1024, round(imageWidth / 3))); for (i = 0; i < 5; i++) thumbWidths = Array.concat(thumbWidths, round(thumbWidths[i] * aR)); for (i = 0; i < thumbWidths.length; i++) if (thumbWidths[i] > 0.9 * scaleImage * imageWidth) thumbWidths = Array.deleteValue(thumbWidths, thumbWidths[i]); thumbWidths = removeDuplicatesInArray(thumbWidths, true); thumbSizes = newArray(); for (i = 0; i < thumbWidths.length; i++) thumbSizes = Array.concat(thumbSizes, "" + thumbWidths[i] + xChar + d2s(round(thumbWidths[i] / aR), 0)); thumbSizes = Array.concat("All", thumbSizes); thumbOptions = newArray("Save PNG thumbnail", "PNG PAL Colors", "Save JPEG thumbnail", "Auto-crop thumbnail", "Use 1% fuzz for auto-crop"); thumbOptionChecks = newArray(call("ij.Prefs.get", tPrefs + "png", false), reduceDepth, call("ij.Prefs.get", tPrefs + "jpg", false), call("ij.Prefs.get", tPrefs + "trimThumb", false), call("ij.Prefs.get", tPrefs + "fuzzCrop", false)); if (reduceDepth) iMPNGSetPAL = " +dither -colors " + palColors; else iMPNGSetPAL = ""; Dialog.setInsets(10, 50, 0); Dialog.addCheckboxGroup(2, 3, thumbOptions, thumbOptionChecks); Dialog.addNumber("PAL colors", palColors, 0, 3, " if PAL selected above"); Dialog.addMessage("Thumbnail sizes \(Original: " + imageWidth + xChar + imageHeight + ". Note that the output sizes will be smaller if auto-cropped\)", infoFontSize, infoColor); Dialog.setInsets(0, 50, 0); if (thumbSizes.length > 7) Dialog.addCheckboxGroup(2, round(thumbSizes.length / 2), thumbSizes, Array.fill(newArray(thumbSizes.length), false)); else Dialog.addCheckboxGroup(1, thumbSizes.length, thumbSizes, Array.fill(newArray(thumbSizes.length), false)); } Dialog.show(); dirChoice = Dialog.getRadioButton(); selDir = Dialog.getString(); if (selDir != "") saveDir = selDir; else if (startsWith(dirChoice, "Active")) saveDir = activeDir; else if (startsWith(dirChoice, "Current")) saveDir = currentDir; else if (startsWith(dirChoice, "Last Saved PNG")) saveDir = pPath; else if (startsWith(dirChoice, "Last Opened")) saveDir = openedDir; if (!File.isDirectory(saveDir)) { if (File.exists(saveDir)) exit("Selected directory \(" + saveDir + "\) is a file not a directory"); else { File.makeDirectory(saveDir); if (diagnostics) IJ.log("New directory created:" + saveDir); } } fileName = Dialog.getString; if (imageWidth > 240) { saveThumbsPNG = Dialog.getCheckbox(); call("ij.Prefs.set", tPrefs + "png", saveThumbsPNG); savePNGThumbsPAL = Dialog.getCheckbox(); saveThumbsJPG = Dialog.getCheckbox(); call("ij.Prefs.set", tPrefs + "jpg", saveThumbsJPG); trimThumb = Dialog.getCheckbox(); call("ij.Prefs.set", tPrefs + "trimThumb", trimThumb); fuzzCrop = Dialog.getCheckbox(); call("ij.Prefs.set", tPrefs + "fuzzCrop", fuzzCrop); pngThumbsCols = Dialog.getNumber; thumbSelSizes = newArray(); for (i = 0, thumbN = 0, allThumbs = false; i < thumbSizes.length; i++) { if (i == 0) { if (Dialog.getCheckbox()) allThumbs = true; } else if (Dialog.getCheckbox() || allThumbs) { thumbSelSizes[thumbN] = replace(thumbSizes[i], xChar, "x"); thumbN++; } } } /* End for PNG Save Dialog */ if (!endsWith(saveDir, fS)) saveDir += fS; savePNGPath = pathLengthCheck(saveDir + fileName + ".png", maxPathL); saveAs("PNG", savePNGPath); pixelCount = Image.height * Image.width; delayTime = 500 + pixelCount / 4E4; showStatus("Waiting for " + delayTime / 1000 + "s for IJ PNG save recognition"); execString = qMark + iMMagickPath + qMarkSp + qMark + savePNGPath + qMarkSp + iMPNGSettings + spQMark + savePNGPath + qMark; if (diagnostics) IJ.log("IM PNG save execString:\n" + execString); showStatus("Saving png using ImageMagick"); exec(execString); if (File.exists(savePNGPath)) { call("ij.Prefs.set", "asc.lastsaved.png.path", savePNGPath); if (reduceDepth) closeImageByTitle(fileName + ".png"); /* Closes original PAL PNG image */ if (iMMagick && imageWidth > 240) { if (thumbSelSizes.length > 0 && (saveThumbsPNG || saveThumbsJPG)) { cropSettings = ""; thumbMod = ""; orSizeString = "" + imageWidth + "x" + imageHeight; /* this ASC image size string is no longer relevant for thumbnails */ thumbFilename = replace(fileName, orSizeString, ""); thumbFilename = unCleanLabel(thumbFilename); saveThumbPre = pathLengthCheck(saveDir + stripKnownExtensionFromString(thumbFilename), maxPathL); if (fuzzCrop && trimThumb) cropSettings += " -fuzz 1%"; if (trimThumb) { cropSettings += " -trim +repage "; thumbMod = "-Trim"; if (fuzzCrop) thumbMod += "Fz1"; } if (savePNGThumbsPAL) iMPNGSetPAL = " +dither -colors " + pngThumbsCols; else iMPNGSetPAL = ""; if (savePNGTransp) { iMPNGSetTransp = " -transparent " + pngTranspSet + " -background " + pngMatteSet + " "; if (fuzz > 0) iMPNGSetTransp += "-fuzz " + fuzz + "% "; } else iMPNGSetTransp = ""; for (i = 0; i < thumbN; i++) { if (saveThumbsPNG) { thumbPSavePath = pathLengthCheck("" + saveThumbPre + "_" + thumbSelSizes[i] + thumbMod + ".png", maxPathL); thumbPSavePath = pathOverwriteCheck(thumbPSavePath); thumbPExec = qMark + iMMagickPath + qMark + " " + qMark + savePNGPath + qMark + cropSettings + " -resize " + thumbSelSizes[i] + iMPNGSettings + iMPNGSetPAL + iMPNGSetTransp + " " + qMark + thumbPSavePath + qMark; showStatus("Saving " + thumbSelSizes[i] + " PNG thumbnail using ImageMagick"); if (diagnostics) IJ.log("IM PNG " + thumbSelSizes[i] + " thumbnail save execString:\n" + thumbPExec); exec(thumbPExec); } if (saveThumbsJPG) { thumbJSavePath = pathLengthCheck("" + saveThumbPre + "_" + thumbSelSizes[i] + thumbMod + ".jpg", maxPathL); thumbJSavePath = pathOverwriteCheck(thumbJSavePath); iMJPEGSettings = " -quality " + jpegQual + " "; thumbJExec = qMark + iMMagickPath + qMark + " " + qMark + savePNGPath + qMark + cropSettings + " -resize " + thumbSelSizes[i] + iMJPEGSettings + " " + qMark + thumbJSavePath + qMark; showStatus("Saving JPEG " + thumbSelSizes[i] + " thumbnail using ImageMagick"); if (diagnostics) IJ.log("IM JPEG " + thumbSelSizes[i] + " thumbnail save execString:\n" + thumbJExec); exec(thumbJExec); } } } } } } /* End of IM PNG output */ else if (endsWith(fileType, "GIF")) { if (nSlices > 1) lastSavedGIFPath = File.getParent(call("ij.Prefs.get", "asc.lastsaved.gifanim.path", "not found")); else lastSavedGIFPath = File.getParent(call("ij.Prefs.get", "asc.lastsaved.gif.path", "not found")); if (lastSavedGIFPath != "not found" && indexOf(lastSavedGIFPath, "plugins" + fS + "Scripts") < 0 && File.isDirectory(lastSavedGIFPath)) saveDirs = Array.concat(saveDirs, "Last Saved GIF Directory: " + lastSavedGIFPath); openImages = getList("image.titles"); openNonStacks = newArray(); for (i = 0; i < lengthOf(openImages); i++) { selectWindow(openImages[i]); if (nSlices == 1) openNonStacks = Array.concat(openNonStacks, openImages[i]); } fileName = unCleanLabel(stripKnownExtensionFromString(getTitle)); Dialog.create("Animated GIF save options \(" + macroL + "\)"); Dialog.addRadioButtonGroup("Select output directory: ", saveDirs, saveDirs.length, 1, saveDirs[0]); Dialog.setInsets(0, 20, 0); Dialog.addDirectory(" . . . or select output directory:", ""); Dialog.setInsets(0, 0, 0); Dialog.addString("Filename \(prefix\):", fileName, Math.constrain(fileName.length, 40, 70)); if (nSlices > 1) { gifSaveSuffix = call("ij.Prefs.get", "asc.lastsaved.gif.suffix", "_anim"); Dialog.setInsets(0, 0, 0); Dialog.addString("Filename suffix:", "_anim", 10); } Dialog.setInsets(0, 0, 0); Dialog.addString("Override with full path:", "--no, use entries above--", Math.constrain(fileName.length, 40, 70)); if (!gifsicleGo) { Dialog.setInsets(0, 0, 0); Dialog.addMessage("GIF transparency options:_________________________", infoFontSize - 1, dividerColor); if (medianBGI == 255) defTranspN = 255; else if (medianBGI == 0) defTranspN = 0; else defTranspN = -1; Dialog.setInsets(0, 0, 0); Dialog.addNumber("Index of Transparent Layer", defTranspN, 0, 5, "-1 for no transparency"); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Transparency index to revert to AFTER save", -1, 0, 5, "should be -1 for no transparency"); Dialog.setInsets(0, 0, 0); Dialog.addMessage("This macro has additional functionality if Gifsicle is installed", infoFontSize, infoColor); } else { /* Gifsicle options */ if (diagnostics) IJ.log("GIF compression uses " + gSPath); colorMaps = newArray("none", "web", "gray", "bw", "lut.txt or other gif"); Dialog.setInsets(0, 20, 0); Dialog.addRadioButtonGroup("Alternative color map:", colorMaps, 1, colorMaps.length, colorMaps[0]); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Number of colors", 256, 0, 4, "\('256' = no change\)"); /* For simplicity the color reduction is set to use the median cut algorithm described by Heckbert: https://www.lcdf.org/gifsicle/man.html. */ mattes = newArray("Colormap index", "# value", "RGB values", "black", "white"); transparencies = Array.concat("None", mattes); mattes = Array.concat("Same", mattes); Dialog.setInsets(0, 20, 0); Dialog.addRadioButtonGroup("Transparency setting __________\(Colormap index \(0-255\), # value i.e. " + medianBGIHexName + "\)", transparencies, 1, transparencies.length, transparencies[0]); Dialog.setInsets(0, 20, 0); Dialog.addRadioButtonGroup("Matte color __________\('Same': Transparency color\)", mattes, 1, mattes.length, mattes[0]); if (nSlices > 1) { disposals = newArray("none \(frames build on previous\)", "background \(clear to background\)", "previous \(preserve background canvas\)"); Dialog.setInsets(0, 20, 0); Dialog.addRadioButtonGroup("Image disposal method __________", disposals, 1, disposals.length, disposals[0]); Dialog.setInsets(0, 20, 0); Dialog.addNumber("Delay between frames:", 200, 0, 5, "mS"); Dialog.setInsets(0, 20, 0); Dialog.addNumber("Animation plays:", 0, 0, 5, "\'0' is continuous \(default\)"); } Dialog.setInsets(5, 20, 0); gsTweaks = newArray("Careful mode \(use if the gifsicle results are corrupted\)", "Interlace", "Duplicate full size with lossy compression?"); /* Originally "Set 2 individual frame delays" was an option but I could not get that to work. "More delays" was the check option. */ gsTweakChecks = newArray(0, 0, 1); Dialog.addCheckboxGroup(2, 2, gsTweaks, gsTweakChecks); Dialog.setInsets(0, 20, 0); Dialog.addNumber("Lossiness:", 20, 0, 3, "% Default is 20 but even 80 can work well \(higher = more compression\)"); Dialog.setInsets(0, 20, 0); Dialog.addCheckbox("Save additional resized images \(opens new options window\)", false); Dialog.addString("Comment", "Creator: " + getInfo("user.name")); } Dialog.show; dirChoice = Dialog.getRadioButton(); selDir = Dialog.getString(); if (selDir != "") saveDir = selDir; else if (startsWith(dirChoice, "Active")) saveDir = activeDir; else if (startsWith(dirChoice, "Current")) saveDir = currentDir; else if (startsWith(dirChoice, "Last Saved GIF")) saveDir = lastSavedGIFPath; else if (startsWith(dirChoice, "Last Opened")) saveDir = openedDir; if (!endsWith(saveDir, fS)) saveDir += fS; if (!File.isDirectory(saveDir)) { if (File.exists(saveDir)) exit("Selected directory \(" + saveDir + "\) is a file not a directory"); else { File.makeDirectory(saveDir); if (diagnostics) IJ.log("New directory created:" + saveDir); } } savePrefix = Dialog.getString(); if (nSlices > 1) { gifSaveSuffix = Dialog.getString(); call("ij.Prefs.set", "asc.lastsaved.gif.suffix", gifSaveSuffix); } else gifSaveSuffix = ""; savePathOverride = Dialog.getString(); if (!startsWith(savePathOverride, "--no")) gifSavePath = savePathOverride; else { gifSavePath = saveDir + savePrefix; if (!endsWith(gifSavePath, gifSaveSuffix)) gifSavePath += gifSaveSuffix; } if (!gifsicleGo) { setTranspIndexGIF = Dialog.getNumber(); /* sets transparency index for saved file */ restoreTranspIndexGIF = Dialog.getNumber(); /* restores transparency index for gif format */ } else { colormap = Dialog.getRadioButton(); colorN = maxOf(2, minOf(256, Dialog.getNumber())); transparency = Dialog.getRadioButton(); matte = Dialog.getRadioButton(); if (nSlices > 1) { disposal = Dialog.getRadioButton(); disposal = substring(disposal, 0, indexOf(disposal, "\(") - 1); frameDelay = round (Dialog.getNumber() / 10); /* gifsicle delay in in 1/100 secs. Must be an integer */ loopcount = Dialog.getNumber(); } // moreDelays = Dialog.getCheckbox(); /* Could not get this to work */ careful = Dialog.getCheckbox(); interlaced = Dialog.getCheckbox(); lossyFull = Dialog.getCheckbox(); lossG = round(Dialog.getNumber()); reducedSizes = Dialog.getCheckbox(); if (reducedSizes) { Dialog.create("Animated GIF reduced size save options \(" + macroL + "\)"); resizeOptions = newArray("As selected below", "All options below", "No additional resized images"); Dialog.addRadioButtonGroup("Resizing options:_______________", resizeOptions, 1, 3, resizeOptions[0]); rMethods = newArray("sample", "mix", "box", "catrom", "mitchell", "lanczos2", "lanczos3"); Dialog.addChoice("Method for resizing:", rMethods, "lanczos3"); Dialog.addCheckbox("Use lossy compression setting below for resized GIFs?", false); Dialog.addNumber("Lossiness:", 20, 0, 3, "% Default is 20 but even 80 can work well \(higher = more compression\)"); Dialog.setInsets(0, 20, 10); Dialog.addMessage("Scaling using lanczos3 is generally more accurate but more CPU-intensive than Catmull-Rom/bicubic in ImageJ", infoFontSize, infoColor); rFactorsBase = newArray(0.75, 0.5, 1 / 3, 0.25, 0.2, 0.125, 0.1, 0.075, 0.05, 0.025, 0.0125, 0.01, 0.0075, 0.005, 0.0025, 0.00125); getDimensions(imageWidth, imageHeight, null, null, null); rFactorsDefault = newArray(); rFactorsDefaultW = newArray(); rFactorsDefaultH = newArray(); lrFB = lengthOf(rFactorsBase); for (i = 0, j = 0; i < lrFB; i++) { newDim = round(imageWidth * rFactorsBase[i]); if (newDim < 1025 && newDim > 125) { rFactorsDefault[j] = rFactorsBase[i]; rFactorsDefaultW[j] = newDim; rFactorsDefaultH[j] = round(imageHeight * rFactorsBase[i]); if (newDim < 125) i = lrFB; j++; } } for (i = 0; i < lengthOf(rFactorsDefault); i++) { Dialog.addCheckbox(i + ". Save compressed reduced size GIF:", true); Dialog.addNumber(i + "._Save_GIF copy at", rFactorsDefault[i], 6, 8, "scale \(" + 100 * rFactorsDefault[i] + "%, width: " + rFactorsDefaultW[i] + ", height: " + rFactorsDefaultH[i] + "\)"); } Dialog.show(); resizes = Dialog.getRadioButton(); if (resizes == "No additional resized images") reducedSizes = false; else { resizeMethod = Dialog.getChoice(); lossyResized = Dialog.getCheckbox(); lossGR = maxOf(0, minOf(100, Dialog.getNumber())); if (lossGR == 0) lossyResized = false; rChecks = newArray(); diaRFactors = newArray(); for (i = 0; i < lengthOf(rFactorsDefault); i++) { rChecks[i] = Dialog.getCheckbox(); diaRFactors[i] = Dialog.getNumber(); } rFactors = newArray(); for (i = 0; i < lengthOf(rFactorsDefault); i++) { if (resizes == "All options below") rChecks[i] = true; else if (resizes == "No additional resized images") rChecks[i] = false; if (rChecks[i]) { if (diaRFactors[i] > 0 && diaRFactors[i] < 1) rFactors = Array.concat(rFactors, diaRFactors[i]); } } if (rFactors.length == 0 && !lossyFull && startsWith(transparency, "No")) gifsicleGo = false; } } // if (moreDelays) { /* I could not get this to work but I left this in here for someone smarter to fix */ // Dialog.create("Sets additional frame delays"); // Dialog.addMessage("Overall delay time is " + delay * 10 + " mS, there are " + nSlices + " frames", infoFontSize, infoColor); // Dialog.addNumber("Optional delay 1", delay * 10, 0, 5, "mS"); // Dialog.addNumber("Frame using delay 1", 0, 0, 5, "#"); // Dialog.addNumber("Optional delay 2", delay * 10, 0, 5, "mS"); // Dialog.addNumber("Frame using delay 2", nSlices - 1, 0, 5, "#"); // Dialog.show(); // delay1FrameT = Dialog.getNumber() / 10; // delay1Frame = Dialog.getNumber(); // delay2FrameT = Dialog.getNumber() / 10; // delay2Frame = Dialog.getNumber(); // moreDelaysC = "-d" + delay1FrameT + " " + qMark + "#" + delay1Frame + qMark + " -d" + delay2FrameT + " " + qMark + "#" + delay2Frame + qMark + " "; // } } /* end of Gifsicle options */ if (!endsWith(toLowerCase(gifSavePath), ".gif")) gifSavePath += ".gif"; endIndex = lastIndexOf(gifSavePath, ".gif"); gifSavePath = pathLengthCheck(gifSavePath, maxPathL); filePathG1 = substring(gifSavePath, 0, endIndex); if (!gifsicleGo) { run("Input/Output...", "gif=" + setTranspIndexGIF + " file=.csv save copy_column copy_row save_column save_row"); /* sets transparency index for saved file */ saveAs("Gif", gifSavePath); if (diagnostics) IJ.log("Defaults ImageJ GIF saved:" + gifSavePath); run("Input/Output...", "jpeg=85 gif=" + restoreTranspIndexGIF + " file=.csv save copy_column copy_row save_column save_row"); /* restores transparency index for gif format, unfortunately all the other default options have to be reapplied otherwise it resets them */ } else { /* Start gifsicle output */ saveAs("Gif", gifSavePath); if (diagnostics) IJ.log("Default ImageJ GIF saved:" + gifSavePath); gSCommands = " -b -O3 "; if (careful) gSCommands += "--careful "; if (interlaced) gSCommands += "--interlace "; if (diagnostics) gSCommands += "-V "; if (startsWith(colormap, "lut")) { mapLUT = File.openDialog("Enter LUT text file or a GIF file path i.e. " + getDirectory("luts")); colormap = qMark + mapLUT + qMark; if (colormap != "none") gSCommands += "--use-colormap=" + colormap + " "; } if (colorN < 256) gSCommands += "--colors=" + colorN + " --color-method=median-cut "; if (nSlices > 1) { gSCommands += "--disposal=" + disposal + " "; if (frameDelay > 0) gSCommands += "--delay=" + frameDelay + " "; if (moreDelays) gSCommands += moreDelaysC; gSCommands += "--loopcount=" + loopcount + " "; } if (startsWith(transparency, "Colormap")) transparent = minOf(255, round(getNumber("Colormap index \(0-255\) for transparent color", 0))); else if (startsWith(transparency, "# value")) transparent = getString("Hexadecimal transparent color #", medianBGIHexName); else if (startsWith(transparency, "RGB")) transparent = getString("R,G,B values for transparent color", "255,255,255"); else if (startsWith(transparency, "white")) transparent = "255,255,255"; else if (startsWith(transparency, "black")) transparent = "0,0,0"; else if (!startsWith(transparency, "No")) transparent = getString("Transparent color: Enter index, # or R,G,B values", "No"); if (!startsWith(transparency, "No")) { gSCommands += "--transparent " + transparent + " "; if (startsWith(matte, "Same")) matteColor = transparent; else if (startsWith(matte, "Colormap")) matteColor = minOf(255, round(getNumber("Colormap index \(0-255\) for matte color", 0))); else if (startsWith(matte, "# value")) matteColor = getString("Hexadecimal matte color #", medianBGIHexName); else if (startsWith(matte, "RGB")) matteColor = getString("R,G,B values for matte color", "255,255,255"); else if (startsWith(matte, "white")) matteColor = "255,255,255"; else if (startsWith(matte, "black")) matteColor = "0,0,0"; else matteColor = 0; gSCommands += "--background " + matteColor + " "; } gifFilePath1 = pathLengthCheck(filePathG1 + "_GS.gif", maxPathL); gifCommandPaths1 = gifSavePath + gSSep + gifFilePath1; gsComm = gSPath + gSCommands + qMark + gifCommandPaths1 + qMark; if (diagnostics) IJ.log("Gifsicle #1\n" + gsComm); exec(gsComm); if (diagnostics) { if (File.exists(gifFilePath1)) IJ.log("Gifsicle #1 successfully saved"); else IJ.log("Gifsicle #1 not saved"); } if (lossyFull) { gifFilePath2 = pathLengthCheck(filePathG1 + "_loss" + lossG + ".gif", maxPathL); gifCommandPaths2 = gifSavePath + gSSep + gifFilePath2; gsComm = gSPath + gSCommands + "--lossy=" + lossG + " " + qMark + gifCommandPaths2 + qMark; if (diagnostics) IJ.log("Gifsicle #2\n" + gsComm); exec(gsComm); if (diagnostics){ if (File.exists(gifFilePath2)) IJ.log ("Gifsicle #2 successfully saved"); else IJ.log("Gifsicle #2 not saved"); } } if (reducedSizes) { gSCommands += "--resize-method=" + resizeMethod + " "; if (rFactors.length > 0) { for (i = 0; i < lengthOf(rFactors); i++) { if (diagnostics) IJ.log("rFactor processing: " + rFactors[i]); if (lossyResized) { lossyPath = pathLengthCheck(filePathG1 + "_loss" + lossGR + "scale" + rFactors[i] + ".gif", maxPathL); lossyCommandPaths1 = gifSavePath + gSSep + lossyPath; gsComm = gSPath + gSCommands + "--lossy=" + lossGR + " " + "--scale=" + rFactors[i] + " " + qMark + lossyCommandPaths1 + qMark; } else { rPath = pathLengthCheck(filePathG1 + " scale" + rFactors[i] + ".gif", maxPathL); gifCommandPathsR = gifSavePath + gSSep + rPath; gsComm = gSPath + gSCommands + "--scale=" + rFactors[i] + " " + qMark + gifCommandPathsR + qMark; } if (diagnostics) IJ.log("Gifsicling with: \n" + gsComm); exec(gsComm); } } } if (File.exists(gifSavePath)) { if (nSlices > 1) call("ij.Prefs.set", "asc.lastsaved.gifanim.path", gifSavePath); else call("ij.Prefs.set", "asc.lastsaved.gif.path", gifSavePath); } } /* End of Gifsicle save */ } /* End of Gif save */ else if (startsWith(fileType, "TIFF")) { lastSavedTIFFPath = call("ij.Prefs.get", "asc.lastsaved.compressedtiff.path", "not found"); if (lastSavedTIFFPath != "not found") { lastSavedTIFFPath = File.getParent(lastSavedTIFFPath); saveDirs = Array.concat(saveDirs, "Last Saved TIFF Directory: " + lastSavedTIFFPath); } // dirListing += "Directory selected below"; // if (endsWith(dirListing,"\n")) = dirListing = substring(dirListing,0,lastIndexOf(dirListing,"\n")); if (fileType == "TIFF-ZIP") compression = ".tiff.zip"; else compression = "_lzw"; fileName = unCleanLabel(stripKnownExtensionFromString(getTitle)); if (scaleImage != 1) { fileName += sPCLab; if (newDim != 0) { if (dimensionChoice == "width") fileName += "_" + d2s(newDim, 0) + "w"; else fileName += "_" + d2s(newDim, 0) + "h"; } } suffix = ""; // if (scaleImage!=1) suffix = sPCLab; if (bitChange != "") suffix += "_" + bitChange; suffix += compression; Dialog.create("Select TIFF format save location \(" + macroL + "\)"); Dialog.addRadioButtonGroup("Pick output directory here . . .", saveDirs, saveDirs.length, 1, saveDirs[0]); Dialog.addDirectory(". . . or set output directory:", ""); Dialog.addString("In new sub-folder:", "", Math.constrain(fileName.length, 40, 90)); Dialog.setInsets(0, 68, 10); // Dialog.addMessage("Default directory above: " + saveDir); Dialog.addString("Filename \(prefix\):", fileName, Math.constrain(fileName.length, 40, 90)); Dialog.addString("Process descriptors \(suffix\):", suffix, Math.constrain(suffix.length, 25, 90)); if (startsWith(fileName, "Result_of_")) Dialog.addCheckbox("Remove 'Result_of_' prefix?", true); if (startsWith(fileName, "Mask_of_")) Dialog.addCheckbox("Remove 'Mask_of_' prefix?", true); Dialog.addString("Override with full path:", "--no...use entries above...--", Math.constrain(fileName.length, 40, 90)); bonusSaves = newArray(); bonusSaveChecks = newArray(); if (bitDepth == 24) { bonusSaves = Array.concat(bonusSaves, "Save separate R,G and B channels instead of color TIFF"); bonusSaveChecks = Array.concat(bonusSaveChecks, false); bonusSaves = Array.concat(bonusSaves, "Save separate R,G and B channels in addition to color TIFF"); bonusSaveChecks = Array.concat(bonusSaveChecks, false); } tSize = " \(" + round(scaleImage * imageWidth) + xChar + round(scaleImage * imageHeight) + "\)"; if (imageWidth < 65500 && imageHeight < 65500) { bonusSaves = Array.concat(bonusSaves, "Also save jpeg file" + tSize); bonusSaveChecks = Array.concat(bonusSaveChecks, jpegToo); } else if (iMMagick) { bonusSaves = Array.concat(bonusSaves, "Also save jp2 file" + tSize); bonusSaveChecks = Array.concat(bonusSaveChecks, jp2Too); } if (lengthOf(bonusSaves) > 0) { Dialog.setInsets(10, 50, 0); Dialog.addCheckboxGroup(lengthOf(bonusSaves), 1, bonusSaves, bonusSaveChecks); Dialog.setInsets(0, 0, 0); if (imageWidth < 65500 && imageHeight < 65500) Dialog.addNumber("IM jpeg quality", jpegQual, 0, 3, "/100"); else if (iMMagick) Dialog.addNumber("IM jp2 quality", jp2Qual, 0, 3, "zero is lossless"); /* Size exceeds jpeg limit so jp2 will be saved */ } alsoSave2s = newArray("Also save png file" + tSize); alsoSave2Checks = newArray("" + pngToo); if (iMMagick) { if (bitDepth == 24) { alsoSave2s = Array.concat(alsoSave2s, "Also save color table file to the same location \(palette tool\)"); alsoSave2Checks = Array.concat(alsoSave2Checks, colorTableToo); } alsoSave2s = Array.concat(alsoSave2s, "Also save 64" + xChar + "64 Windows Icon"); alsoSave2Checks = Array.concat(alsoSave2Checks, iconToo); } if (scaleImage != 1) { alsoSave2s = Array.concat(alsoSave2s, "Close scaled image after saving?"); alsoSave2Checks = Array.concat(alsoSave2Checks, true); } if (lengthOf(alsoSave2s) > 0) { Dialog.setInsets(10, 50, 0); Dialog.addCheckboxGroup(lengthOf(alsoSave2s), 1, alsoSave2s, alsoSave2Checks); } if (iMMagick && imageWidth > 240) { /* Thumbnail section */ tPrefs = "asc.lastsaved.thumbs."; aR = imageWidth / imageHeight; thumbWidths = newArray(64, 128, 256, 512, 1024, 1280, minOf(1024, round(imageWidth / 3))); for (i = 0; i < 5; i++) thumbWidths = Array.concat(thumbWidths, round(thumbWidths[i] * aR)); for (i = 0; i < thumbWidths.length; i++) if (thumbWidths[i] > 0.9 * scaleImage * imageWidth) thumbWidths = Array.deleteValue(thumbWidths, thumbWidths[i]); thumbWidths = removeDuplicatesInArray(thumbWidths, true); thumbSizes = newArray(); for (i = 0; i < thumbWidths.length; i++) thumbSizes = Array.concat(thumbSizes, "" + thumbWidths[i] + xChar + d2s(round(thumbWidths[i] / aR), 0)); thumbSizes = Array.concat("All", thumbSizes); thumbOptions = newArray("Save PNG thumbnail", "PNG PAL Colors", "Save JPEG thumbnail", "Auto-crop thumbnail", "Use 1% fuzz for auto-crop"); thumbOptionChecks = newArray(call("ij.Prefs.get", tPrefs + "png", false), reduceDepth, call("ij.Prefs.get", tPrefs + "jpg", false), call("ij.Prefs.get", tPrefs + "trimThumb", false), call("ij.Prefs.get", tPrefs + "fuzzCrop", false)); Dialog.setInsets(10, 50, 0); Dialog.addCheckboxGroup(2, 3, thumbOptions, thumbOptionChecks); Dialog.addNumber("PAL colors", palColors, 0, 3, " if PAL selected above"); Dialog.addMessage("Thumbnail sizes: \(note that the output sizes will be smaller if auto-cropped\)", infoFontSize, dividerColor); Dialog.setInsets(0, 50, 0); if (thumbSizes.length > 7) Dialog.addCheckboxGroup(2, round(thumbSizes.length / 2), thumbSizes, Array.fill(newArray(thumbSizes.length), false)); else Dialog.addCheckboxGroup(1, thumbSizes.length, thumbSizes, Array.fill(newArray(thumbSizes.length), false)); } Dialog.show; dirChoice = Dialog.getRadioButton(); selDir = Dialog.getString(); if (selDir != "") saveDir = selDir; else if (startsWith(dirChoice, "Active")) saveDir = activeDir; else if (startsWith(dirChoice, "Current")) saveDir = currentDir; else if (startsWith(dirChoice, "Last Saved TIFF")) saveDir = lastSavedTIFFPath; else if (startsWith(dirChoice, "Last Opened")) saveDir = openedDir; else if (startsWith(dirChoice, "Working Directory")) saveDir = workingDir; if (!endsWith(saveDir, fS)) saveDir += fS; if (!File.isDirectory(saveDir)) { if (File.exists(saveDir)) exit("Selected directory is a file not a directory"); else { File.makeDirectory(saveDir); if (diagnostics) IJ.log("New directory created:" + saveDir); } } subFolder = Dialog.getString(); if (subFolder != "") { saveDir += subFolder; if (!endsWith(saveDir, fS)) saveDir += fS; if (!File.isDirectory(saveDir)) { if (File.exists(saveDir)) exit("Selected directory is a file not a directory"); else { File.makeDirectory(saveDir); if (!File.isDirectory(saveDir)) exit("Failed to create subfolder: " + saveDir); else if (diagnostics) IJ.log("New directory created:" + saveDir); } } } savePrefix = Dialog.getString(); saveSuffix = Dialog.getString(); if (startsWith(fileName, "Result_of_")) if (Dialog.getCheckbox) savePrefix = replace(savePrefix, "Result_of_", ""); if (startsWith(fileName, "Mask_of_")) if (Dialog.getCheckbox) savePrefix = replace(savePrefix, "Mask_of_", ""); savePathOverride = Dialog.getString(); splitSave = false; splitSaveToo = false; if (bitDepth == 24) { splitSave = Dialog.getCheckbox(); splitSaveToo = Dialog.getCheckbox(); } if (imageWidth < 65500 && imageHeight < 65500) { jpegToo = Dialog.getCheckbox(); jpegQual = Dialog.getNumber(); } else if (iMMagick) { jp2Too = Dialog.getCheckbox(); jp2Qual = Dialog.getNumber(); } /* alsoSave2 section */ pngToo = Dialog.getCheckbox(); if (iMMagick) { if (bitDepth == 24) colorTableToo = Dialog.getCheckbox(); iconToo = Dialog.getCheckbox(); } if (scaleImage != 1) closeScaledImage = Dialog.getCheckbox(); /* Thumbnails section */ if (iMMagick && imageWidth > 240) { saveThumbsPNG = Dialog.getCheckbox(); call("ij.Prefs.set", tPrefs + "png", saveThumbsPNG); savePNGThumbsPAL = Dialog.getCheckbox(); saveThumbsJPG = Dialog.getCheckbox(); call("ij.Prefs.set", tPrefs + "jpg", saveThumbsJPG); trimThumb = Dialog.getCheckbox(); call("ij.Prefs.set", tPrefs + "trimThumb", trimThumb); fuzzCrop = Dialog.getCheckbox(); call("ij.Prefs.set", tPrefs + "fuzzCrop", fuzzCrop); pngThumbsCols = Dialog.getNumber; thumbSelSizes = newArray(); for (i = 0, thumbN = 0, allThumbs = false; i < thumbSizes.length; i++) { if (i == 0) { if (Dialog.getCheckbox()) allThumbs = true; } else if (Dialog.getCheckbox() || allThumbs) { thumbSelSizes[thumbN] = replace(thumbSizes[i], xChar, "x"); thumbN++; } } } /* *** End of Tif and bonus save dialog *** */ if (scaleImage != 1) { showStatus("Scaling image"); run("Scale...", "x=" + scaleImage + " y=" + scaleImage + " interpolation=" + interpIJ + " average create"); scaledImageID = getImageID(); } saveName = unCleanLabel(savePrefix); if (!endsWith(saveName, saveSuffix)) saveName += saveSuffix; if (!startsWith(savePathOverride, "--no")) savePath = savePathOverride; else savePath = saveDir + saveName; lzwSaved = false; pixelCount = Image.height * Image.width; delayTime = 500 + pixelCount / 4E4; if (fileType == "TIFF-ZIP") saveAs("ZIP", savePath); else if (fileType == "TIFF-LZW") { if (diagnostics) IJ.log("saveTIFFPath before extension strip: " + savePath); saveTIFFPath = stripKnownExtensionFromString(savePath); if (diagnostics) IJ.log("saveTIFFPath after extension strip: " + saveTIFFPath); showStatus("Saving uncompressed TIFF using ImageJ to be used by IM"); preCTitle = getTitle; if (splitSave || splitSaveToo) { chans = newArray("(red)", "(green)", "(blue)"); for (s = 0; s < 3; s++) { if (s == 0) setRGBWeights(1, 0, 0); else if (s == 1) setRGBWeights(0, 1, 0); else setRGBWeights(0, 0, 1); // selectWindow("splitTemp " + chans[s]); chans[s] = pathLengthCheck(saveTIFFPath + chans[s] + "_lzw.tif", maxPathL); chans[s] = pathOverwriteCheck(chans[s]); run("8-bit"); saveAs("Tiff", chans[s]); // close(); run("Undo"); } rename(preCTitle); } if (!splitSave) { if (diagnostics) IJ.log("saveTIFFPath: " + saveTIFFPath); saveTIFFPath = pathLengthCheck(saveTIFFPath + "_lzw.tif", maxPathL); saveTIFFPath = pathOverwriteCheck(saveTIFFPath); saveAs("TIFF", saveTIFFPath); } if (jpegToo && !iMMagick) { showStatus("Saving jpeg using ImageJ"); jpegSavePath = replace(savePath, "_lzw", ""); jpegSavePath = replace(jpegSavePath, "lzw", ""); jpegSavePath = pathLengthCheck("" + jpegSavePath + ".jpg", maxPathL); jpegSavePath = pathOverwriteCheck(jpegSavePath); run("Input/Output...", "jpeg=" + jpegQual + " file=.csv save copy_column copy_row save_column save_row"); saveAs("Jpeg", jpegSavePath); wait(delayTime / 10); if (File.exists(jpegSavePath)) jpegToo = false; } else { /* saving the jpeg provides enough delay for the filesystem to fully register the TIFF - otherwise add this . . . */ if (splitSave || splitSaveToo) delayTime /= 3; showStatus("Waiting for " + round(delayTime / 1000) + "s for uncompressed save recognition"); wait(delayTime); } settings = " -compress lzw "; if (splitSave || splitSaveToo) { for (s = 0; s < 3; s++) { if (!File.exists(chans[s])) IJ.log(chans[s] + " not found for compressing channels"); else { execString = qMark + iMMagickPath + qMarkSp + qMark + chans[s] + qMarkSp + settings + spQMark + chans[s] + qMark; showStatus("Re-saving using ImageMagick: " + execString); exec(execString); call("ij.Prefs.set", "asc.lastsaved.compressedtiff.path", saveTIFFPath); } } if (File.exists(chans[0])) call("ij.Prefs.set", "asc.lastsaved.compressedtiff.path", saveTIFFPath); /* useful to update this */ } if (!splitSave) { execString = qMark + iMMagickPath + qMarkSp + qMark + saveTIFFPath + qMarkSp + settings + spQMark + saveTIFFPath + qMark; if (!File.exists(saveTIFFPath)) exit("No TIFF file for further saves"); showStatus("Re-saving using ImageMagick: " + execString); exec(execString); // IJ.log(execString); showStatus("Re-saved using ImageMagick: " + execString); if (!File.exists(saveTIFFPath)) IJ.log("Failed to save:\n" + saveTIFFPath + "\nFailed command line: \n" + saveTIFFPath); else { // IJ.log("Saved " + savePath); call("ij.Prefs.set", "asc.lastsaved.compressedtiff.path", saveTIFFPath); lzwSaved = true; } } } else exit("TIFF compression not as expected"); savePath = replace(savePath, "_lzw", ""); savePath = replace(savePath, "lzw", ""); if (jpegToo && iMMagick) { jpegSavePath = pathLengthCheck("" + savePath + ".jpg", maxPathL); jpegSavePath = pathOverwriteCheck(jpegSavePath); if (lzwSaved) { iMJPEGSettings = " -quality " + jpegQual + " "; // showStatus("Waiting for " + delayTime/1000 + "s for lzw save recognition"); // wait(delayTime); execString = qMark + iMMagickPath + qMarkSp + qMark + saveTIFFPath + qMarkSp + iMJPEGSettings + spQMark + jpegSavePath + qMark; showStatus("Saving jpeg using ImageMagick"); exec(execString); if (!File.exists(jpegSavePath)) { IJ.log("Failed to save: " + jpegSavePath + " using " + execString); showStatus("Saving jpeg using ImageJ"); saveAs("Jpeg", jpegSavePath); if (!File.exists(jpegSavePath)) IJ.log("Failed to save: " + jpegSavePath); } else { call("ij.Prefs.set", "asc.lastsaved.jpeg.path", jpegSavePath); call("ij.Prefs.set", "asc.lastsaved.jpeg.qual", jpegQual); } } else { showStatus("Saving jpeg using ImageJ"); run("Input/Output...", "jpeg=" + jpegQual + " file=.csv save copy_column copy_row save_column save_row"); saveAs("Jpeg", jpegSavePath); if (!File.exists(jpegSavePath)) IJ.log("Failed to save: " + jpegSavePath); else { call("ij.Prefs.set", "asc.lastsaved.jpeg.path", jpegSavePath); call("ij.Prefs.set", "asc.lastsaved.jpeg.qual", jpegQual); } } } if (jp2Too && iMMagick) { if (lzwSaved) { jp2SavePath = pathLengthCheck("" + savePath + ".jp2", maxPathL); jp2SavePath = pathOverwriteCheck(jp2SavePath); iMJP2Settings = " -format jp2 -quality " + jp2Qual + " "; execString = qMark + iMMagickPath + qMarkSp + qMark + saveTIFFPath + qMarkSp + iMJP2Settings + spQMark + jp2SavePath + qMark; showStatus("Saving jp2 using ImageMagick"); exec(execString); showStatus("Waiting for " + round(delayTime / 1000) + "s for jp2 save recognition"); wait(delayTime); if (!File.exists(jp2SavePath)) IJ.log("Failed to save: " + jp2SavePath + " using " + execString); else { call("ij.Prefs.set", "asc.lastsaved.jp2.path", jp2SavePath); call("ij.Prefs.set", "asc.lastsaved.jp2.qual", jp2Qual); } } } if (colorTableToo && iMMagick) { if (lzwSaved) { colorTableSavePath = pathLengthCheck("" + savePath + ".gif", maxPathL); colorTableSavePath = pathOverwriteCheck(colorTableSavePath); iMColorTableSettings = " -unique-colors "; execString = qMark + iMMagickPath + qMarkSp + qMark + saveTIFFPath + qMarkSp + iMColorTableSettings + spQMark + colorTableSavePath + qMark; showStatus("Saving GIF color table using ImageMagick"); exec(execString); showStatus("Waiting for " + round(delayTime / 1000) + "s for GIF save recognition"); wait(delayTime); if (!File.exists(colorTableSavePath)) IJ.log("Failed to save: " + colorTableSavePath + " using " + execString); else call("ij.Prefs.set", "asc.lastsaved.colorTable.path", colorTableSavePath); } } if (pngToo) { pngSavePath = savePath; if (savePNGTransp) pngSavePath += "_transp"; pngSavePath = pathLengthCheck("" + pngSavePath + ".png", maxPathL); pngSavePath = pathOverwriteCheck(pngSavePath); if (lzwSaved) { if (!jpegToo) { showStatus("Waiting for " + delayTime / 1000 + "s for lzw save recognition"); wait(delayTime); } pngTooExec = qMark + iMMagickPath + qMarkSp + qMark + saveTIFFPath + qMarkSp + iMPNGSettings + spQMark + pngSavePath + qMark; showStatus("Saving png using ImageMagick"); if (diagnostics) IJ.log("IM also-PNG save execString:\n" + pngTooExec); exec(pngTooExec); if (!File.exists(pngSavePath)) { IJ.log("Failed to save: " + pngSavePath + " using " + pngTooExec); showStatus("Saving png using ImageJ"); saveAs("PNG", pngSavePath); if (!File.exists(pngSavePath)) { IJ.log("Failed to save: " + pngSavePath); } else showStatus("png saved using ImageJ"); } else { showStatus("png saved using ImageMagick"); call("ij.Prefs.set", "asc.lastsaved.png.path", pngSavePath); call("ij.Prefs.set", "asc.lastsaved.png.qual", pngQual); } } else { showStatus("Saving png using ImageJ"); saveAs("PNG", pngSavePath); if (!File.exists(pngSavePath)) IJ.log("Failed to save: " + pngSavePath); else showStatus("png saved using ImageJ"); } } if (scaleImage != 1) { if (closeScaledImage) { selectImage(scaledImageID); close(); } } if (iconToo && lzwSaved) { iconSavePath = pathLengthCheck("" + savePath + ".ico", maxPathL); iconSavePath = pathOverwriteCheck(iconSavePath); iMIconSettings = " -resize 64x64^ -gravity center -extent 64x64 "; /* ! fits narrowest dimension and crops excess widest dimension */ iconTooExec = qMark + iMMagickPath + qMarkSp + qMark + saveTIFFPath + qMarkSp + iMIconSettings + spQMark + iconSavePath + qMark; showStatus("Saving windows icon using ImageMagick"); if (diagnostics) IJ.log("IM also-ICON save execString:\n" + iconTooExec); exec(iconTooExec); } if (iMMagick && imageWidth > 240 && lzwSaved) { if (thumbSelSizes.length > 0 && (saveThumbsPNG || saveThumbsJPG)) { cropSettings = ""; thumbMod = ""; if (fuzzCrop && trimThumb) cropSettings += " -fuzz 1%"; if (trimThumb) { cropSettings += " -trim +repage "; thumbMod = "-Trim"; if (fuzzCrop) thumbMod += "Fz1"; } orSizeString = "" + imageWidth + "x" + imageHeight; /* this ASC image size string is no longer relevant for thumbnails */ thumbFilename = replace(fileName, orSizeString, ""); thumbFilename = unCleanLabel(thumbFilename); saveThumbPre = pathLengthCheck(saveDir + stripKnownExtensionFromString(thumbFilename), maxPathL); for (i = 0; i < thumbN; i++) { if (saveThumbsPNG) { thumbPSavePath = pathLengthCheck("" + saveThumbPre + "_" + thumbSelSizes[i] + thumbMod + ".png", maxPathL); thumbPSavePath = pathOverwriteCheck(thumbPSavePath); if (savePNGThumbsPAL) iMPNGSetPAL = " +dither -colors " + pngThumbsCols; else iMPNGSetPAL = ""; if (savePNGTransp) { iMPNGSetTransp = " -transparent " + pngTranspSet + " -background " + pngMatteSet + " "; if (fuzz > 0) iMPNGSetTransp += "-fuzz " + fuzz + "% "; } else iMPNGSetTransp = ""; thumbPExec = qMark + iMMagickPath + qMark + " " + qMark + saveTIFFPath + qMark + cropSettings + " -resize " + thumbSelSizes[i] + iMPNGSettings + iMPNGSetPAL + iMPNGSetTransp + " " + qMark + thumbPSavePath + qMark; showStatus("Saving " + thumbSelSizes[i] + " PNG thumbnail using ImageMagick"); if (diagnostics) IJ.log("IM PNG " + thumbSelSizes[i] + " thumbnail save execString:\n" + thumbPExec); exec(thumbPExec); } if (saveThumbsJPG) { thumbJSavePath = pathLengthCheck("" + saveThumbPre + "_" + thumbSelSizes[i] + thumbMod + ".jpg", maxPathL); thumbJSavePath = pathOverwriteCheck(thumbJSavePath); iMJPEGSettings = " -quality " + jpegQual + " "; thumbJExec = qMark + iMMagickPath + qMark + " " + qMark + saveTIFFPath + qMark + cropSettings + " -resize " + thumbSelSizes[i] + iMJPEGSettings + " " + qMark + thumbJSavePath + qMark; showStatus("Saving JPEG " + thumbSelSizes[i] + " thumbnail using ImageMagick"); if (diagnostics) IJ.log("IM JPEG " + thumbSelSizes[i] + " thumbnail save execString:\n" + thumbJExec); exec(thumbJExec); } } } } } /* End of TIFF save */ if (tempImage) close(tempImageTitle); if (Overlay.size != 0) Overlay.show; setBatchMode("exit and display"); beep(); wait(100); beep(); wait(300); beep(); call("java.lang.System.gc"); showStatus("Compressed format export finished", "flash green"); /* End of Anim or Transp GIF PNG Seq, base64 and comp TIFF save macro */ } /* */ macro "-" {} /* Menu Divider */ macro "Open Sample Image" { run("URL...", "url=https://imagej.net/ij/macros/images/cd3-sample.tif"); } macro "Flatten Overlay" { /* Creates a new RGB image that has the overlay rendered as pixel data. */ Overlay.flatten; } macro "Hide Overlay" { Overlay.hide; } macro "Show Overlay" { Overlay.show; } macro "Remove Overlay [F10]" { /* Removes the current overlay. */ Overlay.remove; } macro "Remove Last Overlay Selection" { if (Overlay.size > 0) Overlay.removeSelection(Overlay.size - 1); else exit("Sorry, no overlays available"); } macro "-" {} /* Menu Divider */ /* ( 8(|) ( 8(|) ASC Macros ( 8(|) ( 8(|) */ macro "Close All Image Windows" { /* Introduced in July 2018 imageJ update */ close("*"); } macro "Close All Other Open Images" { /* Closes all images except for the front image. command introduced in July 2018 imageJ update so this replaces previous ASC function v240118 Also closes open Histogram windows */ close("\\Others"); close("Histogram*"); } macro "Close Results and Clear ROIs" { closeResultsandClearROIs(); } /* Inspired by the BAR ROI_Color_Coder.ijm This macro adds a statistical summary of the analysis to the image in the selection box or at one of the corners of the image. This version defaults to choosing units automatically. ... v220808 Better anti-aliasing */ macro "Add Summary Table to Copy of Image" { macroL = "Fancy_Summary_Table_v240709.ijm"; requires("1.47r"); saveSettings; /* Set options for black objects on white background as this works better for publications */ run("Options...", "iterations=1 white count=1"); /* Set the background to white */ run("Colors...", "foreground=black background=white selection=yellow"); /* Set the preferred colors for these macros */ setOption("BlackBackground", false); run("Appearance...", " "); unInvertLUT(); /* do not use Inverting LUT */ /* The above should be the defaults but this makes sure (black particles on a white background) https://imagej.net/doku.php?id=faq:technical:how_do_i_set_up_imagej_to_deal_with_white_particles_on_a_black_background_by_default */ getPixelSize(unit, pixWidth, pixHeight, pixDepth); selEType = selectionType; if (selEType >= 0) { if ((selEType >= 5) && (selEType <= 7)) { line == true; if (selEType > 5) { /* for 6=segmented line or 7=freehand line do a linear fit */ getSelectionCoordinates(xPoints, yPoints); Array.getStatistics(xPoints, selEX1, selEX2, null, null); Fit.doFit("Linear", xPoints, yPoints); selEY1 = Fit.f(selEX1); selEY2 = Fit.f(selEX2); } else = getLine(selEX1, selEY1, selEX2, selEY2, selLineWidth); x1 = selEX1 * pixWidth; y1 = selEY1 * pixHeight; x2 = selEX2 * pixWidth; y2 = selEY2 * pixHeight; scaledLineAngle = (180 / PI) * Math.atan2((y1 - y2), (x1 - x2)); scaledLineLength = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); selLineLength = sqrt(pow(selEX2 - x1, 2) + pow(selEY2 - selEY1, 2)); } else { line = false; getSelectionBounds(selEX, selEY, selEWidth, selEHeight); } } t = getTitle(); /* Now checks to see if a Ramp legend has been selected by accident */ if (matches(t, ".*Ramp.*") == 1) showMessageWithCancel("Title contains \"Ramp\"", "Do you really want to label " + t + " ?"); setBatchMode(true); checkForResults(); items = nResults; imageWidth = getWidth(); imageHeight = getHeight(); id = getImageID(); fontSize = 22; /* default font size */ lineSpacing = 1.1; dOutS = 6; /* default outline stroke: % of font size */ dShO = 8; /* default outer shadow drop: % of font size */ dIShO = 4; /* default inner shadow drop: % of font size */ /* set default tweaks */ outlineStroke = dOutS; shadowDrop = dShO; shadowDisp = dShO; shadowBlur = floor(0.75 * dShO); shadowDarkness = 30; innerShadowDrop = dIShO; innerShadowDisp = dIShO; innerShadowBlur = floor(dIShO / 2); innerShadowDarkness = 20; offsetX = round(1 + imageWidth / 150); /* default offset of label from edge */ offsetY = round(1 + imageHeight / 150); /* default offset of label from edge */ outlineColor = "black"; imageDepth = bitDepth(); paraLabFontSize = round((imageHeight + imageWidth) / 60); decPlacesSummary = -1; /* defaults to scientific notation */ Dialog.create("Label Formatting Options: " + macroL); headings = split(String.getResultsHeadings); Dialog.addChoice("Measurement:", headings, "Area"); grayChoices = newArray("white", "black", "off-white", "off-black", "lightGray", "gray", "darkGray"); colorChoicesStd = newArray("red", "green", "blue", "cyan", "magenta", "yellow", "pink", "orange", "violet"); colorChoicesMaterials = newArray("bronze", "antique_bronze", "brass", "dull_brass", "brick", "chrome", "copper", "aged_copper", "dusky_copper", "light_copper", "garnet", "burnished_gold", "gold", "slate_gray", "titanium", "vault_garnet", "plaza_brick", "vault_gold"); colorChoicesMod = newArray("aqua_modern", "blue_accent_modern", "blue_dark_modern", "blue_modern", "blue_honolulu", "gray_modern", "green_dark_modern", "green_modern", "green_modern_accent", "green_spring_accent", "orange_modern", "pink_modern", "purple_modern", "red_modern", "tan_modern", "violet_modern", "yellow_modern"); colorChoicesNeon = newArray("jazzberry_jam", "radical_red", "wild_watermelon", "outrageous_orange", "supernova_orange", "atomic_tangerine", "neon_carrot", "sunglow", "laser_lemon", "electric_lime", "screamin'_green", "magic_mint", "blizzard_blue", "dodger_blue", "shocking_pink", "razzle_dazzle_rose", "hot_magenta"); colorChoicesFSU = newArray("stadium_night", "westcott_water", "legacy_blue"); /* Materials colors moved to Materials set */ if (imageDepth == 24) colorChoices = Array.concat(grayChoices, colorChoicesStd, colorChoicesMaterials, colorChoicesMod, colorChoicesNeon, colorChoicesFSU); else colorChoices = grayChoices; Dialog.addChoice("Text color:", colorChoices, colorChoices[0]); fontStyleChoice = newArray("bold", "bold antialiased", "italic", "italic antialiased", "bold italic", "bold italic antialiased", "unstyled"); Dialog.addChoice("Font style:", fontStyleChoice, fontStyleChoice[1]); fontNameChoice = newArray("SansSerif", "Serif", "Monospaced"); fontNameChoice = getFontChoiceList(); iFN = indexOfArray(fontNameChoice, call("ij.Prefs.get", "fancy.scale.font", fontNameChoice[0]), 0); Dialog.addChoice("Font name:", fontNameChoice, fontNameChoice[iFN]); Dialog.addNumber("Line Spacing", lineSpacing, 1, 3, ""); unitChoice = newArray("Auto", "Manual", unit, unit + "^2", "None", "pixels", "pixels^2", fromCharCode(0x00B0), "degrees", "radians", "%", "arb."); Dialog.addChoice("Unit Label \(if needed\):", unitChoice, unitChoice[0]); Dialog.addNumber("Outline stroke:", outlineStroke, 0, 3, "% of font size"); Dialog.addChoice("Outline (background) color:", colorChoices, colorChoices[1]); Dialog.addMessage("Negative drops are upwards and negative displacements are to the left"); Dialog.addNumber("Shadow drop: ±", shadowDrop, 0, 3, "% of font size"); Dialog.addNumber("Shadow displacement right: ±", shadowDrop, 0, 3, "% of font size"); Dialog.addNumber("Shadow Gaussian blur:", shadowBlur, 0, 3, "% of font size"); Dialog.addNumber("Shadow Darkness:", shadowDarkness, 0, 3, "%\(darkest = 100%\)"); Dialog.addNumber("Inner shadow drop: ±", dIShO, 0, 3, "% of font size"); Dialog.addNumber("Inner displacement right: ±", innerShadowDisp, 0, 3, "% of font size"); Dialog.addNumber("Inner shadow mean blur:", innerShadowBlur, 1, 3, "% of font size"); Dialog.addNumber("Inner Shadow Darkness:", innerShadowDarkness, 0, 3, "% \(darkest = 100%\)"); Dialog.show(); parameter = Dialog.getChoice(); labelColor = Dialog.getChoice(); fontStyle = Dialog.getChoice(); fontName = Dialog.getChoice(); lineSpacing = Dialog.getNumber(); unitLabel = Dialog.getChoice(); outlineStroke = Dialog.getNumber(); outlineColor = Dialog.getChoice(); shadowDrop = Dialog.getNumber(); shadowDisp = Dialog.getNumber(); shadowBlur = Dialog.getNumber(); shadowDarkness = Dialog.getNumber(); innerShadowDrop = Dialog.getNumber(); innerShadowDisp = Dialog.getNumber(); innerShadowBlur = Dialog.getNumber(); innerShadowDarkness = Dialog.getNumber(); // Determine parameter label parameterLabel = parameter; if (unitLabel == "Auto") unitLabel = unitLabelFromString(parameter, unit); if (unitLabel == "Manual") { unitLabel = unitLabelFromString(parameter, unit); Dialog.create("Manual unit input"); Dialog.addString("Label:", unitLabel, 8); Dialog.addMessage("^2 & um etc. replaced by " + fromCharCode(178) + " & " + fromCharCode(181) + "m..."); Dialog.show(); unitLabel = Dialog.getString(); } if (unitLabel == "None") unitLabel = ""; parameterLabel = stripUnitFromString(parameter); unitLabel = cleanLabel(unitLabel); parameterLabel = cleanLabel(parameterLabel); parameterLabel = replace(parameterLabel, "px", "pixels"); // expand "px" used to keep Results columns narrower //recombine units and labels if (unitLabel != "") paraLabel = parameterLabel + ", " + unitLabel; else paraLabel = parameterLabel; // parameterLabel = replace(parameterLabel, "_", fromCharCode(0x2009)); // replace underlines with thin spaces parameterLabel = expandLabel(parameterLabel); fontFactor = fontSize / 100; outlineStroke = floor(fontFactor * outlineStroke); negAdj = 0.5; /* negative offsets appear exaggerated at full displacement */ if (shadowDrop < 0) shadowDrop *= negAdj; if (shadowDisp < 0) shadowDisp *= negAdj; if (shadowBlur < 0) shadowBlur *= negAdj; if (innerShadowDrop < 0) innerShadowDrop *= negAdj; if (innerShadowDisp < 0) innerShadowDisp *= negAdj; if (innerShadowBlur < 0) innerShadowBlur *= negAdj; if (shadowDrop >= 0) shadowDrop = maxOf(1, round(fontFactor * shadowDrop)); else if (shadowDrop <= 0) shadowDrop = minOf(-1, round(fontFactor * shadowDrop)); if (shadowDisp >= 0) shadowDisp = maxOf(1, round(fontFactor * shadowDisp)); else if (shadowDisp <= 0) shadowDisp = minOf(-1, round(fontFactor * shadowDisp)); if (shadowBlur >= 0) shadowBlur = maxOf(1, round(fontFactor * shadowBlur)); else if (shadowBlur <= 0) shadowBlur = minOf(-1, round(fontFactor * shadowBlur)); innerShadowDrop = fontFactor * innerShadowDrop; innerShadowDisp = fontFactor * innerShadowDisp; innerShadowBlur = fontFactor * innerShadowBlur; if (abs(innerShadowDrop) < 0.3) innerShadowDrop = 0; if (abs(innerShadowDisp) < 0.3) innerShadowDisp = 0; if (abs(innerShadowBlur) < 0.3) innerShadowBlur = (innerShadowDrop + innerShadowDisp) / 2; unitLabelCheck = matches(unitLabel, "[A-Za-z]+.*.*"); if (fontStyle == "unstyled") fontStyle = ""; paraLabFontSize = round((imageHeight + imageWidth) / 45); statsLabFontSize = round((imageHeight + imageWidth) / 60); /* Get values for chosen parameter */ values = newArray(items); for (i = 0; i < items; i++) values[i] = getResult(parameter, i); Array.getStatistics(values, arrayMin, arrayMax, arrayMean, arraySD); decPlacesSummary = autoCalculateDecPlacesFromValueOnly(arrayMean); coeffVar = (100 / arrayMean) * arraySD; dpLab = decPlacesSummary + 2; /* Increase dp over ramp label autosetting */ coeffVar = d2s(coeffVar, dpLab); arrayMeanLab = d2s(arrayMean, dpLab); coeffVarLab = d2s((100 / arrayMean) * arraySD, dpLab); arraySDLab = d2s(arraySD, dpLab); arrayMinLab = d2s(arrayMin, dpLab); arrayMaxLab = d2s(arrayMax, dpLab); sortedValues = Array.copy(values); sortedValues = Array.sort(sortedValues); arrayMedian = sortedValues[floor(items / 2)]; arrayMedianLab = d2s(arrayMedian, dpLab); if (selEType >= 0) loc = 6; /* default choice selector for dialog */ else loc = 2; /* default choice selector for dialog - center */ paraLabel = expandLabel(paraLabel); Dialog.create("Feature Label Formatting Options"); if (selEType >= 0) paraLocChoice = newArray("Top Left", "Top Right", "Center", "Bottom Left", "Bottom Right", "Center of New Selection", "At Selection"); else paraLocChoice = newArray("Top Left", "Top Right", "Center", "Bottom Left", "Bottom Right", "Center of New Selection"); Dialog.addChoice("Location of Summary:", paraLocChoice, paraLocChoice[loc]); Dialog.addString("Parameter label:", cleanLabel(paraLabel), lengthOf(paraLabel) + 10); Dialog.addChoice("Parameter Label: " + paraLabel, newArray("Yes", "No"), "Yes"); Dialog.addNumber("Image Label Font size:", paraLabFontSize); statsChoice = newArray("None", "No more labels", "Dashed line: ---", "Number of objects: " + items, "Mean: " + arrayMeanLab, "Median: " + arrayMedianLab, "StdDev: " + arraySDLab, "CoeffVar: " + coeffVarLab, "Min-Max: " + arrayMinLab + "-" + arrayMaxLab, "Minimum: " + arrayMinLab, "Maximum: " + arrayMaxLab, "6 Underlines: ___", "12 Underlines: ___", "18 Underlines: ___", "24 Underlines: ___"); statsChoiceLines = 8; for (i = 0; i < statsChoiceLines; i++) Dialog.addChoice("Statistics Label Line " + (i + 2) + ":", statsChoice, statsChoice[i + 2]); dpChoice = newArray(dpLab, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8); Dialog.addChoice("Change Decimal Places from " + dpLab, dpChoice, dpLab); Dialog.addNumber("Statistics Label Font size:", statsLabFontSize); Dialog.show(); paraLabPos = Dialog.getChoice(); paraLabel = Dialog.getString(); paraLabChoice = Dialog.getChoice(); paraLabFontSize = Dialog.getNumber(); statsLabLine = newArray(statsChoiceLines); for (i = 0; i < statsChoiceLines; i++) statsLabLine[i] = Dialog.getChoice(); decPlacesSummary = Dialog.getChoice(); statsLabFontSize = Dialog.getNumber(); if (paraLabChoice == "Yes") labLines = 1; else labLines = 0; statsLines = 0; statsLabLineText = newArray(8); setFont(fontName, statsLabFontSize, fontStyle); longestStringWidth = 0; for (i = 0; i < statsChoiceLines; i++) { // if (statsLabLine[i]!="None") statsLines = statsLines + 1; if (statsLabLine[i] == "No more labels") i = statsChoiceLines; else if (statsLabLine[i] != "None") { statsLines = i + 1; statsLabLine[i] = substring(statsLabLine[i], 0, indexOf(statsLabLine[i], ": ")); if (statsLabLine[i] == "Dashed line") statsLabLineText[i] = "----------"; else if (statsLabLine[i] == "Number of objects") statsLabLineText[i] = "Objects = " + items; else if (statsLabLine[i] == "Mean") statsLabLineText[i] = "Mean = " + d2s(arrayMean, decPlacesSummary) + " " + unitLabel; else if (statsLabLine[i] == "Median") statsLabLineText[i] = "Median = " + d2s(arrayMedian, decPlacesSummary) + " " + unitLabel; else if (statsLabLine[i] == "StdDev") statsLabLineText[i] = "Std.Dev. = " + d2s(arraySD, decPlacesSummary) + " " + unitLabel; else if (statsLabLine[i] == "CoeffVar") statsLabLineText[i] = "Coeff.Var. = " + d2s(coeffVar, decPlacesSummary) + "%"; else if (statsLabLine[i] == "Min-Max") statsLabLineText[i] = "Range = " + d2s(arrayMin, decPlacesSummary) + " - " + d2s(arrayMax, decPlacesSummary) + " " + unitLabel; else if (statsLabLine[i] == "Minimum") statsLabLineText[i] = "Minimum = " + d2s(arrayMin, decPlacesSummary) + " " + unitLabel; else if (statsLabLine[i] == "Maximum") statsLabLineText[i] = "Maximum = " + d2s(arrayMax, decPlacesSummary) + " " + unitLabel; else if (statsLabLine[i] == "6 Underlines") statsLabLineText[i] = "______"; else if (statsLabLine[i] == "12 Underlines") statsLabLineText[i] = "____________"; else if (statsLabLine[i] == "18 Underlines") statsLabLineText[i] = "__________________"; else if (statsLabLine[i] == "24 Underlines") statsLabLineText[i] = "________________________"; if (unitLabel == fromCharCode(0x00B0)) statsLabLineText[i] = replace(statsLabLineText[i], " " + fromCharCode(0x00B0), fromCharCode(0x00B0)); // tweak to remove space before degree symbol if (getStringWidth(statsLabLineText[i]) > longestStringWidth) longestStringWidth = getStringWidth(statsLabLineText[i]); } } linesSpace = lineSpacing * ((labLines * paraLabFontSize) + (statsLines * statsLabFontSize)); // Calculate vertical space taken up by text if (paraLabChoice == "Yes") { setFont(fontName, paraLabFontSize, fontStyle); if (getStringWidth(paraLabel) > longestStringWidth) longestStringWidth = getStringWidth(paraLabel); } if (paraLabPos == "Top Left") { selEX = offsetX; selEY = offsetY; } else if (paraLabPos == "Top Right") { selEX = imageWidth - longestStringWidth - offsetX; selEY = offsetY; } else if (paraLabPos == "Center") { selEX = round((imageWidth / 2) - longestStringWidth / 2); selEY = round((imageHeight / 2) - (linesSpace / 2)); } else if (paraLabPos == "Bottom Left") { selEX = offsetX; selEY = imageHeight - (offsetY + linesSpace); } else if (paraLabPos == "Bottom Right") { selEX = imageWidth - longestStringWidth - offsetX; selEY = imageHeight - (offsetY + linesSpace); } else if (paraLabPos == "Center of New Selection") { if (is("Batch Mode") == true) setBatchMode(false); /* Does not accept interaction while batch mode is on */ setTool("rectangle"); msgtitle = "Location for the summary labels..."; msg = "Draw a box in the image where you want to center the summary labels..."; waitForUser(msgtitle, msg); getSelectionBounds(newSelEX, newSelEY, newSelEWidth, newSelEHeight); selEX = newSelEX + round((newSelEWidth / 2) - longestStringWidth / 1.5); selEY = newSelEY + round((newSelEHeight / 2) - (linesSpace / 2)); if (is("Batch Mode") == false) setBatchMode(true); // toggle batch mode back on } else if (selEType >= 0) { selEX = selEX + round((selEWidth / 2) - longestStringWidth / 1.5); selEY = selEY + round((selEHeight / 2) - (linesSpace / 2)); } run("Select None"); if (selEY <= 1.5 * paraLabFontSize) selEY += paraLabFontSize; if (selEX < offsetX) selEX = offsetX; endX = selEX + longestStringWidth; if ((endX + offsetX) > imageWidth) selEX = imageWidth - longestStringWidth - offsetX; paraLabelX = selEX; paraLabelY = selEY; roiManager("show none"); // roiManager("Show All without labels"); run("Flatten"); if (imageDepth == 8) run("8-bit"); /* restores to 8-bit after flatten */ flatImage = getTitle(); if (is("Batch Mode") == false) setBatchMode(true); setColor(255, 255, 255); roiManager("show none"); roiManager("deselect"); run("Select None"); tempID = getImageID; /* Create new image that will be used to create outlines and shadows */ newImage("label_mask", "8-bit black", imageWidth, imageHeight, 1); statsLabelY = paraLabelY; if (paraLabChoice == "Yes") { writeLabel7(fontName, paraLabFontSize, "white", paraLabel, paraLabelX, paraLabelY, true); statsLabelY += lineSpacing * paraLabFontSize; } setFont(fontName, statsLabFontSize, fontStyle); statsString = ""; for (i = 0; i < statsLines; i++) { if (statsLabLine[i] != "None") { writeLabel7(fontName, statsLabFontSize, "white", statsLabLineText[i], paraLabelX, statsLabelY, true); statsLabelY += lineSpacing * statsLabFontSize; } } setThreshold(0, 128); setOption("BlackBackground", false); run("Convert to Mask"); run("Select None"); selectImage(tempID); run("Select None"); getSelectionFromMask("label_mask"); run("Enlarge...", "enlarge=&outlineStroke pixel"); setBackgroundFromColorName(outlineColor); run("Clear", "slice"); run("Enlarge...", "enlarge=1 pixel"); run("Gaussian Blur...", "sigma=0.55"); run("Convolve...", "text1=[-0.0556 -0.0556 -0.0556 \n-0.0556 1.4448 -0.0556 \n-0.0556 -0.0556 -0.0556]"); /* moderate sharpen */ run("Select None"); /* Create drop shadow if desired */ if (shadowDrop != 0 || shadowDisp != 0 || shadowBlur != 0) { showStatus("Creating drop shadow for labels . . . "); /* Create drop shadow if desired */ if (shadowDrop != 0 || shadowDisp != 0 || shadowBlur != 0) createShadowDropFromMask7("label_mask", shadowDrop, shadowDisp, shadowBlur, shadowDarkness, outlineStroke); /* Create inner shadow if desired */ if (innerShadowDrop != 0 || innerShadowDisp != 0 || innerShadowBlur != 0) createInnerShadowFromMask6("label_mask", innerShadowDrop, innerShadowDisp, innerShadowBlur, innerShadowDarkness); } if (isOpen("shadow") && (shadowDarkness > 0)) imageCalculator("Subtract", flatImage, "shadow"); else if (isOpen("shadow") && (shadowDarkness < 0)) imageCalculator("Add", flatImage, "shadow"); selectWindow(flatImage); /* Draw antialiased summary over top of image */ statsLabelY = paraLabelY; if (paraLabChoice == "Yes") { writeLabel7(fontName, paraLabFontSize, "white", paraLabel, paraLabelX, paraLabelY, true); statsLabelY += lineSpacing * paraLabFontSize; } setFont(fontName, statsLabFontSize, fontStyle); statsString = ""; for (i = 0; i < statsLines; i++) { if (statsLabLine[i] != "None") { writeLabel7(fontName, statsLabFontSize, "white", statsLabLineText[i], paraLabelX, statsLabelY, true); statsLabelY += lineSpacing * statsLabFontSize; } } /* End string rewrite */ if (isOpen("inner_shadow")) imageCalculator("Subtract", flatImage, "inner_shadow"); if ((lastIndexOf(t, ".")) > 0) labeledImageNameWOExt = unCleanLabel(substring(flatImage, 0, lastIndexOf(flatImage, "."))); else labeledImageNameWOExt = unCleanLabel(flatImage); closeImageByTitle("shadow"); closeImageByTitle("inner_shadow"); closeImageByTitle("label_mask"); rename(labeledImageNameWOExt + "_" + parameter); restoreSettings(); setBatchMode("exit & display"); beep(); wait(400); beep(); wait(800); beep(); call("java.lang.System.gc"); showStatus("Fancy Summary Table Macro Finished"); } /* Fork of ROI_Color_Coder.ijm IJ BAR: https://github.com/tferr/Scripts#scripts See full macro for full history. v230414b-v230420: Formatting simplified, "raised" and "recessed" replace inner shadow, major preferences added. f1: updates stripKnownExtensionFromString function. v230517: Add summary save to text file option. v230517b: Keeps focus in selected image for coloring. Fixes saved settings for summary output. v230518: Fixed for missing ramp issue caused by spaces in image title. Reorganized unit choices. v230523: Cropped combination functionality restored. v230803: Replaced getDir for 1.54g10. v230822: Corrected selectImage to selectWindow. Removes duplicates from image list. f1: Updated function removeDuplicatesInArray. v230823: Guesses appropriate legend range and major intervals. Fixed prefs set error that opened console. Added more saved prefs. v230824-5: Added 'rangeFinder' function. Colors added dialogs to highlight instructions vs. info. vs. warnings. v230825b: Simplified output options. f1: Updates indexOf functions. v230905: Tweaked range-finding and updated functions. F1: Updated getColorArrayFromColorName_v230908. v230910: Fixed incorrect outlier prefs request, more tweaks to range. Reverted to full data range for default LUT color mapping. Set allowable data range overflow of 0.5%. v230911-15b: Restricted LUT range to be within ramp range. Fixed outlier range preferences. Add unit separator options. v230916: Most parameters now saved in user prefs. Streamlined trailing zeros detection for legend. v230921: 'Holes' option removed: Composite ROIs preferred. Some cosmetic improvements to menu layouts. v231004: Removed parenthesis in wrong place on line 1720. v231005: Suggests optimum tick count. Minor tick length corrected. Added more saved prefs. v231006: Can examine a subset of ROIs. v231009: If there is a rectangular selection there will be an option to just examine the ROIs enclosed by the selection. Fixed DP issue with summary. v231011: Expands selection types that can be used to select ROIs. Adds the selected ROI list to the summary file. If a subset is used all ROI properties are cleared first. v231012: Saves ROI subset as Zip and CSV files and adds time stamps to filenames. Can import roi csv list as selection. v231017: Just added more descriptions in the main dialog. Testing another minor tick number formula. v231103: Removed redundant defGap line. v231129: Added option to recall previously used bounds for crop area. v231130: Just added a hint on how to handle rejected column names. b: Reorganized ROI restriction options. F1 : Replaced function: pad. v231211: Adds an option to measure ROIs if there are no Results. v231213: IJ seems to be getting picky with column names. This version skips the column name check line ~163. Fixed bad boolean command in manual selection. v231213b: Display of statistics and frequency on ramp for small numbers of features is disabled. v231214: Minor formatting and comments. Removed overly restricted Min and Max Line requirements. v240112: Frequency plots again. v240119: But not if insufficient stats. b: dialog typo fix. v240709: Updated colors. v250424: Fixed interval number dialog that should not have allowed hidden decimals. vv250509: Initial fontSize is integer to match addNumber dp. v260416: Made interim image titles more unique for dbugging purposes. Removed overlays that were hiding labels. */ /* !!! Note shorter name used than standalone: Needs to be changed from 'ROI Color Coder with Scaled Labels and Summary' to 'ASC ROI Color Coder and Summary' for narrower context menu !!! */ /* !!!! Also the macro title in the startup includes the shortcut [F6] !!!! */ macro "ASC ROI Color Coder and Summary [F6]" { macroL = "BAR_ROI_Color_Coder_Unit-Scaled_Labels_Summary_ASC_v260420.ijm"; macroV = substring(macroL, lastIndexOf(macroL, "_v") + 2, maxOf(lastIndexOf(macroL, "."), lastIndexOf(macroL, "_v") + 8)); requires("1.53g"); /* Uses expandable arrays */ close("*Ramp"); /* cleanup: closes previous ramp windows, NOTE this is case insensitive */ call("java.lang.System.gc"); if (!checkForPluginNameContains("Fiji_Plugins")) exit("Sorry this macro requires some functions in the Fiji_Plugins package"); /* Needs Fiji_plugins for autoCrop */ saveSettings; ascPrefsKey = "asc.ROICoder.Prefs."; imageN = nImages; if (imageN == 0) { showMessageWithCancel("No images open or the ROI Manager is empty...\n" + "Run demo? (Results Table and ROI Manager will be cleared)"); runDemo(); } orID = getImageID(); /* get id of image and title */ t = getTitle(); tPath = getDirectory("image"); if (tPath == "") tPath = File.directory; if (indexOf(tPath, "AutoRun") >= 0) tPath = ""; /* Check to see if there is a location already set for ROI selection and/or he summary 0=rectangle, 1=oval, 2=polygon, 3=freehand, 4=traced, 5=straight line, 6=segmented line, 7=freehand line, 8=angle, 9=composite and 10=point. */ selType = selectionType; if (selType >= 0 && selType < 4) { getSelectionBounds(selPosStartX, selPosStartY, originalSelEWidth, originalSelEHeight); /* smallest rectangle that can completely contain the current selection */ selectionExists = true; } else selectionExists = false; run("Select None"); if (roiManager("count") > 0) roiManager("deselect"); /* Set options for black objects on white background as this works better for publications */ run("Options...", "iterations=1 white count=1"); /* Set the background to white */ run("Colors...", "foreground=black background=white selection=yellow"); /* Set the preferred colors for these macros */ setOption("BlackBackground", false); selectImage(orID); if (is("Inverting LUT")) run("Invert LUT"); nROIs = checkForRoiManager(); /* macro requires that the objects are in the ROI manager */ checkForResults(); /* macro requires that there are results to display */ /* Check for unwanted black border */ oImageDepth = bitDepth(); if (!is("binary") && nROIs < 1) { yMax = Image.height - 1; xMax = Image.width - 1; cornerPixels = newArray(getPixel(0, 0), getPixel(1, 1), getPixel(0, yMax), getPixel(xMax, 0), getPixel(xMax, yMax), getPixel(xMax - 1, yMax - 1)); Array.getStatistics(cornerPixels, cornerMin, cornerMax, cornerMean, cornerStdDev); if (cornerMax != cornerMin) { actionOptions = newArray("Remove black edge objects", "Invert, then remove black edge objects", "Exit", "Feeling lucky"); Dialog.create("Border pixel inconsistency"); Dialog.addMessage("cornerMax=" + cornerMax + " but cornerMin=" + cornerMin + " and cornerMean = " + cornerMean + " problem with image border"); Dialog.addRadioButtonGroup("Actions:", actionOptions, actionOptions.length, 1, "Remove black edge objects"); Dialog.show(); edgeAction = Dialog.getRadioButton(); if (edgeAction == "Exit") restoreExit(); else if (edgeAction == "Invert, then remove black edge objects") { run("Invert"); removeBlackEdgeObjects(); } else if (edgeAction == "Remove white edge objects, then invert") { removeBlackEdgeObjects(); run("Invert"); } } /* Sometimes the outline procedure will leave a pixel border around the outside - this next step checks for this. i.e. the corner 4 pixels should now be all black, if not, we have a "border issue". */ if (cornerMean < 1 && cornerMean != -1) { inversion = getBoolean("The corner mean has an intensity of " + cornerMean + ", do you want the intensities inverted?", "Yes Please", "No Thanks"); if (inversion) run("Invert"); } } checkForUnits(); /* Required function */ getPixelSize(unit, pixelWidth, pixelHeight); medianBGIs = guessBGMedianIntensity(); bgI = round((medianBGIs[0] + medianBGIs[1] + medianBGIs[2]) / 3); lcf = (pixelWidth + pixelHeight) / 2; /* length conversion factor needed for morph. centroids */ nRes = nResults; tSize = Table.size; if (nRes == 0 && tSize > 0) { oTableTitle = Table.title; renameTable = getBoolean("There is no Results table but " + oTableTitle + " has " + tSize + " rows:", "Rename to Results", "No, I will take may chances"); if (renameTable) { Table.rename(oTableTitle, "Results"); nRes = nResults; } } if (nRes != nROIs) { measureROIs = getBoolean("There is no Results table but " + nROIs + " ROIs", "Measure theROIs?", "Exit"); if (measureROIs) { roiManager("Deselect"); roiManager("Measure"); nRes = nResults; } else restoreExit("Goodbye"); } if (nROIs <= 1) restoreExit("Exit: ROI Manager has only \(" + nROIs + "\) entries."); /* exit so that this ambiguity can be cleared up */ items = nROIs; run("Remove Overlay"); countNaN = 0; /* Set this counter here so it is not skipped by later decisions */ menuLimit = 0.8 * screenHeight; /* used to limit menu size for small screens */ // menuLimit = 700; /* for testing only resolution options only */ outlineStrokePC = 6; /* default outline stroke: % of font size */ sup2 = fromCharCode(178); degreeChar = fromCharCode(0x00B0); sigmaChar = fromCharCode(0x03C3); geq = fromCharCode(0x2265); ums = getInfo("micrometer.abbreviation"); grayChoices = newArray("white", "black", "off-white", "off-black", "lightGray", "gray", "darkGray"); colorChoicesStd = newArray("red", "green", "blue", "cyan", "magenta", "yellow", "pink", "orange", "violet"); colorChoicesMaterials = newArray("bronze", "antique_bronze", "brass", "dull_brass", "brick", "chrome", "copper", "aged_copper", "dusky_copper", "light_copper", "garnet", "burnished_gold", "gold", "slate_gray", "titanium", "vault_garnet", "plaza_brick", "vault_gold"); colorChoicesMod = newArray("aqua_modern", "blue_accent_modern", "blue_dark_modern", "blue_modern", "blue_honolulu", "gray_modern", "green_dark_modern", "green_modern", "green_modern_accent", "green_spring_accent", "orange_modern", "pink_modern", "purple_modern", "red_modern", "tan_modern", "violet_modern", "yellow_modern"); colorChoicesNeon = newArray("jazzberry_jam", "radical_red", "wild_watermelon", "outrageous_orange", "supernova_orange", "atomic_tangerine", "neon_carrot", "sunglow", "laser_lemon", "electric_lime", "screamin'_green", "magic_mint", "blizzard_blue", "dodger_blue", "shocking_pink", "razzle_dazzle_rose", "hot_magenta"); colorChoicesFSU = newArray("stadium_night", "westcott_water", "legacy_blue"); /* Materials colors moved to Materials set */ allColors = Array.concat(colorChoicesStd, colorChoicesMaterials, colorChoicesMod, colorChoicesNeon, colorChoicesFSU, grayChoices); tN = stripKnownExtensionFromString(unCleanLabel(t)); /* File.nameWithoutExtension is specific to last opened file, also remove special characters that might cause issues saving file */ if (lengthOf(tN) > 43) tNL = substring(tN, 0, 21) + "..." + substring(tN, lengthOf(tN) - 21); else tNL = tN; imageHeight = getHeight(); imageWidth = getWidth(); rampH = round(0.89 * imageHeight); /* suggest ramp slightly small to allow room for labels */ acceptMinFontSize = true; fontSize = maxOf(10, round(imageHeight / 28)); /* default fonts size based on imageHeight */ imageDepth = bitDepth(); /* required for shadows at different bit depths */ headings = split(String.getResultsHeadings, "\t"); /* the tab specificity avoids problems with unusual column titles */ headingsWithRange = newArray; warningTxt = ""; for (i = 0, countH = 0; i < lengthOf(headings); i++) { showProgress(i, countH); showStatus("Getting Results headings list"); resultsColumn = newArray(items); headingClean = cleanLabel(headings[i]); if (headingClean != headings[i]) { if (Table.columnExists(headings[i])) { Table.renameColumn(headings[i], headingClean); headings[i] = headingClean; } else warningTxt += "IJ claims that it cannot find a column named '" + headings[i] + "'\n"; } if (warningTxt != "") IJ.log(warningTxt + "This sometimes happens with files exported from Excel\nHint: Resave from IJ then reimport the resaved version"); for (j = 0; j < items; j++) resultsColumn[j] = getResult(headings[i], j); Array.getStatistics(resultsColumn, min, max, null, null); if (min != max && min >= 0 && !endsWith(max, "Infinity")) { /* No point in listing parameters without a range , also, the macro does not handle negative numbers well (let me know if you need them)*/ headingsWithRange[countH] = headings[i] + ": " + min + " - " + max; countH++; } } if (headingsWithRange[0] == " : Infinity - -Infinity") headingsWithRange[0] = "Object" + ": 1 - " + items; /* relabels ImageJ ID column */ headingsWithRange = Array.trim(headingsWithRange, countH); if (imageN > 1) { /* workaround for issue with duplicate names for single open image */ imageList = removeDuplicatesInArray(getList("image.titles"), false); imageN = lengthOf(imageList); } subset = false; subsetROIs = "---ROI#s \(starting at 1\) separated by commas---"; if (selectionExists) { batchMode = is("Batch Mode"); /* Store batch status mode before toggling */ if (!batchMode) setBatchMode(true); /* Toggle batch mode on if setBatchMode(true); /* finds ROIs inside and at selection */ roiManager("select", 0); roiStart = Roi.getName; roiManager("Deselect"); run("Restore Selection"); roiManager("Add"); roiManager("select", 0); roiStart2 = Roi.getName; if (roiStart == roiStart2) roiManager("select", items); else roiManager("select", 0); iTempROI = roiManager("index"); roiManager("Rename", "Temp_SelectionROI"); for (i = 0, nSels = 0; i < items + 1; i++) { if (i != iTempROI) { // roiManager("deselect"); roiManager("select", i); roiSize = getValue("selection.size"); roiManager("Select", newArray(i, iTempROI)); roiManager("AND"); if (getValue("selection.size") == roiSize) { subsetROIs += "" + i + 1 + ", "; nSels++; } } } roiManager("deselect"); RoiManager.selectByName("Temp_SelectionROI"); if (roiManager("index") == iTempROI) roiManager("delete"); roiManager("deselect"); if (endsWith(subsetROIs, ", ")) subsetROIs = substring(subsetROIs, 0, lengthOf(subsetROIs) - 1); if (!batchMode) setBatchMode(false); /* Toggle batch mode off */ } infoColor = "#006db0"; /* Honolulu blue */ instructionColor = "#798541"; /* green_dark_modern (121, 133, 65) AKA Wasabi */ infoWarningColor = "#ff69b4"; /* pink_modern AKA hot pink */ infoFontSize = 12; /* Create initial dialog prompt to determine parameters */ Dialog.create("Parameter Selection: " + macroL); /* if called from the BAR menu there will be no macro.filepath so the following checks for that */ startInfo = "Filename: " + tNL + "\nImage has " + nROIs + " ROIs that will be color coded"; if (tPath.length > 60) startInfo += "\nDirectory: " + substring(tPath, 0, tPath.length / 2) + "...\n ..." + substring(tPath, tPath.length / 2); Dialog.setInsets(0, 10, 20); Dialog.addMessage(startInfo, infoFontSize + 1.5, infoColor); Dialog.setInsets(0, 20, 0); Dialog.addDirectory("Output directory:", tPath); Dialog.setInsets(0, 20, 10); if (imageN == 1) { colImage = t; colImageL = lengthOf(colImage); if (colImageL > 50) colImage = "" + substring(colImage, 0, 24) + "..." + substring(colImage, colImageL - 24); Dialog.setInsets(0, 40, 0); Dialog.addMessage("Image for coloring is: " + colImage, infoFontSize, infoColor); } else if (imageN > 5) Dialog.addChoice("Image for color coding", imageList, t); else Dialog.addRadioButtonGroup("Choose image for color coding: ", imageList, imageN, 1, imageList[0]); iDefHeading = indexOfArrayThatStartsWith(headingsWithRange, call("ij.Prefs.get", ascPrefsKey + "parameter", "Area"), 1); Dialog.addChoice("Parameter", headingsWithRange, headingsWithRange[iDefHeading]); luts = getLutsList(); /* I prefer this to new direct use of getList used in the recent versions of the BAR macro YMMV */ Dialog.addChoice("LUT:", luts, call("ij.Prefs.get", ascPrefsKey + "lut", luts[0])); Dialog.setInsets(0, 170, 0); Dialog.addCheckbox("Reverse LUT?", call("ij.Prefs.get", ascPrefsKey + "revLut", false)); Dialog.addMessage("Color Coding:______Borders, Filled ROIs or None \(just labels\)?", infoFontSize, instructionColor); Dialog.setInsets(5, 20, 0); Dialog.addNumber("Outlines or Solid?", 0, 0, parseInt(call("ij.Prefs.get", ascPrefsKey + "stroke", 0)), "Width \(pixels\), 0=fill ROIs, -1= label only"); Dialog.setInsets(0, 20, 0); Dialog.addSlider("Coding opacity (%):", 0, 100, parseInt(call("ij.Prefs.get", ascPrefsKey + "opacity", 100))); outlierOptions = newArray("No", "1" + sigmaChar, "2" + sigmaChar, "3" + sigmaChar, "Ramp range", "Manual input"); iOutlierChoice = indexOfArray(outlierOptions, call("ij.Prefs.get", ascPrefsKey + "outlierChoice", "No"), 0); Dialog.setInsets(0, 20, 0); Dialog.addRadioButtonGroup("Outliers: Outline if outside the following values:", outlierOptions, 2, 4, outlierOptions[iOutlierChoice]); Dialog.setInsets(3, 0, 15); iOutlierColor = indexOfArray(allColors, call("ij.Prefs.get", ascPrefsKey + "outlierColor", allColors[0]), 0); Dialog.setInsets(0, 20, 0); Dialog.addChoice("Outliers: Outline color:", allColors, allColors[iOutlierColor]); Dialog.setInsets(-1, 20, 0); Dialog.addMessage("Negative " + sigmaChar + " \('<'\) outliers can be reported separately and set to a different color:", infoFontSize, instructionColor); allColorsS = Array.concat("same", allColors); iOutlierColorsS = indexOfArray(allColorsS, call("ij.Prefs.get", ascPrefsKey + "outlierColor2", allColorsS[0]), 0); Dialog.setInsets(0, 20, 0); Dialog.addChoice("Outliers '<': Outline color:", allColorsS, allColorsS[iOutlierColorsS]); Dialog.setInsets(0, 20, 0); Dialog.addNumber("Outlier outline thickness:", round(call("ij.Prefs.get", ascPrefsKey + "outlierStrokePC", 2)), 0, 3, "% of font size"); Dialog.setInsets(10, 20, 0); Dialog.addCheckbox("Apply colors and formatted labels to image copy \(no change to original\)", true); if (selectionExists) { Dialog.setInsets(10, 20, 0); Dialog.addMessage("An area selection exists that can be used for ROI selection and/or the Summary/Parameter location,\nyou can edit it here:", infoFontSize, infoColor); Dialog.addNumber("Starting", selPosStartX, 0, 5, "X"); Dialog.setInsets(-28, 150, 0); Dialog.addNumber("Starting", selPosStartY, 0, 5, "Y"); Dialog.addNumber("Selected", originalSelEWidth, 0, 5, "Width"); Dialog.setInsets(-28, 150, 0); Dialog.addNumber("Selected", originalSelEHeight, 0, 5, "Height"); } restrictions = newArray("No"); restriction = call("ij.Prefs.get", ascPrefsKey + "restriction", "No"); if (selectionExists) restrictions = Array.concat(restrictions, "ROIs within selection"); else if (restriction == "ROIs within selection") restriction = "No"; restrictions = Array.concat(restrictions, "ROIs listed below", "ROIs from csv file"); Dialog.addRadioButtonGroup("Restrict ROI list?", restrictions, 1, restrictions.length, restriction); Dialog.setInsets(0, 20, 0); subsetROIs = call("ij.Prefs.get", ascPrefsKey + "subsetROIs", subsetROIs); Dialog.addString("ROI subset", subsetROIs, 40); importCSVPath = call("ij.Prefs.get", ascPrefsKey + "importCSVPath", "---csv file expected---"); Dialog.addFile("Import ROI subset", importCSVPath); Dialog.addCheckbox("Diagnostics", false); Dialog.show; tPath = Dialog.getString(); if (imageN == 1) imageChoice = t; else if (imageN > 5) imageChoice = Dialog.getChoice(); else imageChoice = Dialog.getRadioButton(); parameterWithLabel = Dialog.getChoice; parameter = substring(parameterWithLabel, 0, indexOf(parameterWithLabel, ": ")); call("ij.Prefs.set", ascPrefsKey + "parameter", parameter); lut = Dialog.getChoice; call("ij.Prefs.set", ascPrefsKey + "lut", lut); revLut = Dialog.getCheckbox; call("ij.Prefs.set", ascPrefsKey + "revLut", revLut); stroke = Dialog.getNumber; call("ij.Prefs.set", ascPrefsKey + "stroke", stroke); opacity = Dialog.getNumber(); call("ij.Prefs.set", ascPrefsKey + "opacity", opacity); alpha = String.pad(toHex(255 * opacity / 100), 2); outlierChoice = Dialog.getRadioButton; call("ij.Prefs.set", ascPrefsKey + "outlierChoice", outlierChoice); if (outlierChoice != "No") sigmaR = (parseInt(substring(outlierChoice, 0, 1))); else sigmaR = NaN; outlierColor = Dialog.getChoice(); /* Outline color for outliers */ call("ij.Prefs.set", ascPrefsKey + "outlierColor", outlierColor); outlierColor2 = Dialog.getChoice(); /* Outline color for outliers */ call("ij.Prefs.set", ascPrefsKey + "outlierColor2", outlierColor2); outlierStrokePC = Dialog.getNumber(); /* default outline stroke: % of font size */ call("ij.Prefs.set", ascPrefsKey + "outlierStrokePC", outlierStrokePC); addLabels = Dialog.getCheckbox; if (selectionExists) { selPosStartX = Dialog.getNumber; selPosStartY = Dialog.getNumber; originalSelEWidth = Dialog.getNumber; originalSelEHeight = Dialog.getNumber; } restriction = Dialog.getRadioButton(); call("ij.Prefs.set", ascPrefsKey + "restriction", restriction); subsetROIs = Dialog.getString(); call("ij.Prefs.set", ascPrefsKey + "subsetROIs", subsetROIs); importCSVPath = Dialog.getString(); call("ij.Prefs.set", ascPrefsKey + "importCSVPat", importCSVPath); diagnostics = Dialog.getCheckbox(); if (restriction == "ROIs from csv file" && !startsWith(importCSVPath, "---")) { subsetROIs = File.openAsString(importCSVPath); subset = true; } else if (restriction == "ROIs listed below") { if (!startsWith(subsetROIs, "---")) subset = true; } else if (restriction == "ROIs within selection") { subset = false; subsetROIs = ""; setBatchMode(true); /* finds sub-elements inside and at selection */ roiManager("select", 0); roiStart = Roi.getName; roiManager("Deselect"); run("Restore Selection"); roiManager("Add"); roiManager("select", 0); roiStart2 = Roi.getName; if (roiStart == roiStart2) roiManager("select", nROIs); else roiManager("select", 0); iTempROI = roiManager("index"); roiManager("Rename", "Temp_SelectionROI"); for (i = 0, nSels = 0; i < nROIs + 1; i++) { if (i != iTempROI) { // roiManager("deselect"); roiManager("select", i); roiSize = getValue("selection.size"); roiManager("Select", newArray(i, iTempROI)); roiManager("AND"); if (getValue("selection.size") == roiSize) { subsetROIs += "" + i + 1 + ", "; nSels++; } } } roiManager("deselect"); RoiManager.selectByName("Temp_SelectionROI"); if (roiManager("index") == iTempROI) roiManager("delete"); roiManager("deselect"); if (endsWith(subsetROIs, ", ")) subsetROIs = substring(subsetROIs, 0, lengthOf(subsetROIs) - 1); subset = true; setBatchMode(false); } if (subset == true) { subsetArray = split(subsetROIs, ", , "); subsetArray = Array.sort(subsetArray); if (lengthOf(subsetArray) > 0) { items = lengthOf(subsetArray); IJ.log("Analysis restricted to " + items + " selected ROIs:"); Array.print(subsetArray); iROIs = newArray; for (i = 0; i < items; i++) iROIs[i] = parseInt(subsetArray[i]) - 1; } else subset = false; } if (subset == false) iROIs = Array.getSequence(nROIs); if (!diagnostics) setBatchMode(true); call("ij.Prefs.set", ascPrefsKey + "subset", subset); selectWindow(imageChoice); orID = getImageID(); /* update after selection of image */ t = getTitle(); if (outlierColor2 == "same") outlierColor2 = outlierColor; unitLabel = cleanLabel(unitLabelFromString(parameter, unit)); unitLabel = replace(unitLabel, degreeChar, "degrees"); /* replace lonely ° symbol */ /* get values for chosen parameter */ values = newArray(); if (parameter == "Object") for (i = 0; i < iROIs.length; i++) values[i] = iROIs[i] + 1; else for (i = 0; i < iROIs.length; i++) values[i] = getResult(parameter, iROIs[i]); Array.getStatistics(values, arrayMin, arrayMax, arrayMean, arraySD); arrayRange = arrayMax - arrayMin; rampMin = arrayMin; rampMax = arrayMax; rampMax = rangeFinder(rampMax, true); rampMin = rangeFinder(rampMin, false); rampRange = rampMax - rampMin; if (rampMin < 0.05 * rampRange) { rampMin = 0; rampRange = rampMax; } intStr = d2s(rampRange, -1); intStr = substring(intStr, 0, indexOf(intStr, "E")); numIntervals = parseFloat(intStr); if (numIntervals > 4) if (endsWith(d2s(numIntervals, 3), ".500")) numIntervals = round(numIntervals * 2); else if (numIntervals >= 2) { if (endsWith(d2s(numIntervals, 3), "00")) { if (endsWith(d2s(numIntervals * 5, 3), "000")) numIntervals = round(numIntervals * 5); else numIntervals = round(numIntervals * 10); } } else if (numIntervals < 2) numIntervals = Math.ceil(10 * numIntervals); else numIntervals = Math.ceil(5 * numIntervals); /* Just in case parameter still has units appended... */ pu1 = indexOf(parameter, "\("); pu2 = indexOf(parameter, "\)"); pu3 = indexOf(parameter, "0-90"); /* Exception for 0-90° label */ if (pu1 > 0 && pu2 > 0 && pu3 < 0) parameterLabel = parameter.substring(0, pu1); else parameterLabel = parameter; parameterLabelExp = expandLabel(parameterLabel); /* Create dialog prompt to determine look */ Dialog.create("Ramp \(Legend\) Options \(LUT " + lut + "\): ROI Color Coder V:" + macroV); Dialog.addString("Parameter label - edit for ramp/legend/title", parameterLabelExp, 30); Dialog.setInsets(-5, 20, 5); Dialog.addMessage("Do NOT include the 'unit' in the this label as it will be added from options below...\)", infoFontSize, infoWarningColor); unitChoices = newArray("Manual", "None"); unitLinearChoices = newArray(unitLabel, unit, "pixels", "%", "arb."); if (unit == "microns" && (unitLabel != ums || unitLabel != ums + sup2)) unitLinearChoices = Array.concat(ums, unitLinearChoices); unitAngleChoices = newArray(degreeChar, "degrees", "radians"); unitAreaChoices = newArray(unit + sup2, "pixels" + sup2); if (indexOf(parameter, "Area") >= 0) { if (unit == "microns" && (unitLabel != ums || unitLabel != ums + sup2)) unitAreaChoices = Array.concat(ums + sup2, unitAreaChoices); unitChoices = Array.concat(unitAreaChoices, unitChoices, unitLinearChoices, unitAngleChoices); } else if (indexOf(parameter, "Angle") >= 0) unitChoices = Array.concat(unitAngleChoices, unitChoices, unitLinearChoices, unitAreaChoices); else unitChoices = Array.concat(unitLinearChoices, unitChoices, unitAreaChoices, unitAngleChoices); if (unitLabel == "None" || unitLabel == "") dialogUnit = ""; else dialogUnit = " " + unitLabel; defaultUnit = unitChoices[0]; if (indexOf(parameterLabelExp, "HV") >= 0) defaultUnit = "None"; Dialog.addChoice("Unit label \(" + unitLabel + "\):", unitChoices, defaultUnit); Dialog.setInsets(-38, 400, 0); Dialog.addMessage("Default shown is based on\nthe selected parameter", infoFontSize, infoColor); unitSeparators = newArray("\(unit\)", ", unit", "[unit]", "{unit}"); iUnitSeparators = indexOfArray(unitSeparators, call("ij.Prefs.get", ascPrefsKey + "unitSeparator", "\(unit\)"), unitSeparators[0]); Dialog.addChoice("Unit separator\(s\) in label:", unitSeparators, unitSeparators[iUnitSeparators]); Dialog.setInsets(0, 20, 0); rangeMessage = "Original data range: " + arrayMin + "-" + arrayMax + " \(range = " + (arrayRange) + " " + dialogUnit + "\)"; if (outlierChoice == "1" + sigmaChar) rangeMessage += "\nOutlier range \(" + sigmaChar + "\): < " + (arrayMean - arraySD) + " > " + (arrayMean + arraySD) + dialogUnit; else if (outlierChoice == "2" + sigmaChar) rangeMessage += "\nOutlier range \(2 " + sigmaChar + "\): < " + (arrayMean - 2 * arraySD) + " > " + (arrayMean + 2 * arraySD) + dialogUnit; else rangeMessage += "\nOutlier range \(3" + sigmaChar + "\): < " + (arrayMean - 3 * arraySD) + " > " + (arrayMean + 3 * arraySD) + dialogUnit; Dialog.addMessage(rangeMessage, infoFontSize, infoColor); Dialog.addString("Legend \(ramp\) data range \(" + rampRange + "\):", rampMin + "-" + rampMax, 15); Dialog.setInsets(-20, 400, 0); /* top, left, bottom */ Dialog.addMessage("\(e.g. n-n\)", infoFontSize + 2, instructionColor); Dialog.addString("LUT colors applied across range \(n-n format\):", arrayMin + "-" + arrayMax, 15); Dialog.setInsets(-7, 10, 7); Dialog.addMessage("The LUT gradient will be remapped to this range \(limited by the ramp min and max\)\nBeyond this range the top and bottom LUT colors will be applied", infoFontSize, instructionColor); Dialog.setInsets(-4, 120, 0); Dialog.addCheckbox("Add legend \(ramp\) labels at Min. & Max. if inside Range", true); Dialog.addNumber("No. of major intervals:", round(numIntervals), 0, 3, "Major tick count will be + 1 more than this"); defTickN = parseInt(substring(d2s(rampRange / numIntervals, 1), 0, 1)) - 1; if (defTickN < 2) defTickN = 4; Dialog.addNumber("No. of ticks between major ticks:", defTickN, 0, 3, "i.e. 4 ticks for 5 minor intervals"); Dialog.addChoice("Decimal places:", newArray("Auto", "Manual", "Scientific", "0", "1", "2", "3", "4"), "Auto"); Dialog.addChoice("Legend \(ramp\) height \(pixels\):", newArray(d2s(rampH, 0), 128, 256, 512, 1024, 2048, 4096), rampH); Dialog.setInsets(-38, 350, 0); Dialog.addMessage(rampH + " pixels suggested\nby image height", infoFontSize, infoColor); fontStyleChoice = newArray("bold", "italic", "bold italic", "unstyled"); iFontStyle = indexOfArray(fontStyleChoice, call("ij.Prefs.get", ascPrefsKey + "rampFStyle", "bold"), 0); Dialog.addChoice("Font style:", fontStyleChoice, fontStyleChoice[iFontStyle]); fontNameChoice = getFontChoiceList(); Dialog.addChoice("Font name:", fontNameChoice, fontNameChoice[0]); Dialog.addNumber("Font_size \(height\):", fontSize, 0, 3, "pixels"); rampFormatChoices = newArray("Draw border and top/bottom ticks", "Force vertical \(rotated\) legend label", "Off-white interior legend labels"); brdr = call("ij.Prefs.get", ascPrefsKey + "brdr", true); rotLegend = call("ij.Prefs.get", ascPrefsKey + "rotLegend", false); offWhiteIntRampLabels = call("ij.Prefs.get", ascPrefsKey + "offWhiteIntRampLabels", false); rampFormatChecks = newArray(brdr, rotLegend, offWhiteIntRampLabels); if (iROIs.length > 5) { rampFormatChoices = Array.concat(rampFormatChoices, "Frequency plotted inside legend"); /* Assumed that a histogram is not useful enough if you only have 5 objects of less */ freqDistRamp = call("ij.Prefs.get", ascPrefsKey + "freqDistRamp", true); rampFormatChecks = Array.concat(rampFormatChecks, freqDistRamp); } Dialog.setInsets(0, 70, 0); Dialog.addCheckboxGroup(2, 2, rampFormatChoices, rampFormatChecks); if (iROIs.length > 3) { /* Assumed that stats are not useful enough if you only have 3 objects or less */ Dialog.setInsets(3, 0, -2); rampStatsOptions = newArray("No", "Linear", "Ln"); statsRampLines = call("ij.Prefs.get", ascPrefsKey + "statsRampLines", "Linear"); Dialog.setInsets(-20, 15, 18); Dialog.addRadioButtonGroup("Legend \(Ramp\) Stats: Mean and " + fromCharCode(0x00B1) + sigmaChar + " on ramp \(if \"Ln\" then outlier " + sigmaChar + " will be \"Ln\" too\)", rampStatsOptions, 1, 5, statsRampLines); /* will be used for sigma outlines too */ statsRampTick = parseInt(call("ij.Prefs.get", ascPrefsKey + "statsRampTickL", 50)); Dialog.addNumber("Tick length:", statsRampTick, 0, 3, "% of major tick. Also Min. & Max. Lines"); } thinLinesFontSTweak = parseInt(call("ij.Prefs.get", ascPrefsKey + "thinLinesFontSTweak", 100)); Dialog.addNumber("Label font:", 100, 0, 3, "% of font size. Also Min. & Max. Lines"); Dialog.setInsets(4, 120, 0); Dialog.addHelp("https://imagej.net/doku.php?id=macro:roi_color_coder"); Dialog.show; parameterLabel = Dialog.getString; unitLabel = Dialog.getChoice(); if (unitLabel == "None") unitLabel = ""; unitSeparator = Dialog.getChoice(); call("ij.Prefs.set", ascPrefsKey + "unitSeparator", unitSeparator); rangeS = Dialog.getString; /* changed from original to allow negative values - see below */ rangeLUT = Dialog.getString; if (rangeLUT == "same as ramp range") rangeLUT = rangeS; /* maintained for old preferences */ rampMinMaxLines = Dialog.getCheckbox; numIntervals = Dialog.getNumber; /* The number intervals along ramp */ numLabels = numIntervals + 1; /* The number of major ticks/labels is one more than the intervals */ minorTicks = Dialog.getNumber; /* The number of minor ticks/labels is one less than the intervals */ dpChoice = Dialog.getChoice; rampHChoice = parseInt(Dialog.getChoice); fontStyle = Dialog.getChoice; call("ij.Prefs.set", ascPrefsKey + "rampFStyle", fontStyle); if (fontStyle == "unstyled") fontStyle = ""; fontStyle += " antialiased"; /* why not? */ fontName = Dialog.getChoice(); fontSize = Dialog.getNumber(); brdr = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "brdr", brdr); rotLegend = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "rotLegend", rotLegend); offWhiteIntRampLabels = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "offWhiteIntRampLabels", offWhiteIntRampLabels); if (iROIs.length > 5) { freqDistRamp = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "freqDistRamp", freqDistRamp); } else { freqDistRamp = false; } if (iROIs.length > 3) { statsRampLines = Dialog.getRadioButton; call("ij.Prefs.set", ascPrefsKey + "statsRampLines", statsRampLines); statsRampTickL = Dialog.getNumber; call("ij.Prefs.set", ascPrefsKey + "statsRampTickL", statsRampTickL); } else statsRampLines = "No"; thinLinesFontSTweak = Dialog.getNumber; call("ij.Prefs.set", ascPrefsKey + "thinLinesFontSTweak", thinLinesFontSTweak); if (imageChoice != t) { t = imageChoice; tN = stripKnownExtensionFromString(t); tN = unCleanLabel(tN); } if (fontSize < 10) { acceptMinFontSize = getBoolean("A font size of 10 is the minimum recommended font size for the macro; increase font size to 10?"); if (acceptMinFontSize) fontSize = 10; } rampParameterLabel = cleanLabel(parameterLabel); rampW = round(rampH / 8); /* this will be updated later */ if (statsRampLines == "Ln") rampParameterLabel = rampParameterLabel + " \(ln stats\)"; rampUnitLabel = unitLabel.replace("^-", "-"); if (((rotLegend && rampHChoice == rampH)) || (rampW < maxOf(getStringWidth(rampUnitLabel), getStringWidth(rampParameterLabel)))) rampH = imageHeight - fontSize; /* tweaks automatic height selection for vertical legend */ else rampH = rampHChoice; rampW = round(rampH / 8); range = split(rangeS, "-"); if (lengthOf(range) == 1) { rampMin = NaN; rampMax = parseFloat(range[0]); } else { rampMin = parseFloat(range[0]); rampMax = parseFloat(range[1]); } if (indexOf(rangeS, "-") == 0) rampMin = 0 - rampMin; /* checks to see if rampMin is a negative value (lets hope the rampMax isn't). */ lutRange = split(rangeLUT, "-"); if (lengthOf(lutRange) == 1) { minLUT = NaN; maxLUT = parseFloat(lutRange[0]); } else { minLUT = parseFloat(lutRange[0]); maxLUT = parseFloat(lutRange[1]); } if (indexOf(rangeLUT, "-") == 0) minLUT = 0 - minLUT; /* checks to see if min is a negative value (lets hope the max isn't). */ /* Restrict LUT range to be within set ramp range: */ minLUT = maxOf(minLUT, rampMin); maxLUT = minOf(maxLUT, rampMax); fontSR2 = fontSize * thinLinesFontSTweak / 100; rampLW = maxOf(1, round(rampH / 512)); /* ramp line width with a minimum of 1 pixel */ minmaxLW = round(rampLW / 4); /* line widths for ramp stats */ if (isNaN(rampMin)) rampMin = arrayMin; if (isNaN(rampMax)) rampMax = arrayMax; rampRange = rampMax - rampMin; coeffVar = arraySD * 100 / arrayMean; if (iROIs.length > 2) { sortedValues = Array.copy(values); sortedValues = Array.sort(sortedValues); /* all this effort to get the median without sorting the original array! */ arrayQuartile = newArray(3); for (q = 0; q < 3; q++) arrayQuartile[q] = sortedValues[round((q + 1) * iROIs.length / 4)]; IQR = arrayQuartile[2] - arrayQuartile[0]; } else IQR = NaN; mode = NaN; autoDistW = NaN; /* The following section produces frequency/distribution data for the optional distribution plot on the ramp */ if (IQR > 0) { /* For some data sets IQR can be zero which produces an error in the distribution calculations */ autoDistW = 2 * IQR * exp((-1 / 3) * log(iROIs.length)); /* Uses the optimal binning of Freedman and Diaconis (summarized in [Izenman, 1991]), see https://www.fmrib.ox.ac.uk/datasets/techrep/tr00mj2/tr00mj2/node24.html */ autoDistWCount = round(arrayRange / autoDistW); arrayDistFreq = newArray(autoDistWCount); arrayDistInt = newArray(autoDistWCount); for (f = 0; f < autoDistWCount + 1; f++) arrayDistInt[f] = arrayMin + f * autoDistW; modalBin = 0; freqMax = 0; for (f = 0; f < autoDistWCount; f++) { for (i = 0; i < iROIs.length; i++) { if ((values[i] >= arrayDistInt[f]) && (values[i] < arrayDistInt[f + 1])) arrayDistFreq[f] += 1; } if (arrayDistFreq[f] > freqMax) { freqMax = arrayDistFreq[f]; modalBin = f; } } /* use adjacent bin estimate for mode */ if (modalBin > 0) mode = (arrayMin + (modalBin * autoDistW)) + autoDistW * ((arrayDistFreq[modalBin] - arrayDistFreq[maxOf(0, modalBin - 1)]) / ((arrayDistFreq[modalBin] - arrayDistFreq[maxOf(0, modalBin - 1)]) + (arrayDistFreq[modalBin] - arrayDistFreq[minOf(arrayDistFreq.length - 1, modalBin + 1)]))); Array.getStatistics(arrayDistFreq, freqMin, freqMax, freqMean, freqSD); if (isNaN(freqSD) || isNaN(mode)) { freqDistRamp = false; IJ.log("Unable to generate statistics required for in-legend histogram: freqSD = " + freqSD + ", mode = " + mode); } /* End of frequency/distribution section */ } else freqDistRamp = false; sIntervalsR = round(rampRange / arraySD); meanPlusSDs = newArray(sIntervalsR); meanMinusSDs = newArray(sIntervalsR); for (s = 0; s < sIntervalsR; s++) { meanPlusSDs[s] = arrayMean + (s * arraySD); meanMinusSDs[s] = arrayMean - (s * arraySD); } /* Calculate ln stats for summary and also ramp if requested */ lnValues = lnArray(values); Array.getStatistics(lnValues, null, null, lnMean, lnSD); expLnMeanPlusSDs = newArray(sIntervalsR); expLnMeanMinusSDs = newArray(sIntervalsR); expLnSD = exp(lnSD); for (s = 0; s < sIntervalsR; s++) { expLnMeanPlusSDs[s] = exp(lnMean + s * lnSD); expLnMeanMinusSDs[s] = exp(lnMean - s * lnSD); } /* Create the parameter label */ if (unitLabel == "Manual") { unitLabel = unitLabelFromString(parameter, unit); Dialog.create("Manual unit input"); Dialog.addString("Label:", unitLabel, 8); Dialog.addMessage("^2 & um etc. replaced by " + sup2 + " & " + fromCharCode(181) + "m...", infoFontSize, instructionColor); Dialog.show(); unitLabel = Dialog.getString(); } unitLabel = cleanLabel(unitLabel); /* Begin object color coding if stroke set */ if (stroke >= 0) { /* Create LUT-map legend */ rampTBMargin = 2 * fontSize; canvasH = round(2 * rampTBMargin + rampH); canvasH = round(4 * fontSize + rampH); canvasW = round(rampH / 2); tickL = round(rampW / 4); if (statsRampLines != "No" || rampMinMaxLines) tickL = round(tickL / 2); /* reduce tick length to provide more space for inside label */ if (statsRampLines != "No") tickLR = round(tickL * statsRampTickL / 100); else tickLR = round(tickL * tickL / 50); getLocationAndSize(imgx, imgy, imgwidth, imgheight); call("ij.gui.ImageWindow.setNextLocation", imgx + imgwidth, imgy); tR = replace(tN + "_" + parameterLabel + "_Ramp", " ", "_"); newImage(tR, "ramp", rampH, rampW, "8-bit"); /* Height and width swapped for later rotation */ /* ramp color/gray range is horizontal only so must be rotated later */ if (revLut) run("Flip Horizontally"); tR = getTitle; /* short variable label for ramp */ run(lut); /* modify lut if requested */ if (rangeLUT != rangeS) { /* recode legend if LUT over restricted range */ rampIncr = 255 / rampRange; maxLUTi = round((maxLUT - rampMin) * rampIncr); minLUTi = round((minLUT - rampMin) * rampIncr); lutIncr = 255 / (maxLUTi - minLUTi); getLut(reds, greens, blues); newReds = newArray(); newGreens = newArray(); newBlues = newArray(); for (i = 0; i < 256; i++) { if (i < minLUTi) { newReds[i] = reds[0]; newGreens[i] = greens[0]; newBlues[i] = blues[0]; } else if (i > maxLUTi) { newReds[i] = reds[255]; newGreens[i] = greens[255]; newBlues[i] = blues[255]; } else { newReds[i] = reds[round((i - minLUTi) * lutIncr)]; newGreens[i] = greens[round((i - minLUTi) * lutIncr)]; newBlues[i] = blues[round((i - minLUTi) * lutIncr)]; } } setLut(newReds, newGreens, newBlues); } roiColors = hexLutColors(); /* creates a hexColor array: requires function */ /* continue the legend design */ /* Frequency line if requested */ if (freqDistRamp) { rampRXF = rampH / (rampRange); /* RXF short for Range X Factor Units/pixel */ rampRYF = (rampW - 2 * rampLW) / freqMax; /* RYF short for Range Y Factor Freg/pixel - scale from zero */ distFreqPosX = newArray(); distFreqPosY = newArray(); for (f = 0; f < (autoDistWCount); f++) { distFreqPosX[f] = (arrayDistInt[f] - rampMin) * rampRXF; distFreqPosY[f] = arrayDistFreq[f] * rampRYF; } distFreqPosXIncr = distFreqPosX[autoDistWCount - 1] - distFreqPosX[autoDistWCount - 2]; fLastX = newArray(distFreqPosX[autoDistWCount - 1] + distFreqPosXIncr, ""); distFreqPosX = Array.concat(distFreqPosX, fLastX); freqDLW = maxOf(1, round(rampLW / 2)); setLineWidth(freqDLW); for (f = 0; f < (autoDistWCount); f++) { /* Draw All Shadows First */ setColor(0, 0, 0); /* Don't change to "black". Note that this color will be converted to LUT equivalent */ if (arrayDistFreq[f] > 0) { drawLine(distFreqPosX[f] - freqDLW, freqDLW, distFreqPosX[f] - freqDLW, distFreqPosY[f] - freqDLW); drawLine(distFreqPosX[f] - freqDLW, distFreqPosY[f] - freqDLW, distFreqPosX[f + 1] - freqDLW, distFreqPosY[f] - freqDLW); /* Draw bar top */ drawLine(distFreqPosX[f + 1] - freqDLW, freqDLW, distFreqPosX[f + 1] - freqDLW, distFreqPosY[f] - freqDLW); /* Draw bar side */ } } for (f = 0; f < autoDistWCount; f++) { setColor(250, 250, 250); /* Note that this color will be converted to LUT equivalent */ if (arrayDistFreq[f] > 0) { drawLine(distFreqPosX[f], freqDLW, distFreqPosX[f], distFreqPosY[f]); /* Draw bar side - right/bottom */ drawLine(distFreqPosX[f], distFreqPosY[f], distFreqPosX[f + 1], distFreqPosY[f]); /* Draw bar cap */ drawLine(distFreqPosX[f + 1], freqDLW, distFreqPosX[f + 1], distFreqPosY[f]); /* Draw bar side - left/top */ } } } setColor(0, 0, 0); /* Don't change to "black" */ setBackgroundColor(255, 255, 255); /* Don't change to "white" */ numLabelFontSize = minOf(fontSize, rampH / numLabels); if ((numLabelFontSize < 10) && acceptMinFontSize) numLabelFontSize = maxOf(10, numLabelFontSize); setFont(fontName, numLabelFontSize, fontStyle); if (imageDepth != 8 || lut != "Grays") run("RGB Color"); /* converts ramp to RGB if not using grays only */ setLineWidth(rampLW * 2); if (brdr) { drawRect(0, 0, rampH, rampW); /* The next steps add the top and bottom ticks */ rampWT = rampW + 2 * rampLW; run("Canvas Size...", "width=" + rampH + " height=" + rampWT + " position=Top-Center"); setLineWidth(rampLW * 1.5); drawLine(0, 0, 0, rampW - 1 + rampLW); /* Draw full width line at top an bottom */ drawLine(rampH - 1, 0, rampH - 1, rampW - 1 + rampLW); /* Draw full width line at top an d bottom */ } run("Rotate 90 Degrees Left"); run("Canvas Size...", "width=" + canvasW + " height=" + canvasH + " position=Center-Left"); if (dpChoice == "Auto") decPlaces = autoCalculateDecPlaces3(rampMin, rampMax, numIntervals); else if (dpChoice == "Manual") decPlaces = getNumber("Choose Number of Decimal Places", 0); else if (dpChoice == "Scientific") decPlaces = -1; else decPlaces = parseFloat(dpChoice); if (parameter == "Object") decPlaces = 0; /* This should be an integer */ /* draw ticks and values */ rampOffset = (getHeight - rampH) / 2; step = rampH; if (numLabels > 2) step /= (numIntervals); stepV = rampRange / numIntervals; if (diagnostics) IJ.log("numIntervals: " + numIntervals + ", step: " + step + ", rampH: " + rampH + ", numLabels: " + numLabels + ", stepV: " + stepV); /* Create array of ramp labels that can be used to optimize label length */ rampLabelString = newArray; for (i = 0, maxDP = 0; i < numLabels; i++) { rampLabel = rampMin + i * stepV; rampLabelString[i] = d2s(rampLabel, decPlaces); } /* Ramp number label cleanup */ for (i = 0; i < decPlaces; i++) { for (nL = 0, allEndZeros = true; nL < numLabels; nL++) if (!endsWith(rampLabelString[nL], "0") && indexOf(rampLabelString[nL], ".") >= 0) allEndZeros = false; for (nL = 0; nL < numLabels && allEndZeros; nL++) if (indexOf(rampLabelString[nL], ".") >= 0) rampLabelString[nL] = substring(rampLabelString[nL], 0, rampLabelString[nL].length - 1); for (nL = 0, allEndPeriods = true; nL < numLabels; nL++) if (!endsWith(rampLabelString[nL], ".")) allEndPeriods = false; for (nL = 0; nL < numLabels && allEndPeriods; nL++) rampLabelString[nL] = substring(rampLabelString[nL], 0, rampLabelString[nL].length - 1); for (nL = 0; nL < numLabels && !allEndPeriods; nL++) if (endsWith(rampLabelString[nL], ".")) rampLabelString[nL] = rampLabelString[nL] + "0"; } /* clean up top and bottom zero labels are special cases even in non-auto mode */ for (i = 0; i < numLabels; i = i + numLabels - 1) if (parseFloat(rampLabelString[i]) == 0) rampLabelString[i] = "0"; /* end of ramp number label cleanup */ setLineWidth(rampLW); for (i = 0; i < numLabels; i++) { yPos = rampH + rampOffset - i * step - 1; /* minus 1 corrects for coordinates starting at zero */ /*Now add overrun text labels at the top and/or bottom of the ramp if the true data extends beyond the ramp range */ if (i == 0 && rampMin > (1.001 * arrayMin)) rampLabelString[i] = fromCharCode(0x2264) + rampLabelString[i]; if (i == (numLabels - 1) && rampMax < (0.999 * arrayMax)) rampLabelString[i] = fromCharCode(0x2265) + rampLabelString[i]; drawString(rampLabelString[i], rampW + 4 * rampLW, yPos + numLabelFontSize / 1.5); /* major ticks are not optional in this version as they are needed to make sense of the ramp labels */ if ((i > 0) && (i < (numIntervals))) { setLineWidth(rampLW); drawLine(0, yPos, tickL, yPos); /* left tick */ drawLine(rampW - 1 - tickL, yPos, rampW, yPos); drawLine(rampW, yPos, rampW + rampLW, yPos); /* right tick extends over border slightly as subtle cross-tick */ } /* end of ramp major tick drawing */ } setFont(fontName, fontSize, fontStyle); /* draw minor ticks */ if (minorTicks > 0) { minorTickStep = step / (minorTicks + 1); numTick = numLabels + numIntervals * minorTicks - 1; /* no top tick */ for (i = 1; i < numTick; i++) { /* no bottom tick */ yPos = rampH + rampOffset - i * minorTickStep - 1; /* minus 1 corrects for coordinates starting at zero */ setLineWidth(round(rampLW / 4)); drawLine(0, yPos, tickLR, yPos); /* left minor tick */ drawLine(rampW - tickLR - 1, yPos, rampW - 1, yPos); /* right minor tick */ } } /* end draw minor ticks */ /* now add lines and the true min and max and for stats if chosen in previous dialog */ if (rampMinMaxLines || statsRampLines != "No") { newImage("ramp_Label-Mask", "8-bit black", getWidth(), getHeight(), 1); setColor("white"); setLineWidth(rampLW); minPos = 0; maxPos = rampH; /* to be used in later sd overlap if statement */ if (rampMinMaxLines) { if (rampMin == rampMax) restoreExit("Something terribly wrong with this range!"); trueMaxFactor = (arrayMax - rampMin) / (rampRange); maxPos = rampTBMargin + (rampH * (1 - trueMaxFactor)) - 1; trueMinFactor = (arrayMin - rampMin) / (rampRange); minPos = rampTBMargin + (rampH * (1 - trueMinFactor)) - 1; if (trueMaxFactor < 1 && maxPos < (rampH - 0.5 * fontSR2)) { setFont(fontName, fontSR2, fontStyle); stringY = round(maxOf(maxPos + 0.75 * fontSR2, rampTBMargin + 0.75 * fontSR2)); drawString("Max", round((rampW - getStringWidth("Max")) / 2), stringY); drawLine(rampLW, maxPos, tickLR, maxPos); drawLine(rampW - 1 - tickLR, maxPos, rampW - rampLW - 1, maxPos); } if (trueMinFactor > 0 && minPos > (0.5 * fontSR2)) { setFont(fontName, fontSR2, fontStyle); stringY = round(minOf(minPos + 0.75 * fontSR2, rampTBMargin + rampH - 0.25 * fontSR2)); drawString("Min", round((rampW - getStringWidth("Min")) / 2), stringY); drawLine(rampLW, minPos, tickLR, minPos); drawLine(rampW - 1 - tickLR, minPos, rampW - rampLW - 1, minPos); } } if (statsRampLines != "No") { rampMeanPlusSDFactors = newArray(sIntervalsR); rampMeanMinusSDFactors = newArray(sIntervalsR); plusSDPos = newArray(sIntervalsR); minusSDPos = newArray(sIntervalsR); if (statsRampLines == "Ln") { rampSD = exp(lnSD); rampMeanPlusSDs = expLnMeanPlusSDs; rampMeanMinusSDs = expLnMeanMinusSDs; } else { rampSD = arraySD; rampMeanPlusSDs = meanPlusSDs; rampMeanMinusSDs = meanMinusSDs; } for (s = 0; s < sIntervalsR; s++) { rampMeanPlusSDFactors[s] = (rampMeanPlusSDs[s] - rampMin) / rampRange; rampMeanMinusSDFactors[s] = (rampMeanMinusSDs[s] - rampMin) / rampRange; plusSDPos[s] = rampTBMargin + (rampH * (1 - rampMeanPlusSDFactors[s])) - 1; minusSDPos[s] = rampTBMargin + (rampH * (1 - rampMeanMinusSDFactors[s])) - 1; } meanFS = 0.9 * fontSR2; setFont(fontName, meanFS, fontStyle); if ((rampMeanPlusSDs[0] > (rampMin + 0.2 * rampRange)) && ((rampMeanPlusSDs[0] - rampMin) <= (0.92 * rampRange))) { drawString("Mean", round((rampW - getStringWidth("Mean")) / 2), plusSDPos[0] + 0.75 * meanFS); drawLine(rampLW, plusSDPos[0], tickLR, plusSDPos[0]); drawLine(rampW - 1 - tickLR, plusSDPos[0], rampW - rampLW - 1, plusSDPos[0]); } else IJ.log("Warning: Mean not drawn on ramp as determined to be to be out of filled ramp range"); lastDrawnPlusSDPos = plusSDPos[0]; sPLimit = lengthOf(rampMeanPlusSDFactors) - 1; /* should be sIntervalsR but this was a voodoo fix for some issue here */ sMLimit = lengthOf(rampMeanMinusSDFactors) - 1; /* should be sIntervalsR but this was a voodoo fix for some issue here */ for (s = 1; s < sIntervalsR; s++) { if ((rampMeanPlusSDFactors[minOf(sPLimit, s)] <= 1) && (plusSDPos[s] <= (rampH - fontSR2)) && (abs(plusSDPos[s] - lastDrawnPlusSDPos) > 0.75 * fontSR2)) { setFont(fontName, fontSR2, fontStyle); if (rampMinMaxLines) { if (plusSDPos[s] <= (maxPos - 0.9 * fontSR2) || plusSDPos[s] >= (maxPos + 0.9 * fontSR2)) { /* prevent overlap with max line */ drawString(" + " + s + sigmaChar, round((rampW - getStringWidth(" + " + s + sigmaChar)) / 2), round(plusSDPos[s] + 0.75 * fontSR2)); drawLine(rampLW, plusSDPos[s], tickLR, plusSDPos[s]); drawLine(rampW - 1 - tickLR, plusSDPos[s], rampW - rampLW - 1, plusSDPos[s]); lastDrawnPlusSDPos = plusSDPos[s]; } } else { drawString(" + " + s + sigmaChar, round((rampW - getStringWidth(" + " + s + sigmaChar)) / 2), round(plusSDPos[s] + 0.75 * fontSR2)); drawLine(rampLW, plusSDPos[s], tickLR, plusSDPos[s]); drawLine(rampW - 1 - tickLR, plusSDPos[s], rampW - rampLW - 1, plusSDPos[s]); lastDrawnPlusSDPos = plusSDPos[s]; } if (rampMeanPlusSDFactors[minOf(sPLimit, minOf(sIntervalsR, s + 1))] >= 0.98) s = sIntervalsR; } } lastDrawnMinusSDPos = minusSDPos[0]; for (s = 1; s < sIntervalsR; s++) { if ((rampMeanMinusSDFactors[minOf(sPLimit, s)] > 0) && (minusSDPos[s] > fontSR2) && (abs(minusSDPos[s] - lastDrawnMinusSDPos) > 0.75 * fontSR2)) { setFont(fontName, fontSR2, fontStyle); if (rampMinMaxLines) { if ((minusSDPos[s] < (minPos - 0.9 * fontSR2)) || (minusSDPos[s] > (minPos + 0.9 * fontSR2))) { /* prevent overlap with min line */ drawString("-" + s + sigmaChar, round((rampW - getStringWidth("-" + s + sigmaChar)) / 2), round(minusSDPos[s] + 0.5 * fontSR2)); drawLine(rampLW, minusSDPos[s], tickLR, minusSDPos[s]); drawLine(rampW - 1 - tickLR, minusSDPos[s], rampW - rampLW - 1, minusSDPos[s]); lastDrawnMinusSDPos = minusSDPos[s]; } } else { drawString("-" + s + sigmaChar, round((rampW - getStringWidth("-" + s + sigmaChar)) / 2), round(minusSDPos[s] + 0.5 * fontSR2)); drawLine(rampLW, minusSDPos[s], tickLR, minusSDPos[s]); drawLine(rampW - 1 - tickLR, minusSDPos[s], rampW - rampLW - 1, minusSDPos[s]); lastDrawnMinusSDPos = minusSDPos[s]; } if (rampMeanMinusSDs[minOf(sMLimit, minOf(sIntervalsR, s + 1))] < 0.93 * rampMin) s = sIntervalsR; } } } run("Duplicate...", "title=rampStats_textImage"); /* now use a mask to create black outline around white text to stand out against ramp colors */ selectWindow("ramp_Label-Mask"); rampOutlineStroke = maxOf(1, round(rampLW / 2)); setThreshold(0, 128); setOption("BlackBackground", false); run("Convert to Mask"); selectWindow(tR); run("Select None"); getSelectionFromMask("ramp_Label-Mask"); getSelectionBounds(maskX, maskY, null, null); if (rampOutlineStroke > 0) rampOutlineOffset = maxOf(0, (rampOutlineStroke / 2) - 1); setSelectionLocation(maskX + rampOutlineStroke, maskY + rampOutlineStroke); /* Offset selection to create shadow effect */ run("Enlarge...", "enlarge=" + rampOutlineStroke + " pixel"); setBackgroundColor(0, 0, 0); run("Clear"); run("Enlarge...", "enlarge=" + rampOutlineStroke + " pixel"); run("Gaussian Blur...", "sigma=" + rampOutlineStroke); run("Select None"); getSelectionFromMask("ramp_Label-Mask"); if (offWhiteIntRampLabels) setBackgroundColor(245, 245, 245); /* set to off-white so that these labels are not transparent when white is set as the transparent layer */ else setBackgroundColor(255, 255, 255); run("Clear"); run("Select None"); setBackgroundColor(255, 255, 255); /* Restore background to white for ramp expansion */ /* The following steps smooth the interior of the text labels */ selectWindow("rampStats_textImage"); getSelectionFromMask("ramp_Label-Mask"); if (selectionType() >= 0) run("Make Inverse"); else restoreExit("Ramp creation: No selection to invert"); run("Invert"); run("Select None"); imageCalculator("Min", tR, "rampStats_textImage"); if (!diagnostics) closeImageByTitle("ramp_Label-Mask"); if (!diagnostics) closeImageByTitle("rampStats_textImage"); /* reset colors and font */ setFont(fontName, fontSize, fontStyle); setColor(0, 0, 0); /* Color right sigma tick mark with outlier color for outlier range */ if (statsRampLines != "No") { lastDrawnPlusSDPos = plusSDPos[0]; setColorFromColorName(outlierColor); for (s = 1; s < sIntervalsR; s++) { if ((outlierChoice != "No") && (s >= sigmaR)) { if ((rampMeanPlusSDFactors[minOf(sPLimit, s)] <= 1) && (plusSDPos[s] <= (rampH - fontSR2)) && (abs(plusSDPos[s] - lastDrawnPlusSDPos) > 0.75 * fontSR2)) { if (rampMinMaxLines) { if ((plusSDPos[s] <= (maxPos - 0.75 * fontSR2)) || (plusSDPos[s] >= (maxPos + 0.75 * fontSR2))) { /* prevent overlap with max line */ drawLine(rampW - 1 - tickLR, plusSDPos[s] + rampLW * 0.75, rampW - rampLW - 1, plusSDPos[s] + rampLW * 0.75); drawLine(rampW - 1 - tickLR, plusSDPos[s] - rampLW * 0.75, rampW - rampLW - 1, plusSDPos[s] - rampLW * 0.75); } } else { drawLine(rampW - 1 - tickLR, plusSDPos[s] + rampLW * 0.75, rampW - rampLW - 1, plusSDPos[s] + rampLW * 0.75); drawLine(rampW - 1 - tickLR, plusSDPos[s] - rampLW * 0.75, rampW - rampLW - 1, plusSDPos[s] - rampLW * 0.75); lastDrawnPlusSDPos = plusSDPos[s]; } if (rampMeanPlusSDFactors[minOf(sPLimit, minOf(sIntervalsR, s + 1))] >= 0.98) s = sIntervalsR; } } } setColorFromColorName(outlierColor2); lastDrawnMinusSDPos = minusSDPos[0]; for (s = 1; s < sIntervalsR; s++) { if ((outlierChoice != "No") && (s >= sigmaR)) { if ((rampMeanMinusSDFactors[minOf(sMLimit, s)] > 0) && (minusSDPos[s] > fontSR2) && (abs(minusSDPos[s] - lastDrawnMinusSDPos) > 0.75 * fontSR2)) { if (rampMinMaxLines) { if (minusSDPos[s] < (minPos - 0.75 * fontSR2) || minusSDPos[s] > (minPos + 0.75 * fontSR2)) { /* prevent overlap with min line */ drawLine(rampW - 1 - tickLR, minusSDPos[s] + rampLW * 0.75, rampW - rampLW - 1, minusSDPos[s] + rampLW * 0.75); drawLine(rampW - 1 - tickLR, minusSDPos[s] - rampLW * 0.75, rampW - rampLW - 1, minusSDPos[s] - rampLW * 0.75); lastDrawnMinusSDPos = minusSDPos[s]; } } else { drawLine(rampW - 1 - tickLR, minusSDPos[s] + rampLW * 0.75, rampW - rampLW - 1, minusSDPos[s] + rampLW * 0.75); drawLine(rampW - 1 - tickLR, minusSDPos[s] - rampLW * 0.75, rampW - rampLW - 1, minusSDPos[s] - rampLW * 0.75); lastDrawnMinusSDPos = minusSDPos[s]; } if (rampMeanMinusSDs[minOf(sMLimit, minOf(sIntervalsR, s + 1))] < 0.93 * rampMin) s = sIntervalsR; } } } } } setColor(0, 0, 0); /* parse symbols in unit and draw final label below ramp */ selectWindow(tR); if ((rampW > maxOf(getStringWidth(rampUnitLabel), getStringWidth(rampParameterLabel))) && !rotLegend) { /* can center align if labels shorter than ramp width */ if (rampParameterLabel != "") drawString(rampParameterLabel, round((rampW - (getStringWidth(rampParameterLabel))) / 2), round(1.5 * fontSize)); if (rampUnitLabel != "") drawString(rampUnitLabel, round((rampW - (getStringWidth(rampUnitLabel))) / 2), round(canvasH - 0.5 * fontSize)); } else { /* need to left align if labels are longer and increase distance from ramp */ autoCropGuessBackgroundSafe(); /* toggles batch mode */ getDisplayedArea(null, null, canvasW, canvasH); run("Rotate 90 Degrees Left"); canvasW = getHeight + round(2.5 * fontSize); if (rampUnitLabel != "") { if (unitSeparator == "\(unit\)") rampParameterLabel += " \(" + rampUnitLabel + "\)"; else if (unitSeparator == "[unit]") rampParameterLabel += " [" + rampUnitLabel + "]"; else if (unitSeparator == "{unit}") rampParameterLabel += " {" + rampUnitLabel + "}"; else rampParameterLabel += ", " + rampUnitLabel; } run("Canvas Size...", "width=" + canvasH + " height=" + canvasW + " position=Bottom-Center"); if (rampParameterLabel != "") { rampParLabL = getStringWidth(rampParameterLabel); if (rampParLabL > 0.9 * canvasH) { modFSRLab = 0.9 * fontSize * canvasH / rampParLabL; setFont(fontName, modFSRLab, fontStyle); drawString(rampParameterLabel, round((canvasH - (getStringWidth(rampParameterLabel))) / 2), round(1.5 * fontSize)); setFont(fontName, fontSize, fontStyle); } else drawString(rampParameterLabel, round((canvasH - (getStringWidth(rampParameterLabel))) / 2), round(1.5 * fontSize)); } run("Rotate 90 Degrees Right"); } autoCropGuessBackgroundSafe(); /* toggles batch mode */ /* add padding to legend box - better than expanding crop selection as is adds padding to all sides */ getDisplayedArea(null, null, canvasW, canvasH); canvasW += round(imageWidth / 150); canvasH += round(imageHeight / 150); run("Canvas Size...", "width=" + canvasW + " height=" + canvasH + " position=Center"); /* iterate through the ROI Manager list and colorize ROIs */ selectImage(orID); /* iterate through the ROI Manager list and colorize ROIs */ roiManager("Deselect"); roiManager("Show All"); roiManager("Show None"); if (subset) { /* clear to full transparency area and line colors from any previous run */ for (i = 0; i < nROIs; i++) { /* Needs to be ALL ROIs */ roiManager("select", i); roiManager("Set Line Width", 0); roiManager("Set Color", "none"); roiManager("Set Fill Color", "#00ffffff"); } if (!selectionExists) { /* Starts the creation of a selection area for later cropping */ selPosStartX = imageWidth; selPosStartY = imageHeight; selPosEndX = 0; selPosEndY = 0; } } for (countNaN = 0, i = 0; i < iROIs.length; i++) { if (isNaN(values[i])) countNaN++; if (!revLut) { if (values[i] <= rampMin) lutIndex = 0; else if (values[i] > rampMax) lutIndex = 255; else lutIndex = round(255 * (values[i] - rampMin) / (rampRange)); } else { if (values[i] <= rampMin) lutIndex = 255; else if (values[i] > rampMax) lutIndex = 0; else lutIndex = round(255 * (rampMax - values[i]) / (rampRange)); } roiManager("select", iROIs[i]); if (stroke > 0) { roiManager("Set Line Width", stroke); roiManager("Set Color", alpha + roiColors[lutIndex]); } else { roiManager("Set Line Width", 0); roiManager("Set Color", "#00ffffff"); roiManager("Set Fill Color", alpha + roiColors[lutIndex]); } if (subset && !selectionExists) { /* For the creation of a selection area for later cropping */ getSelectionBounds(x, y, width, height); selPosStartX = minOf(selPosStartX, x); selPosStartY = minOf(selPosStartY, y); selPosEndX = maxOf(selPosEndX, x + width); selPosEndY = maxOf(selPosEndY, y + height); } } if (subset && !selectionExists) { originalSelEWidth = selPosEndX - selPosStartX; originalSelEHeight = selPosEndY - selPosStartY; } if (!addLabels) roiManager("show all with labels"); else roiManager("show all without labels"); run("Flatten"); /* creates an RGB copy of the image with color coded objects or not */ run("Remove Overlay"); tN = tN + "_" + parameterLabel + "_coded"; rename(tN); } else { IJ.log("Stroke/fill option set to labels only , we are headed into untested territory D:"); decPlaces = autoCalculateDecPlaces3(rampMin, rampMax, numIntervals); } workingImage = getTitle(); /* End of object coloring */ /* recombine units and labels that were used in Ramp */ paraLabel = parameterLabel; paraLabelExp = parameterLabelExp; if (unitLabel != "") { paraLabel = parameterLabel + ", " + unitLabel; paraLabelExp = parameterLabelExp + ", " + unitLabel; } /* Now to add scaled object labels */ /* First: set default label settings */ shadowDropPC = 10; /* default outer shadow drop: % of font size */ dIShOPC = 4; /* default inner shadow drop: % of font size */ offsetX = maxOf(1, round(imageWidth / 150)); /* default offset of label from edge */ offsetY = maxOf(1, round(imageHeight / 150)); /* default offset of label from edge */ fontColor = "white"; outlineColor = "black"; paraLabFontSize = round((imageHeight + imageWidth) / 75); if ((paraLabFontSize < 10) && acceptMinFontSize) paraLabFontSize = 11; statsLabFontSize = round((imageHeight + imageWidth) / 100); if ((statsLabFontSize < 10) && acceptMinFontSize) statsLabFontSize = 10; /* Feature Label Formatting Options Dialog . . . */ Dialog.create("Feature Label Formatting Options \(" + macroV + "\)"); Dialog.setInsets(0, 150, 6); Dialog.addCheckbox("Add feature labels to each ROI?", false); allGrays = newArray("white", "black", "off-white", "off-black", "lightGray", "gray", "darkGray"); if (lut != "Grays") colorChoices = Array.concat(allGrays, allColors); else colorChoices = allGrays; if (offWhiteIntRampLabels) objectFontColor = "off-white"; else objectFontColor = "white"; objectFontColor = call("ij.Prefs.get", ascPrefsKey + "objectFontColor", objectFontColor); iColor = indexOfArray(colorChoices, objectFontColor, 2); Dialog.addChoice("Object label color:", colorChoices, colorChoices[iColor]); Dialog.addNumber("Font scaling:", 60, 0, 3, "\% of auto \(" + round(fontSize) + "\)"); minROIFont = round(imageWidth / 90); if ((minROIFont < 10) && acceptMinFontSize) minROIFont = 10; Dialog.addNumber("Restrict label font size:", minROIFont, 0, 4, "Min to "); Dialog.setInsets(-28, 90, 0); maxROIFont = round(imageWidth / 16); if ((maxROIFont < 10) && acceptMinFontSize) maxROIFont = 10; Dialog.addNumber("Max", maxROIFont, 0, 4, "Max"); iFontStyle = indexOfArray(fontStyleChoice, call("ij.Prefs.get", ascPrefsKey + "objFStyle", "bold"), 0); Dialog.addChoice("Font style:", fontStyleChoice, fontStyleChoice[iFontStyle]); /* Reuse font list from previous dialog */ Dialog.addChoice("Font name:", fontNameChoice, fontName); /* Default to previous fontName */ Dialog.addChoice("Decimal places \(Fixed_Auto=" + decPlaces + "\):", newArray("Fixed_Auto", "Auto_Trimmed", "Manual", "Scientific", "0", "1", "2"), "Fixed_Auto"); /* reuse previous dpChoice as default */ Dialog.setInsets(-6, 100, 6); Dialog.addMessage("Auto_Trimmed removes point-trailing zeros", infoFontSize, infoColor); Dialog.addNumber("Label outline stroke:", outlineStrokePC, 0, 3, "% of auto mean size"); iColor = indexOfArray(colorChoices, call("ij.Prefs.get", ascPrefsKey + "objectOutlineColor", colorChoices[1]), 0); Dialog.addChoice("Label outline \(background\) color:", colorChoices, colorChoices[iColor]); if (menuLimit > 796) { Dialog.addNumber("Shadow drop: " + fromCharCode(0x00B1), shadowDropPC, 0, 3, "% of mean font size"); Dialog.addNumber("Shadow displacement right: " + fromCharCode(0x00B1), shadowDropPC, 0, 3, "% of mean font size"); Dialog.addNumber("Shadow Gaussian blur:", floor(0.75 * shadowDropPC), 0, 3, "% of mean font size"); Dialog.addNumber("Shadow darkness \(darkest = 100%\):", 50, 0, 3, "%, neg.= glow"); } else Dialog.addCheckbox("Tweak label format?", false); fancyInnerEffectsChoices = newArray("None", "Recessed", "Raised"); fancyInnerEffectsChoice = call("ij.Prefs.get", ascPrefsKey + "innerEffects", "None"); Dialog.addChoice("Inner text effects \(font size " + geq + "12\):", fancyInnerEffectsChoices, fancyInnerEffectsChoice); Dialog.setInsets(3, 0, 3); if (isNaN(getResult("mc_X\(px\)", 0)) && (checkForPlugin("morphology_collection"))) { Dialog.addChoice("Object labels at: ", newArray("ROI Center", "Morphological Center"), "ROI Center"); Dialog.setInsets(-3, 40, 6); Dialog.addMessage("If selected, morphological centers will be added to the results table", infoFontSize, instructionColor); } else if (isNaN(getResult("mc_X\(px\)", 0)) && (!checkForPlugin("morphology_collection"))) { Dialog.addChoice("Object labels at: ", newArray("ROI Center"), "ROI Center"); Dialog.setInsets(-3, 40, 6); Dialog.addMessage("Morphology plugin not available to find morphological centers", infoFontSize, infoWarningColor); } else Dialog.addChoice("Object label at:", newArray("ROI Center", "Morphological Center"), "Morphological Center"); paraLabAdd = call("ij.Prefs.get", ascPrefsKey + "paraLabAdd", false); Dialog.addCheckbox("Add parameter label title: \(" + paraLabel + "\)?", paraLabAdd); summaryOutputChecks = newArray("Summary->Image", "Summary->Log", "Summary->File"); summaryToImage = call("ij.Prefs.get", ascPrefsKey + "summaryToImage", false); summaryToLog = call("ij.Prefs.get", ascPrefsKey + "summaryToLog", true); summaryToFile = call("ij.Prefs.get", ascPrefsKey + "summaryToFile", true); Dialog.addCheckboxGroup(1, 3, summaryOutputChecks, newArray(summaryToImage, summaryToLog, summaryToFile)); if (selectionExists) paraLocChoice = newArray("Current Selection", "Top Left", "Top Right", "Center", "Bottom Left", "Bottom Right", "At New Selection"); else paraLocChoice = newArray("Top Left", "Top Right", "Center", "Bottom Left", "Bottom Right", "At New Selection"); Dialog.addChoice("Title and summary table location:", paraLocChoice, paraLocChoice[0]); if (menuLimit > 752) Dialog.addNumber("How many rows in table?", 16, 0, 2, ""); else Dialog.addNumber("How many rows in table?", 8, 0, 2, ""); Dialog.show(); addLabels = Dialog.getCheckbox; objectFontColor = Dialog.getChoice(); /* Object label color */ call("ij.Prefs.set", ascPrefsKey + "objectFontColor", objectFontColor); fontSCorrection = (Dialog.getNumber) / 100; minLFontS = Dialog.getNumber(); maxLFontS = Dialog.getNumber(); fontStyle = Dialog.getChoice(); call("ij.Prefs.set", ascPrefsKey + "objFStyle", fontStyle); if (fontStyle == "unstyled") fontStyle = ""; fontStyle += " antialiased"; fontName = Dialog.getChoice(); call("ij.Prefs.set", ascPrefsKey + "objFCol", fontName); dpChoice = Dialog.getChoice(); outlineStrokePC = Dialog.getNumber(); outlineColor = Dialog.getChoice(); call("ij.Prefs.set", ascPrefsKey + "objectOutlineColor", outlineColor); if (menuLimit > 800) { shadowDrop = Dialog.getNumber(); shadowDisp = Dialog.getNumber(); shadowBlur = Dialog.getNumber(); shadowDarkness = Dialog.getNumber(); tweakLabels = false; } else tweakLabels = Dialog.getCheckbox(); innerTextEffect = Dialog.getChoice(); call("ij.Prefs.set", ascPrefsKey + "innerEffects", innerTextEffect); ctrChoice = Dialog.getChoice(); /* Choose ROI or morphological centers for object labels */ paraLabAdd = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "paraLabAdd", paraLabAdd); summaryToImage = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "summaryToImage", summaryToImage); summaryToLog = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "summaryToLog", summaryToLog); summaryToFile = Dialog.getCheckbox(); call("ij.Prefs.set", ascPrefsKey + "summaryToFile", summaryToFile); summaryChoice = ""; if (summaryToImage || summaryToLog || summaryToFile) { summaryAdd = true; if (summaryToImage) summaryChoice += "to image, "; if (summaryToLog) summaryChoice += "to log, "; if (summaryToFile) summaryChoice += "to file, "; summaryChoice += "end"; summaryChoice = replace(summaryChoice, ", end", ""); } else summaryAdd = false; paraLabPos = Dialog.getChoice(); /* Parameter Label Position */ statsChoiceLines = Dialog.getNumber(); if (menuLimit <= 796) { if (tweakLabels) { Dialog.create("Label tweak options for low resolution monitors"); Dialog.addNumber("Shadow drop: " + fromCharCode(0x00B1), shadowDropPC, 0, 3, "% of mean font size"); Dialog.addNumber("Shadow displacement Right: " + fromCharCode(0x00B1), shadowDropPC, 0, 3, "% of mean font size"); Dialog.addNumber("Shadow Gaussian blur:", floor(0.75 * shadowDropPC), 0, 3, "% of mean font size"); Dialog.addNumber("Shadow darkness \(darkest = 100%\):", 50, 0, 3, "%, neg.= glow"); Dialog.show(); shadowDrop = Dialog.getNumber(); shadowDisp = Dialog.getNumber(); shadowBlur = Dialog.getNumber(); shadowDarkness = Dialog.getNumber(); } else { /* set the default values if no tweaking and lo-res monitor */ shadowDrop = shadowDropPC; shadowDisp = shadowDropPC; shadowBlur = 0.75 * shadowDropPC; shadowDarkness = 50; } } if (isNaN(getResult("mc_X\(px\)", 0)) && (ctrChoice == "Morphological Center")) { if (!is("binary")) { run("Duplicate...", "title=temp_binary_for_MCs"); run("8-bit"); AddMCsToResultsTable(); if (!diagnostics) closeImageByTitle("temp_binary_for_MCs"); } else AddMCsToResultsTable(); } selectWindow(workingImage); if (dpChoice == "Manual") decPlaces = getNumber("Choose Number of Decimal Places", decPlaces); else if (dpChoice == "Scientific") decPlaces = -1; else if (dpChoice != "Auto_Trimmed" && dpChoice != "Fixed_Auto") decPlaces = dpChoice; if (fontStyle == "unstyled") fontStyle = ""; if (stroke >= 0) { run("Flatten"); /* Flatten converts to RGB so . . . */ rename(tN + "_" + parameterLabel + "_labels"); if ((imageDepth == 8) && (lut == "Grays")) run("8-bit"); /* restores gray if all gray settings */ } else { run("Duplicate...", "title=labeled"); rename(tN + "_" + parameterLabel + "_labels"); } workingImage = getTitle(); if (!diagnostics || is("Batch Mode") == false) setBatchMode(true); if (outlierChoice != "No") { if (outlierChoice == "Manual input") { Dialog.create("Input Outlier Limits"); Dialog.addString("Outlier Limits \(n-n format\):", "Low-High", 16); Dialog.addMessage("Alternatively enter single integer for " + sigmaChar + ", i.e: 4" + sigmaChar + ", 5" + sigmaChar + " etc. \(up to 9\)", infoFontSize, instructionColor); Dialog.show(); outlierLimit = Dialog.getString(); outlierLimits = split(outlierLimit, "-"); if (lengthOf(outlierLimits) < 2) { sigmaR = parseInt(outlierLimit); outlierChoice = "" + sigmaR + sigmaChar; } else { outlierMin = parseInt(outlierLimits[0]); if (indexOf(outlierLimits, "-") == 0) outlierMin = 0 - outlierMin; /* split ignores first '-'. It is hope that the max is not negative too! */ outlierMax = parseInt(outlierLimits[1]); } } outlierStroke = maxOf(1, round(fontSize / 100 * outlierStrokePC)); run("Line Width...", "line=" + outlierStroke); for (i = 0, outlierCounterPos = 0, outlierCounterNeg = 0; i < iROIs.length; i++) { roiManager("select", iROIs[i]); if (outlierChoice == "Ramp range") { if (values[i] > rampMax) { setForegroundColorFromName(outlierColor); run("Draw", "slice"); outlierCounterPos++; } if (values[i] < rampMin) { setForegroundColorFromName(outlierColor2); run("Draw", "slice"); outlierCounterNeg++; } } else if (outlierChoice == "Manual input") { if (values[i] > outlierMax) { setForegroundColorFromName(outlierColor); run("Draw", "slice"); outlierCounterPos++; } if (values[i] < outlierMin) { setForegroundColorFromName(outlierColor2); run("Draw", "slice"); outlierCounterNeg++; } } else if (sigmaR > 0) { if (statsRampLines == "Ln") { if (values[i] > (expLnMeanPlusSDs[minOf(sIntervalsR - 1, sigmaR)])) { setForegroundColorFromName(outlierColor); run("Draw", "slice"); outlierCounterPos++; } if (values[i] < (expLnMeanMinusSDs[minOf(sIntervalsR - 1, sigmaR)])) { setForegroundColorFromName(outlierColor2); run("Draw", "slice"); outlierCounterNeg++; } } else if (values[i] < (meanMinusSDs[minOf(sIntervalsR - 1, sigmaR)]) || values[i] > (meanPlusSDs[minOf(sIntervalsR - 1, sigmaR)])) { if (values[i] > (meanPlusSDs[minOf(sIntervalsR - 1, sigmaR)])) { setForegroundColorFromName(outlierColor); run("Draw", "slice"); outlierCounterPos++; } if (values[i] < (meanPlusSDs[minOf(sIntervalsR - 1, sigmaR)])) { setForegroundColorFromName(outlierColor2); run("Draw", "slice"); outlierCounterNeg++; } } } else { outlierChoice = "No"; i = iROIs.length; } /* there seems to be a coding malfunction */ } run("Line Width...", "line=1"); /* Reset line width to ImageJ default */ outlierCounter = outlierCounterPos + outlierCounterNeg; } else outlierCounter = "No"; if (addLabels) { newImage("addLabelsTextImage", "8-bit black", imageWidth, imageHeight, 1); /* iterate through the ROI Manager list and draw scaled labels onto mask */ fontArray = newArray(); for (i = 0; i < iROIs.length; i++) { showStatus("Creating labels for " + iROIs.length + "ROIs"); showProgress(i, iROIs.length); roiManager("select", iROIs[i]); labelString = d2s(values[i], decPlaces); /* Reduce decimal places for labeling (move these two lines to below the labels you prefer) */ if (dpChoice == "Auto_Trimmed") labelString = removeTrailingZerosAndPeriod(labelString); Roi.getBounds(roiX, roiY, roiWidth, roiHeight); if (roiWidth >= roiHeight) roiMin = roiHeight; else roiMin = roiWidth; lFontS = fontSize; /* Initial estimate */ setFont(fontName, lFontS, fontStyle); lFontS = fontSCorrection * fontSize * roiMin / (getStringWidth(labelString)); if (lFontS > maxLFontS) lFontS = maxLFontS; if (lFontS < minLFontS) lFontS = minLFontS; if ((lFontS < 10) && acceptMinFontSize) lFontS = 10; setFont(fontName, lFontS, fontStyle); if (ctrChoice == "ROI Center") { textOffset = roiX + ((roiWidth) - getStringWidth(labelString)) / 2; textDrop = roiY + roiHeight / 2 + lFontS / 2; } else { textOffset = getResult("mc_X\(px\)", iROIs[i]) - getStringWidth(labelString) / 2; textDrop = getResult("mc_Y\(px\)", iROIs[i]) + lFontS / 2; } /* Now make sure label is not out of the canvas */ lFontFactor = lFontS / 100; textOffset = maxOf(lFontFactor * shadowDisp, textOffset); textOffset = minOf(imageWidth - getStringWidth(labelString) - lFontFactor * shadowDisp, textOffset); textDrop = maxOf(0, textDrop); textDrop = minOf(imageHeight, textDrop); /* draw object label */ setColor(255, 255, 255); drawString(labelString, textOffset, textDrop); fontArray[i] = lFontS; } Array.getStatistics(fontArray, minFontSize, null, meanFontSize, null); negAdj = 0.5; /* negative offsets appear exaggerated at full displacement */ if (shadowDrop < 0) labelShadowDrop = round(shadowDrop * negAdj); else labelShadowDrop = shadowDrop; if (shadowDisp < 0) labelShadowDisp = round(shadowDisp * negAdj); else labelShadowDisp = shadowDisp; if (shadowBlur < 0) labelShadowBlur = round(shadowBlur * negAdj); else labelShadowBlur = shadowBlur; fontFactor = meanFontSize / 100; minFontFactor = minFontSize / 100; if (outlineStrokePC > 0) objectOutlineStroke = maxOf(1, round(fontFactor * outlineStrokePC)); else objectOutlineStroke = 0; if (shadowDrop > 0) objectLabelShadowDrop = maxOf(1 + objectOutlineStroke, floor(fontFactor * labelShadowDrop)); else objectLabelShadowDrop = 0; if (shadowDisp > 0) labelShadowDisp = maxOf(1 + objectOutlineStroke, floor(fontFactor * labelShadowDisp)); objectLabelShadowDisp = 0; if (shadowBlur > 0) objectLabelShadowBlur = maxOf(objectOutlineStroke, floor(fontFactor * labelShadowBlur)); else objectLabelShadowBlur = 0; run("Select None"); roiManager("show none"); if (minFontSize < 12) innerTextEffect = "none"; fancyTextOverImage3(workingImage, "addLabelsTextImage", objectFontColor, outlineColor, objectLabelShadowDrop, objectLabelShadowDisp, objectLabelShadowBlur, shadowDarkness, objectOutlineStroke, innerTextEffect); /* requires "textImage" and original workingImage */ if (!diagnostics) closeImageByTitle("addLabelsTextImage"); if (stroke >= 0) workingImage = getTitle(); } /* End of optional parameter label section */ titleAbbrev = substring(workingImage, 0, minOf(15, lengthOf(workingImage))) + "..."; /* Start of Optional Summary section */ if (summaryAdd) { /* Reduce decimal places - but not as much as ramp labels */ summaryDP = parseInt(decPlaces) + 1; outlierChoiceAbbrev = cleanLabel(outlierChoice); if (outlierColor2 == outlierColor) { if (outlierChoice == "Manual input") outlierChoiceAbbrev = "<" + outlierMin + " >" + outlierMax + " " + unitLabel; else if (outlierChoice == "Ramp range") outlierChoiceAbbrev = "<" + rampMin + " >" + rampMax + " " + unitLabel; else outlierChoiceAbbrev = "<" + outlierChoiceAbbrev + ">"; } else { if (outlierChoice == "Manual input") outlierChoiceAbbrevPos = "> " + outlierMax + " " + unitLabel; else if (outlierChoice == "Ramp range") outlierChoiceAbbrevPos = "> " + rampMax + " " + unitLabel; else outlierChoiceAbbrevPos = "> + " + outlierChoiceAbbrev; if (outlierChoice == "Manual input") outlierChoiceAbbrevNeg = "< " + outlierMin + " " + unitLabel; else if (outlierChoice == "Ramp range") outlierChoiceAbbrevNeg = "< " + rampMin + " " + unitLabel; else outlierChoiceAbbrevNeg = "< -" + outlierChoiceAbbrev; } arraySum = d2s(arrayMean * items, summaryDP); arrayMean = d2s(arrayMean, summaryDP); coeffVar = d2s((100 / arrayMean) * arraySD, summaryDP); arraySD = d2s(arraySD, summaryDP); arrayMin = d2s(arrayMin, summaryDP); arrayMax = d2s(arrayMax, summaryDP); if (iROIs.length > 2) median = d2s(arrayQuartile[1], summaryDP); else median = NaN; if (IQR != 0) mode = d2s(mode, summaryDP); /* Then Statistics Summary Options Dialog . . . */ Dialog.create("Statistics Summary Options"); Dialog.addMessage("Summary output selected: " + summaryChoice, infoFontSize, infoColor); Dialog.addNumber("Change decimal places from " + summaryDP + ": ", summaryDP, 0, 2, ""); defValLines = 6; statsChoice1 = newArray("Skip", "No More Stats", "Dashed Line: ---", "Number of objects: " + items); if (outlierChoice != "No") { if (outlierColor2 == outlierColor) statsChoice2 = newArray("Outlines: " + outlierCounter + " objects " + outlierChoiceAbbrev + " in " + outlierColor); else statsChoice2 = newArray("Outlines > : " + outlierCounterPos + " objects " + outlierChoiceAbbrevPos + " in " + outlierColor, "Outlines < : " + outlierCounterNeg + " objects " + outlierChoiceAbbrevNeg + " in " + outlierColor2); } statsChoice3 = newArray( "Mean: " + arrayMean + " " + unitLabel, "Median: " + median + " " + unitLabel, "StdDev: " + arraySD + " " + unitLabel, "CoeffVar: " + coeffVar + "%"); statsChoice3a = newArray("Sum: " + arraySum + " " + unitLabel); statsChoice3b = newArray("Min-Max: " + arrayMin + " - " + arrayMax + " " + unitLabel); statsChoice3c = newArray("Minimum: " + arrayMin + " " + unitLabel, "Maximum: " + arrayMax + " " + unitLabel); if (indexOf(parameter, "Area") >= 0) { statsChoice3 = Array.concat(statsChoice3, statsChoice3a, statsChoice3b); defValLines++; } else statsChoice3 = Array.concat(statsChoice3, statsChoice3b, statsChoice3a); statsChoice4 = newArray( /* additional frequency distribution stats */ "Mode: " + mode + " " + unitLabel + " \(W = " + autoDistW + "\)", "InterQuartile Range: " + IQR + " " + unitLabel); statsChoice5 = newArray(); /* log stats */ eLMPS = expLnMeanPlusSDs.length; eLMMS = expLnMeanMinusSDs.length; if (eLMPS > 0) statsChoice5 = Array.concat(statsChoice5, "ln Stats Mean: " + d2s(expLnMeanPlusSDs[0], summaryDP) + " " + unitLabel); if (eLMPS > 1) statsChoice5 = Array.concat(statsChoice5, "ln Stats + SD: " + d2s((expLnMeanPlusSDs[1] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel); if (eLMPS > 2) statsChoice5 = Array.concat(statsChoice5, "ln Stats + 2SD: " + d2s((expLnMeanPlusSDs[2] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel); if (eLMPS > 3) statsChoice5 = Array.concat(statsChoice5, "ln Stats + 3SD: " + d2s((expLnMeanPlusSDs[3] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel); if (eLMMS > 1) statsChoice5 = Array.concat(statsChoice5, "ln Stats -SD: " + d2s((expLnMeanMinusSDs[1] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel); if (eLMMS > 2) statsChoice5 = Array.concat(statsChoice5, "ln Stats -2SD: " + d2s((expLnMeanMinusSDs[2] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel); if (eLMMS > 3) statsChoice5 = Array.concat(statsChoice5, "ln Stats -3SD: " + d2s((expLnMeanMinusSDs[3] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel); statsChoice6 = newArray("Pixel Size: " + lcf + " " + unit, "Image Title: " + titleAbbrev, "Manual", "Long Underline: ___", "Blank line"); if ((IQR != 0) && freqDistRamp) statsChoice3 = Array.concat(statsChoice3, statsChoice4, statsChoice3c); if (outlierChoice != "No") statsChoice = Array.concat(statsChoice1, statsChoice2, statsChoice3, statsChoice5, statsChoice6, statsChoice3c); else statsChoice = Array.concat(statsChoice1, statsChoice3, statsChoice5, statsChoice6, statsChoice3c); for (i = 0; i < statsChoiceLines; i++) { if (i < 10) Dialog.addChoice("Statistics label line " + (i + 1) + ":", statsChoice, statsChoice[i + 2]); else Dialog.addChoice("Statistics label line " + (i + 1) + ":", statsChoice, statsChoice[0]); } if (menuLimit > 752) textChoiceLines = 3; else textChoiceLines = 1; userInput = newArray(textChoiceLines); for (i = 0; i < textChoiceLines; i++) Dialog.addString("Manual: Line selected above: " + (i + 1) + ":", "None", 30); Dialog.show(); newSummaryDP = Dialog.getNumber; statsLabLine = newArray(statsChoiceLines); for (i = 0; i < statsChoiceLines; i++) statsLabLine[i] = Dialog.getChoice(); textInputLines = newArray(textChoiceLines); for (i = 0; i < textChoiceLines; i++) textInputLines[i] = Dialog.getString(); if (newSummaryDP != summaryDP) { summaryDP = newSummaryDP; arraySum = d2s(arrayMean * items, summaryDP); arrayMean = d2s(arrayMean, summaryDP); coeffVar = d2s((100 / arrayMean) * arraySD, summaryDP); arraySD = d2s(arraySD, summaryDP); arrayMin = d2s(arrayMin, summaryDP); arrayMax = d2s(arrayMax, summaryDP); median = d2s(arrayQuartile[1], summaryDP); if (IQR != 0) mode = d2s(mode, summaryDP); } } finalID = getImageID(); sTextS = true; sTextC = "white"; sTextCInv = "black"; if (summaryToImage || paraLabAdd) { if (bgI >= 0) { if (imageDepth == 8 || imageDepth == 24) { if (bgI < 5) sTextC = "white"; else if (bgI > 250) { sTextC = "black"; sTextCInv = "white"; } else sText = false; } if (imageDepth == 16) { if (bgI < (1285)) sTextC = "white"; else if (bgI > 64250) { sTextC = "black"; sTextCInv = "white"; } else sText = false; } } Dialog.create("Parameter Label and Summary Formatting Options: " + macroV); if (paraLabAdd) { Dialog.addString("Parameter Label or Title:", paraLabelExp, 3 + minOf(32, lengthOf(paraLabelExp))); Dialog.addNumber("Parameter Label font size:", paraLabFontSize, 1, 4, ""); } if (summaryToImage) Dialog.addNumber("Statistics text font size:", statsLabFontSize, 1, 4, ""); if (sTextS) { Dialog.addChoice("Summary and parameter font color:", colorChoices, sTextC); Dialog.addChoice("Summary and parameter outline color:", colorChoices, sTextCInv); Dialog.setInsets(2, 20, -10); Dialog.addMessage("Guessed background is " + bgI + ", suggesting simple " + sTextC + " summary text:", infoFontSize, instructionColor); Dialog.setInsets(8, 20, 10); Dialog.addCheckbox("Override formatting with simple " + sTextC + " text, no outline or shadow", false); } else { iFontColorS = indexOfArray(colorChoices, call("ij.Prefs.qet", ascPrefsKey + "summaryFontColor", colorChoices[0]), 0); Dialog.addChoice("Summary and parameter font color:", colorChoices, colorChoices[iFontColorS]); iOutlineColorS = indexOfArray(colorChoices, call("ij.Prefs.qet", ascPrefsKey + "summaryTextOutlineColor", colorChoices[1]), 1); Dialog.addChoice("Summary and parameter outline color:", colorChoices, colorChoices[iOutlineColorS]); } if (menuLimit >= 796) { /* room to show full dialog */ Dialog.addNumber("Outline stroke:", round(outlineStrokePC), 0, 3, "% of summary font size"); Dialog.addNumber("Shadow drop: ±", shadowDropPC, 0, 3, "% of summary font size"); Dialog.addNumber("Shadow displacement Right: ±", shadowDropPC, 0, 3, "% of summary font size"); Dialog.addNumber("Shadow Gaussian blur:", floor(0.75 * shadowDropPC), 0, 3, "% of summary font size"); Dialog.addNumber("Shadow darkness \(darkest = 100\):", 50, 0, 3, "%, neg.= glow"); } else Dialog.addCheckbox("Tweak summary format?", false); fancyInnerEffectsChoices = newArray("None", "Recessed", "Raised"); fancyInnerEffectsChoice = call("ij.Prefs.get", ascPrefsKey + "summaryTextInnerEffects", "None"); Dialog.addRadioButtonGroup("Inner text effects \(font size " + geq + "12):__________", fancyInnerEffectsChoices, 1, fancyInnerEffectsChoices.length, fancyInnerEffectsChoice); Dialog.show(); if (paraLabAdd) { paraLabel = Dialog.getString(); paraLabFontSize = Dialog.getNumber(); } if (summaryToImage) statsLabFontSize = Dialog.getNumber(); fontColorS = Dialog.getChoice(); call("ij.Prefs.set", ascPrefsKey + "summaryFontColor", fontColorS); outlineColorS = Dialog.getChoice(); call("ij.Prefs.set", ascPrefsKey + "summaryTextOutlineColor", outlineColorS); if (sTextS && !Dialog.getCheckbox()) sTextS = false; if (menuLimit >= 796) { textLabelOutlineStrokePC = Dialog.getNumber(); textLabelShadowDrop = Dialog.getNumber(); textLabelShadowDisp = Dialog.getNumber(); textLabelShadowBlur = Dialog.getNumber(); textLabelShadowDarkness = Dialog.getNumber(); } else if (Dialog.getCheckbox) { Dialog.create("Statistics Summary Options Tweaks"); Dialog.addNumber("Outline stroke:", outlineStrokePC, 0, 3, "% of stats label font size"); Dialog.addNumber("Shadow drop: ±", shadowDropPC, 0, 3, "% of stats label font size"); Dialog.addNumber("Shadow displacement Right: ±", shadowDropPC, 0, 3, "% of stats label font size"); Dialog.addNumber("Shadow Gaussian blur:", floor(0.75 * shadowDropPC), 0, 3, "% of stats label font size"); Dialog.addNumber("Shadow darkness \(darkest = 100\):", 50, 0, 3, "%, neg.= glow"); Dialog.show(); textLabelOutlineStrokePC = Dialog.getNumber(); textLabelShadowDrop = Dialog.getNumber(); textLabelShadowDisp = Dialog.getNumber(); textLabelShadowBlur = Dialog.getNumber(); textLabelShadowDarkness = Dialog.getNumber(); } else { textLabelOutlineStrokePC = outlineStrokePC; textLabelShadowDrop = shadowDropPC; textLabelShadowDisp = shadowDropPC; textLabelShadowBlur = floor(0.75 * shadowDropPC); textLabelShadowDarkness = 50; } summaryTextEffect = Dialog.getRadioButton(); call("ij.Prefs.set", ascPrefsKey + "summaryTextInnerEffects", summaryTextEffect); fontFactor = statsLabFontSize / 100; if (paraLabFontSize < 12 || statsLabFontSize < 12) summaryTextEffect = "none"; if (sTextS) { fontColorS = sTextC; if (sTextC == "black") outlineColorS = "White"; else outlineColorS = "Black"; textLabelOutlineStroke = 0; textLabelOutlineStrokePC = 0; textLabelShadowDrop = 0; textLabelShadowDisp = 0; textLabelShadowBlur = 0; textLabelShadowDarkness = 0; paraOutlineStroke = 0; summaryTextEffect = "None"; } else { /* End optional parameter label dialog */ if (textLabelShadowDrop < 0) textLabelShadowDrop = round(textLabelShadowDrop * negAdj); if (textLabelShadowDisp < 0) textLabelShadowDisp = round(textLabelShadowDisp * negAdj); if (textLabelShadowBlur < 0) textLabelShadowBlur = round(textLabelShadowBlur * negAdj); /* convert font percentages to pixels */ textLabelOutlineStroke = round(fontFactor * textLabelOutlineStrokePC); textLabelShadowDrop = floor(fontFactor * textLabelShadowDrop); textLabelShadowDisp = floor(fontFactor * textLabelShadowDisp); textLabelShadowBlur = floor(fontFactor * textLabelShadowBlur); paraOutlineStroke = textLabelOutlineStroke * paraLabFontSize / minLFontS; } } /* End of on-image text drawing format options /* Count lines of summary label */ if (paraLabAdd) labLines = 1; else labLines = 0; if (summaryAdd) { statsLabLineText = newArray(statsChoiceLines); setFont(fontName, statsLabFontSize, fontStyle); if (lengthOf(t) > round(imageWidth / (1.5 * fontSize))) titleShort = substring(t, 0, round(imageWidth / (1.5 * fontSize))) + "..."; else titleShort = t; for (i = 0, j = 0, statsLines = 0, longestStringWidth = 0, userTextLine = 0; i < statsLabLineText.length; i++) { if (statsLabLine[i] != "None") { if (statsLabLine[i] == "No More Stats") i = statsLabLineText.length; else { statsLines = i + 1; if (indexOf(statsLabLine[i], ": ") > 0) statsLabLine[i] = substring(statsLabLine[i], 0, indexOf(statsLabLine[i], ": ")); if (startsWith(statsLabLine[i], "Dashed")) statsLabLineText[i] = "----------"; else if (statsLabLine[i] == "Number of objects") statsLabLineText[j] = "Objects = " + items; else if (statsLabLine[i] == "Outlines") statsLabLineText[j] = "Outlines: " + outlierCounter + " objects " + outlierChoiceAbbrev + " in " + replace(outlierColor, "_", " "); else if (statsLabLine[i] == "Outlines > ") statsLabLineText[j] = "Outlines > : " + outlierCounterPos + " objects " + outlierChoiceAbbrevPos + " in " + replace(outlierColor, "_", " "); else if (statsLabLine[i] == "Outlines < ") statsLabLineText[j] = "Outlines < : " + outlierCounterNeg + " objects " + outlierChoiceAbbrevNeg + " in " + replace(outlierColor2, "_", " "); else if (statsLabLine[i] == "Mean") statsLabLineText[j] = "Mean = " + arrayMean + " " + unitLabel; else if (statsLabLine[i] == "Median") statsLabLineText[j] = "Median = " + median + " " + unitLabel; else if (statsLabLine[i] == "StdDev") statsLabLineText[j] = "Std.Dev.: " + arraySD + " " + unitLabel; else if (statsLabLine[i] == "CoeffVar") statsLabLineText[j] = "Coeff.Var.: " + coeffVar + "%"; else if (statsLabLine[i] == "Min-Max") statsLabLineText[j] = "Range: " + arrayMin + " - " + arrayMax + " " + unitLabel; else if (statsLabLine[i] == "Minimum") statsLabLineText[j] = "Minimum: " + arrayMin + " " + unitLabel; else if (statsLabLine[i] == "Maximum") statsLabLineText[j] = "Maximum: " + arrayMax + " " + unitLabel; else if (statsLabLine[i] == "Sum") statsLabLineText[j] = "Sum: " + arraySum + " " + unitLabel; else if (statsLabLine[i] == "Mode") statsLabLineText[j] = "Mode = " + mode + " " + unitLabel + " \(W = " + autoDistW + "\)"; else if (statsLabLine[i] == "InterQuartile Range") statsLabLineText[j] = "InterQuartile Range = " + IQR + " " + unitLabel; else if (statsLabLine[i] == "ln Stats Mean") statsLabLineText[j] = "ln Stats Mean: " + d2s(expLnMeanPlusSDs[0], summaryDP) + " " + unitLabel; else if (statsLabLine[i] == "ln Stats + SD") statsLabLineText[j] = "ln Stats + SD: " + d2s((expLnMeanPlusSDs[1] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel; else if (statsLabLine[i] == "ln Stats + 2SD") statsLabLineText[j] = "ln Stats + 2SD: " + d2s((expLnMeanPlusSDs[2] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel; else if (statsLabLine[i] == "ln Stats + 3SD") statsLabLineText[j] = "ln Stats + 3SD: " + d2s((expLnMeanPlusSDs[3] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel; else if (statsLabLine[i] == "ln Stats -SD") statsLabLineText[j] = "ln Stats -SD: " + d2s((expLnMeanMinusSDs[1] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel; else if (statsLabLine[i] == "ln Stats + 2SD") statsLabLineText[j] = "ln Stats -2SD: " + d2s((expLnMeanMinusSDs[2] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel; else if (statsLabLine[i] == "ln Stats + 3SD") statsLabLineText[j] = "ln Stats -3SD: " + d2s((expLnMeanMinusSDs[3] - expLnMeanPlusSDs[0]), summaryDP) + " " + unitLabel; else if (statsLabLine[i] == "Pixel Size") statsLabLineText[j] = "Scale: 1 pixel = " + lcf + " " + unit; else if (statsLabLine[i] == "Image Title") statsLabLineText[j] = "Image: " + titleShort; else if (statsLabLine[i] == "Manual") { if (textInputLines[userTextLine] != "None") statsLabLineText[j] = textInputLines[userTextLine]; else statsLabLineText[j] = ""; userTextLine += 1; } else if (statsLabLine[i] == "Long Underline") statsLabLineText[j] = "__________"; else if (statsLabLine[i] == "Blank Line") statsLabLineText[j] = " "; if (statsLabLine[i] != "Skip") { if (getStringWidth(statsLabLineText[j]) > longestStringWidth) longestStringWidth = getStringWidth(statsLabLineText[j]); j++; } } } } linesSpace = 1.2 * ((labLines * paraLabFontSize) + (statsLines * statsLabFontSize)); } if (paraLabAdd && !summaryAdd) longestStringWidth = getStringWidth(paraLabel); if (paraLabAdd || summaryToImage) { setFont(fontName, paraLabFontSize, fontStyle); just = "left"; /* set default justification */ if (getStringWidth(paraLabel) > longestStringWidth) longestStringWidth = getStringWidth(paraLabel); if (paraLabPos == "Top Left") { posStartX = offsetX; posStartY = offsetY; } else if (paraLabPos == "Top Right") { posStartX = imageWidth - longestStringWidth - offsetX; posStartY = offsetY; just = "right"; } else if (paraLabPos == "Center") { posStartX = round((imageWidth - longestStringWidth) / 2); posStartY = round((imageHeight - linesSpace) / 2); just = "center"; } else if (paraLabPos == "Bottom Left") { posStartX = offsetX; posStartY = imageHeight - offsetY - linesSpace; just = "left"; } else if (paraLabPos == "Bottom Right") { posStartX = imageWidth - longestStringWidth - offsetX; posStartY = imageHeight - offsetY - linesSpace; just = "right"; } else if (paraLabPos == "At New Selection") { batchOn = is("Batch Mode"); if (batchOn) setBatchMode("exit & display"); /* need to see what you are selecting */ setTool("rectangle"); msgtitle = "Location for the summary labels..."; msg = "Draw a box in the image where you want to center the summary labels..."; waitForUser(msgtitle, msg); getSelectionBounds(newSelPosStartX, newSelPosStartY, posWidth, posHeight); run("Select None"); posStartX = newSelPosStartX; posStartY = newSelPosStartY; if (!diagnostics || if (batchOn) setBatchMode(true); /* Return to original batch mode setting */ } else if (paraLabPos == "Current Selection") { posStartX = selPosStartX; posStartY = selPosStartY; posWidth = originalSelEWidth; posHeight = originalSelEHeight; if (selPosStartX < imageWidth * 0.4) just = "left"; else if (selPosStartX > imageWidth * 0.6) just = "right"; else just = "center"; } if (endsWith(paraLabPos, "election")) { shrinkX = minOf(1, posWidth / longestStringWidth); shrinkY = minOf(1, posHeight / linesSpace); shrinkF = minOf(shrinkX, shrinkY); shrunkFont = shrinkF * paraLabFontSize; if (shrinkF < 1) { Dialog.create("Shrink Text"); Dialog.addCheckbox("Text will not fit inside selection; Reduce font size from " + paraLabFontSize + "?", true); Dialog.addNumber("Choose new font size; font size for fit =", round(shrunkFont)); Dialog.show; reduceFontSize = Dialog.getCheckbox(); shrunkFont = Dialog.getNumber(); shrinkF = shrunkFont / paraLabFontSize; } else reduceFontSize = false; if (reduceFontSize == true) { paraLabFontSize = shrunkFont; statsLabFontSize = shrinkF * statsLabFontSize; linesSpace = shrinkF * linesSpace; longestStringWidth = shrinkF * longestStringWidth; fontFactor = statsLabFontSize / 100; if (!sTextS) { if (paraOutlineStroke > 1) paraOutlineStroke = maxOf(1, round(fontFactor * paraOutlineStroke)); else outlineStroke = round(fontFactor * paraOutlineStroke); if (textLabelShadowDrop > 1) textLabelShadowDrop = maxOf(1, round(fontFactor * textLabelShadowDrop)); else textLabelShadowDrop = round(fontFactor * textLabelShadowDrop); if (textLabelShadowDisp > 1) textLabelShadowDisp = maxOf(1, round(fontFactor * textLabelShadowDisp)); else textLabelShadowDisp = round(fontFactor * textLabelShadowDisp); if (textLabelShadowBlur > 1) textLabelShadowBlur = maxOf(1, round(fontFactor * textLabelShadowBlur)); else textLabelShadowBlur = round(fontFactor * textLabelShadowBlur); } } if (just == "auto") { if (posStartX < imageWidth * 0.4) just = "left"; else if (posStartX > imageWidth * 0.6) just = "right"; else just = "center"; } if (just == "left") posStartX = posStartX + paraLabFontSize / 2; else posStartX = posStartX + round((posWidth / 2) - longestStringWidth / 2); if (selPosStartY < imageHeight * 0.4) posStartY = posStartY + paraLabFontSize / 2; else posStartY = posStartY + round((posHeight / 2) - (linesSpace / 2) + fontSize); } run("Select None"); if (posStartY <= 1.5 * paraLabFontSize) posStartY += paraLabFontSize; if (posStartX < offsetX) posStartX = offsetX; endX = posStartX + longestStringWidth; if ((endX + offsetX) > imageWidth) posStartX = imageWidth - longestStringWidth - offsetX; paraLabelX = posStartX; paraLabelY = posStartY; setColor(255, 255, 255); } else { paraLabelY = 1.5 * fontSize; paraLabelX = 1.5 * fontSize; } if (summaryToImage || paraLabAdd) { /* Draw summary over top of object labels */ if (!diagnostics || is("Batch Mode") == false) setBatchMode(true); /* toggle batch mode back on */ addLabelsTextImages = newArray("paramSumLabelTextImage", "paramSumLabelAntialias"); /* Create Label Mask */ newImage("paramSumLabelTextImage", "8-bit black", imageWidth, imageHeight, 1); roiManager("deselect"); run("Select None"); if (paraLabFontSize >= 0) { setFont(fontName, paraLabFontSize, fontStyle); newImage("paramSumLabelAntialias", imageDepth, imageWidth, imageHeight, 1); /* Draw text for mask and antiAliased tweak */ /* determine font color intensities settings for antialiased tweak */ fontColorArray = getColorArrayFromColorName(fontColorS); Array.getStatistics(fontColorArray, fontIntMean); fontInt = floor(fontIntMean); outlineColorArray = getColorArrayFromColorName(outlineColorS); Array.getStatistics(outlineColorArray, outlineIntMean); outlineInt = floor(outlineIntMean); paraLabelY1 = paraLabelY; for (tImage = 0; tImage < 2; tImage++) { selectWindow(addLabelsTextImages[tImage]); if (tImage == 0) setColor("white"); else { paraLabelY = paraLabelY1; run("Select All"); setColorFromColorName(outlineColorS); fill(); roiManager("deselect"); run("Select None"); setColorFromColorName(fontColorS); } if (paraLabAdd) { setFont(fontName, paraLabFontSize, fontStyle); if (just == "left") drawString(paraLabel, paraLabelX, paraLabelY); else if (just == "right") drawString(paraLabel, paraLabelX + (longestStringWidth - getStringWidth(paraLabel)), paraLabelY); else drawString(paraLabel, paraLabelX + (longestStringWidth - getStringWidth(paraLabel)) / 2, paraLabelY); paraLabelY += round(1.2 * paraLabFontSize); } if (summaryToImage) { setFont(fontName, statsLabFontSize, fontStyle); for (iS = 0; iS < statsLines; iS++) { if (statsLabLineText[iS] != "0" && statsLabLineText[iS] != "") { if (just == "left") drawString(statsLabLineText[iS], paraLabelX, paraLabelY); else if (just == "right") drawString(statsLabLineText[iS], paraLabelX + (longestStringWidth - getStringWidth(statsLabLineText[iS])), paraLabelY); else drawString(statsLabLineText[iS], paraLabelX + (longestStringWidth - getStringWidth(statsLabLineText[iS])) / 2, paraLabelY); paraLabelY += round(1.2 * statsLabFontSize); } } } } fancyTextOverImage3(workingImage, "paramSumLabelTextImage", fontColorS, outlineColorS, textLabelShadowDrop, textLabelShadowDisp, textLabelShadowBlur, textLabelShadowDarkness, textLabelOutlineStroke, summaryTextEffect); /* requires "textImage" and original "workingImage" */ /* function fancyTextOverImage@ requires shadowDrop, shadowDisp, shadowBlur, shadowDarkness, outlineStroke, text effect Requires: functions: createShadowDropFromMask7Safe */ if (isOpen("paramSumLabelAntialias")) { imageCalculator("Max", "paramSumLabelTextImage", "paramSumLabelAntialias"); imageCalculator("Min", workingImage, "paramSumLabelTextImage"); } if (!diagnostics) closeImageByTitle("paramSumLabelTextImage"); if (!diagnostics) closeImageByTitle("label_mask"); if (!diagnostics) closeImageByTitle("paramSumLabelAntialias"); } } if (summaryToLog || summaryToFile) { summaryText = "" + paraLabel + " summary for " + t; if (subset) summaryText += " Restricted to ROIs:\n" + subsetROIs; for (iS = 0; iS < statsLines; iS++) if (statsLabLineText[iS] != "0" && statsLabLineText[iS] != "") summaryText += "\n" + statsLabLineText[iS]; if (summaryToLog) IJ.log(summaryText); if (summaryToFile) { timeStamp = getDateTimeCode(); timeStamp = substring(timeStamp, 0, lastIndexOf(timeStamp, "m")); if (!subset) summaryTextPath = tPath + tNL + parameter + "_summary_" + timeStamp + ".txt"; else summaryTextPath = tPath + tNL + parameter + "_" + items + "objects-summary_" + timeStamp + ".txt"; File.saveString(summaryText, summaryTextPath); } } finalID = getImageID(); /* End of Optional Summary section */ if (stroke >= 0) { run("Colors...", "foreground=black background=white selection=yellow"); /* reset colors */ selectWindow(workingImage); if (countNaN != 0) IJ.log("\n>>>> ROI Color Coder:\n" + "Some values from the '" + parameter + "' column could not be retrieved.\n" + countNaN + " ROI(s) were labeled with a default color."); tNC = getTitle(); /* Image and Ramp combination dialog */ roiManager("Deselect"); run("Select None"); Dialog.create("Combine labeled image and color-code legend?"); comboChoice = newArray("No", "Image + color-code legend", "Auto-cropped image + color-code legend", "Manually cropped image + color-code legend"); Dialog.addRadioButtonGroup("Combine labeled image with color-code legend?", comboChoice, 5, 1, comboChoice[1]); Dialog.show(); createCombo = Dialog.getRadioButton; if (createCombo != "No") { if (indexOf(createCombo, "cropped") > 0) { if (!diagnostics || is("Batch Mode") == true) setBatchMode("exit & display"); /* toggle batch mode off */ selectWindow(tNC); run("Duplicate...", "title=" + tNC + "_crop"); cropID = getImageID; run("Select Bounding Box (guess background color)"); run("Enlarge...", "enlarge=" + round(imageHeight * 0.02) + " pixel"); /* Adds a 2% margin */ if (startsWith(createCombo, "Manual")) { if (subset) { makeRectangle(selPosStartX, selPosStartY, originalSelEWidth, originalSelEHeight); enlarge2pc = round(0.01 * (originalSelEWidth + originalSelEHeight)); run("Enlarge...", "enlarge=" + enlarge2pc + " pixel"); } else { usePreviousBounds = false; lastBounds = call("ij.Prefs.get", "asc.roi.manual.bounds", ""); if (lastBounds != "") { previousBounds = split(lastBounds, ", "); if (lengthOf(previousBounds) == 4) usePreviousBounds = getBoolean("Do you want to use the previously saved bounds for cropping?"); } if (usePreviousBounds) makeRectangle(parseInt(previousBounds[0]), parseInt(previousBounds[1]), parseInt(previousBounds[2]), parseInt(previousBounds[3])); else { getSelectionBounds(xA, yA, widthA, heightA); makeRectangle(maxOf(2, xA), maxOf(2, yA), minOf(imageWidth - 4, widthA), minOf(imageHeight - 4, heightA)); } } setTool("rectangle"); title = "Crop Location for Combined Image"; msg = "1. Select the area that you want to crop to. 2. Click on OK"; waitForUser(title, msg); } if (!diagnostics || is("Batch Mode") == false) setBatchMode(true); /* toggle batch mode back on */ selectImage(cropID); if (selectionType >= 0) run("Crop"); else IJ.log("Combination with cropped image desired by no crop made"); run("Select None"); if (!diagnostics) closeImageByTitle(tNC); rename(tNC); imageHeight = getHeight(); imageWidth = getWidth(); } if (canvasH > imageHeight) { rampScale = imageHeight / canvasH; selectWindow(tR); run("Scale...", "x=" + rampScale + " y=" + rampScale + " interpolation=Bicubic average create title=scaled_ramp"); if (!diagnostics) closeImageByTitle(tR); rename(tR); canvasH = getHeight(); /* update ramp height */ canvasW = getWidth(); /* update ramp height */ } rampMargin = maxOf(2, imageWidth / 500); rampSelW = canvasW + rampMargin; comboW = imageWidth + rampSelW; if (!diagnostics || is("Batch Mode") == true) setBatchMode("exit & display"); /* toggle batch mode off */ selectWindow(tNC); run("Canvas Size...", "width=" + comboW + " height=" + imageHeight + " position=Top-Left"); selectWindow(tR); wait(5); Image.copy; selectWindow(tNC); wait(5); Image.paste(imageWidth + maxOf(2, imageWidth / 500), round((imageHeight - canvasH) / 2)); rename(tNC + " + legend"); if ((imageDepth == 8 && lut == "Grays") || is("grayscale")) run("8-bit"); /* restores gray if all gray settings */ if (!diagnostics) closeImageByTitle(tR); } } if (subset) { timeStamp = getDateTimeCode(); timeStamp = substring(timeStamp, 0, lastIndexOf(timeStamp, "m")); roiPath = tPath + tNL + "_" + iROIs.length + "_" + timeStamp + "_" + "SelectedROIs.zip"; roiManager("select", iROIs); /* iROIs ? */ roiManager("save selected", roiPath); roiManager("Deselect"); roiListPath = tPath + tNL + "_" + iROIs.length + "_" + timeStamp + "_" + "SelectedROIs.csv"; File.saveString(subsetROIs, roiListPath); call("ij.Prefs.set", ascPrefsKey + "roiListPath", roiListPath); } finalID = getImageID(); if (selectionExists) { /* Restore original selection to original image */ if (selType == 0 || selType == 1) { selectImage(orID); roiManager("show none"); if (selType == 0) makeRectangle(selPosStartX, selPosStartY, originalSelEWidth, originalSelEHeight); else makeOval(selPosStartX, selPosStartY, originalSelEWidth, originalSelEHeight); selectImage(finalID); } } selectImage(finalID); roiManager("Show All"); roiManager("Show None"); run("Remove Overlay"); setBatchMode("exit & display"); restoreSettings; memFlush(200); showStatus(macroL + " macro finished", "flash green"); beep(); wait(300); beep(); wait(300); beep(); /* End of ROI Color Coder with Scaled Labels and Summary */ } macro "Add Multiple Lines of Fancy Text To Image" { /* This macro adds multiple lines of text to a copy of the image. Peter J. Lee Applied Superconductivity Center at National High Magnetic Field Laboratory. ANSI encoded for Windows. ... v220823 Gray indices refer to grayChoices only; v240709 Updated colors. v250124 Added bottom and top center options to locations. Added simple formatting options. Replaced inner-shadow option with raised/embossed. v250127 Added semi-Fancy option and transparent overlay shadows. Restored missing "Center of Selection" option to text locations. Updated functions to match Fancy Scalebar macro. v250530 Corrects fancy.textlabel to fancy.textLabels. */ macroL = "Fancy_Text_Labels_v250530.ijm"; requires("1.47r"); originalImage = getTitle(); if (matches(originalImage, ".*Ramp.*") == 1) showMessageWithCancel("Title contains \"Ramp\"", "Do you want to label" + originalImage + " ?"); saveSettings; /* Set options for black objects on white background as this works better for publications */ run("Options...", "iterations=1 white count=1"); /* Set the background to white */ run("Colors...", "foreground=black background=white selection=yellow"); /* Set the preferred colors for these macros */ setOption("BlackBackground", false); run("Appearance...", " "); /* do not use Inverting LUT * /* Check to see if a Ramp legend rather than the image has been selected by accident */ getPixelSize(unit, pixelWidth, pixelHeight, pixelDepth); getDimensions(imageWidth, imageHeight, channels, slices, frames); selEType = selectionType; scaledLineAngle = 0; if (selEType >= 0) { selectionExists = true; if ((selEType >= 5) && (selEType <= 7)) { line = true; if (selEType > 5) { /* for 6=segmented line or 7=freehand line do a linear fit */ getSelectionCoordinates(xPoints, yPoints); Array.getStatistics(xPoints, orSelEX1, orSelEX2, orSelEX, null); Fit.doFit("Straight Line", xPoints, yPoints); orSelEY1 = Fit.f(orSelEX1); orSelEY2 = Fit.f(orSelEX2); } else getLine(orSelEX1, orSelEY1, orSelEX2, orSelEY2, selLineWidth); x1 = orSelEX1 * pixelWidth; y1 = orSelEY1 * pixelHeight; x2 = orSelEX2 * pixelWidth; y2 = orSelEY2 * pixelHeight; lineXPx = (orSelEX1 - orSelEX2); /* used for label offsets later */ lineYPx = (orSelEY1 - orSelEY2); /* used for label offsets later */ scaledLineAngle = (180 / PI) * Math.atan2(lineYPx, lineXPx); if (scaledLineAngle < -90) scaledLineAngle += 180; else if (scaledLineAngle > 90) scaledLineAngle -= 180; scaledLineLength = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); orSelEX = minOf(orSelEX1, orSelEX2); orSelEY = minOf(orSelEY1, orSelEY2); orSelEWidth = abs(orSelEX2 - orSelEX1); orSelEHeight = abs(orSelEY2 - orSelEY1); selLineLength = sqrt(pow(orSelEWidth, 2) + pow(orSelEHeight, 2)); } else { line = false; getSelectionBounds(orSelEX, orSelEY, orSelEWidth, orSelEHeight); } } else { selectionExists = false; line = false; } setBatchMode(true); startSliceNumber = getSliceNumber(); remSlices = slices - startSliceNumber; imageDims = imageHeight + imageWidth; imageDepth = bitDepth(); id = getImageID(); fontSize = round(imageDims / 75); /* default font size */ if (fontSize < 10) fontSize = 10; /* set minimum default font size as 10 */ lineSpacing = 1.1; outlineStroke = 7; /* default outline stroke: % of font size */ shadowDrop = 10; /* default outer shadow drop: % of font size */ shadowDisp = shadowDrop; shadowBlur = floor(0.6 * shadowDrop); shadowDarkness = 50; offsetX = round(8 + imageWidth / 150); /* default offset of label from edge */ offsetY = round(8 + imageHeight / 150); /* default offset of label from edge */ textRot = 0; textAboveLine = false; /* Then Basic Options Dialog . . . */ Dialog.create("Basic Label Options: " + macroL); if (Overlay.size == 0) overwriteChoice = newArray("Overwrite", "New image", "Add overlays"); else overwriteChoice = newArray("Overwrite", "New image", "Add overlays", "Replace All overlays"); iOver = indexOfArray(overwriteChoice, call("ij.Prefs.get", "fancy.textLabels.output", overwriteChoice[1]), 1); Dialog.addRadioButtonGroup("Output choices \(Overwrite & New: Destructive Overlays: Non-destructive\):", overwriteChoice, 1, 3, overwriteChoice[iOver]); if (selectionExists) { textLocChoices = newArray("Top Left", "Top Right", "Top Center", "Center", "Bottom Left", "Bottom Center", "Bottom Right", "Center of Selection", "Center of New Selection"); iLoc = indexOfArray(textLocChoices, "Center of Selection", 0); /* Overrides preferences as an active selection is assumed to be a priority */ } else { textLocChoices = newArray("Top Left", "Top Right", "Top Center", "Center", "Bottom Left", "Bottom Center", "Bottom Right", "Center of New Selection"); iLoc = indexOfArray(textLocChoices, call("ij.Prefs.get", "fancy.textLabels.location", textLocChoices[0]), 0); } Dialog.addChoice("Location of New Text:", textLocChoices, textLocChoices[iLoc]); Dialog.addNumber("Edge margin left/right:", offsetX, 0, 6, "pixels"); Dialog.addNumber("Edge margin top/bottom:", offsetY, 0, 6, "pixels"); if (selectionExists) { Dialog.addNumber("Original selection X start = ", orSelEX); Dialog.addNumber("Original selection Y start = ", orSelEY); Dialog.addNumber("Original selection width = ", orSelEWidth); Dialog.addNumber("Original selection height = ", orSelEHeight); if (selEType == 0 || selEType == 1 || selEType == 5 || selEType == 6 || selEType == 7) { Dialog.addCheckbox("Restore this selection at macro completion?", true); } else restoreSelection = false; if (orSelEX < imageWidth * 0.4) just = "left"; else if (orSelEX > imageWidth * 0.6) just = "right"; else just = "center"; if ((selEType >= 5) && (selEType <= 7)) { Dialog.addNumber("Text Rotation Angle = ", scaledLineAngle); Dialog.addMessage("Note: Rotated text may not be pretty."); Dialog.addCheckbox("1st line of text above line", true); } } else restoreSelection = false; textJustChoices = newArray("auto", "left", "center", "right"); if (selectionExists) Dialog.addChoice("Text justification \(\"auto\" will be \"" + just + "\"\)", textJustChoices, textJustChoices[0]); else Dialog.addChoice("Text justification", textJustChoices, textJustChoices[0]); Dialog.addNumber("Default font size:", fontSize); fontStyleChoice = newArray("bold", "bold antialiased", "italic", "italic antialiased", "bold italic", "bold italic antialiased", "unstyled"); iFS = indexOfArray(fontStyleChoice, call("ij.Prefs.get", "fancy.textLabels.font.style", fontStyleChoice[1]), 1); Dialog.addChoice("Font style:", fontStyleChoice, fontStyleChoice[iFS]); fontNameChoice = getFontChoiceList(); iFN = indexOfArray(fontNameChoice, call("ij.Prefs.get", "fancy.textLabels.font", fontNameChoice[0]), 0); Dialog.addChoice("Font name:", fontNameChoice, fontNameChoice[iFN]); fancyStyleEffectsOptions = newArray("No shadows", "No outline", "Raised", "Recessed"); fancyStyleEffectsDefaults = newArray(false, false, false, false); fancyStyleEffectsPrefs = call("ij.Prefs.get", "fancy.textLabels.fancyStyleEffects", "not found"); if (fancyStyleEffectsPrefs != "not found") { fancyStyleEffects = split(fancyStyleEffectsPrefs, "|"); if (fancyStyleEffects.length == fancyStyleEffectsOptions.length) fancyStyleEffectsDefaults = fancyStyleEffects; } Dialog.setInsets(10, 20, 0); Dialog.addCheckboxGroup(1, fancyStyleEffectsOptions.length, fancyStyleEffectsOptions, fancyStyleEffectsDefaults); fancyLevels = newArray("Not Fancy", "Semi-Fancy", "Full-Fancy"); howFancy = call("ij.Prefs.get", "fancy.textLabels.howFancy", fancyLevels[2]); Dialog.addRadioButtonGroup("How fancy?", fancyLevels, 1, 2, howFancy); grayChoices = newArray("white", "black", "off-white", "off-black", "lightGray", "gray", "darkGray"); colorChoicesStd = newArray("red", "green", "blue", "cyan", "magenta", "yellow", "pink", "orange", "violet"); colorChoicesMaterials = newArray("bronze", "antique_bronze", "brass", "dull_brass", "brick", "chrome", "copper", "aged_copper", "dusky_copper", "light_copper", "garnet", "burnished_gold", "gold", "slate_gray", "titanium", "vault_garnet", "plaza_brick", "vault_gold"); colorChoicesMod = newArray("aqua_modern", "blue_accent_modern", "blue_dark_modern", "blue_modern", "blue_honolulu", "gray_modern", "green_dark_modern", "green_modern", "green_modern_accent", "green_spring_accent", "orange_modern", "pink_modern", "purple_modern", "red_modern", "tan_modern", "violet_modern", "yellow_modern"); colorChoicesNeon = newArray("jazzberry_jam", "radical_red", "wild_watermelon", "outrageous_orange", "supernova_orange", "atomic_tangerine", "neon_carrot", "sunglow", "laser_lemon", "electric_lime", "screamin'_green", "magic_mint", "blizzard_blue", "dodger_blue", "shocking_pink", "razzle_dazzle_rose", "hot_magenta"); colorChoicesFSU = newArray("stadium_night", "westcott_water", "legacy_blue"); /* Materials colors moved to Materials set */ colorChoices = Array.concat(grayChoices, colorChoicesStd, colorChoicesMaterials, colorChoicesMod, colorChoicesNeon, colorChoicesFSU); iTC = indexOfArray(colorChoices, call("ij.Prefs.get", "fancy.textLabels.font.color", colorChoices[0]), 0); iBC = indexOfArray(colorChoices, call("ij.Prefs.get", "fancy.textLabels.outline.color", colorChoices[1]), 1); iTCg = indexOfArray(grayChoices, call("ij.Prefs.get", "fancy.textLabels.font.gray", grayChoices[0]), 0); iBCg = indexOfArray(grayChoices, call("ij.Prefs.get", "fancy.textLabels.outline.gray", grayChoices[1]), 1); if (imageDepth == 24) { Dialog.addChoice("Text color:", colorChoices, colorChoices[iTC]); Dialog.addChoice("Outline \(background\) color:", colorChoices, colorChoices[iBC]); } else { Dialog.addMessage("Gray options for destructive labels:________"); Dialog.addChoice("Destructive text gray choices:", grayChoices, grayChoices[iTCg]); Dialog.addChoice("Destructive text outline gray choices:", grayChoices, grayChoices[iBCg]); Dialog.addMessage("Color options for overlay labels:________"); Dialog.addChoice("Overlay text color choices:", colorChoices, colorChoices[iTC]); Dialog.addChoice("Overlay text outline color choices:", colorChoices, colorChoices[iBC]); } Dialog.addCheckbox("Tweak outline and shadow settings?", false); Dialog.show(); overWrite = Dialog.getRadioButton(); textLocChoice = Dialog.getChoice(); offsetX = Dialog.getNumber(); offsetY = Dialog.getNumber(); if (selectionExists) { selEX = Dialog.getNumber(); /* Allows user to tweak pre-selection using dialog boxes */ selEY = Dialog.getNumber(); selEWidth = Dialog.getNumber(); selEHeight = Dialog.getNumber(); restoreSelection = Dialog.getCheckbox(); if ((selEType >= 5) && (selEType <= 7)) { textRot = Dialog.getNumber(); textAboveLine = Dialog.getCheckbox; } } just = Dialog.getChoice(); fontSize = Dialog.getNumber(); /* fancy style effects checkbox group order: "No shadows", "Raised", "Recessed" */ fancyStyleEffectsString = ""; noShadow = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(noShadow, 0); noOutline = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(noOutline, 0); raised = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(raised, 0); recessed = Dialog.getCheckbox(); fancyStyleEffectsString += "|" + d2s(recessed, 0); howFancy = Dialog.getRadioButton(); call("ij.Prefs.set", "fancy.textLabels.howFancy", howFancy); notFancy = false; if (!noShadow || !noOutline || raised || recessed) notFancy = false; if (howFancy == "Not Fancy") notFancy = true; else if (howFancy == "Semi-Fancy") { shadowDrop /= 2; /* default outer shadow drop: % of font size */ shadowDisp /= 2; shadowBlur /= 2; shadowDarkness = 25; outlineStroke = 3.5; /* default outline stroke: % of font size */ } if (notFancy) { noShadow = true; noOutline = true; raised = false; recessed = false; fancyStyleEffectsString = "|1|1|0|0"; } call("ij.Prefs.set", "fancy.textLabels.fancyStyleEffects", fancyStyleEffectsString); /* End of checkbox group */ fontStyle = Dialog.getChoice(); fontName = Dialog.getChoice(); if (imageDepth == 24) { fontColor = Dialog.getChoice; outlineColor = Dialog.getChoice; } else { desFontGray = Dialog.getChoice(); desOutlineGray = Dialog.getChoice(); ovFontColor = Dialog.getChoice(); ovOutlineColor = Dialog.getChoice(); if (endsWith(overWrite, "overlays")) { fontColor = ovFontColor; outlineColor = ovOutlineColor; } else { fontColor = desFontGray; outlineColor = desOutlineGray; } } tweakFormat = Dialog.getCheckbox(); if (tweakFormat == "Yes" && !notFancy) { Dialog.create("Advanced Formatting Options"); Dialog.addNumber("Line Spacing", lineSpacing, 1, 3, "\(default 1\)"); Dialog.addNumber("Outline stroke:", outlineStroke, 0, 3, "% of font size"); Dialog.addNumber("Shadow Drop: ?", shadowDrop, 0, 3, "% of font size"); Dialog.addNumber("Shadow Displacement Right: ?", shadowDisp, 0, 3, "% of font size"); Dialog.addNumber("Shadow Gaussian blur:", shadowBlur, 0, 3, "% of font size"); Dialog.addNumber("Shadow Darkness:", shadowDarkness, 0, 3, "%\(darkest = 100%\)"); Dialog.show(); lineSpacing = Dialog.getNumber(); outlineStroke = Dialog.getNumber(); shadowDrop = Dialog.getNumber(); shadowDisp = Dialog.getNumber(); shadowBlur = Dialog.getNumber(); shadowDarkness = Dialog.getNumber(); } Dialog.create("Label Options: " + macroL); Dialog.addMessage("\"^2\" & \"um\" etc. replaced by " + fromCharCode(178) + " & " + fromCharCode(181) + "m etc. If the units are in the parameter label, within \(...\) i.e. \(unit\) they will override this selection.\n\"degreeC\" will be replaced with " + fromCharCode(0x00B0) + "C, \"degree C\" will be replaced with " + fromCharCode(0x00B0) + " C, \"degrees\" by " + fromCharCode(0x00B0) + ",\n\"symbol-Greek letter\" by the Greek letter, i.e. \"symbol-omega\" translates to " + fromCharCode(0x03C9) + ",\"symbol-Omega\" to " + fromCharCode(0x03A9) + "\nand \"symbol->=\" to " + fromCharCode(0x2265) + " etc. Arrows: \"arrow-up\" " + fromCharCode(0x21E7) + " \"arrow-left\" " + fromCharCode(0x21E6) + " etc."); textChoiceLines = 8; lengthOfDirectory = lengthOf(getInfo("image.directory")); if (lengthOfDirectory > 57) { directorySubstring = substring(getInfo("image.directory"), lengthOfDirectory - 57, lengthOfDirectory); directorySubstring = "..." + substring(directorySubstring, indexOf(directorySubstring, "\\")); } else directorySubstring = getInfo("image.directory"); imageFilename = getInfo("image.filename"); if (lastIndexOf(imageFilename, ".") > 0) imageFilenameWOExtension = substring(imageFilename, 0, lastIndexOf(imageFilename, ".")); else imageFilenameWOExtension = imageFilename; metaDataChoices1 = newArray("Enter text above or select here from this list", getTitle()); metaDataChoices2 = newArray(getMetadata("Info"), directorySubstring, imageFilename, imageFilenameWOExtension, "pixel width = " + pixelWidth + " " + unit, "Image width = " + imageWidth + " pixels", getInfo("image.subtitle")); if (line) { lineData = newArray("" + scaledLineLength + " " + unit + ", " + scaledLineAngle + fromCharCode(0x00B0), "" + scaledLineAngle + fromCharCode(0x00B0), "" + scaledLineLength + " " + unit); metaDataChoices = Array.concat(metaDataChoices1, lineData, metaDataChoices2); } else metaDataChoices = Array.concat(metaDataChoices1, metaDataChoices2); for (i = 0; i < textChoiceLines; i++) { tIPrefsName = "fancy.textLabels.texLabel." + i; textLabel = call("ij.Prefs.get", tIPrefsName, "-blank-"); Dialog.addString("Label line " + (i + 1) + ":", textLabel, 35); Dialog.addChoice("", metaDataChoices, metaDataChoices[0]); } Dialog.show(); textInputLines = newArray(textChoiceLines); for (i = 0; i < textChoiceLines; i++) { textInputLines[i] = Dialog.getString(); tIPrefsName = "fancy.textLabels.texLabel." + i; call("ij.Prefs.set", tIPrefsName, textInputLines[i]); metaChoice = Dialog.getChoice(); if (metaChoice != "Enter text above or select here from this list") textInputLines[i] = metaChoice; textInputLines[i] = "" + cleanLabel(textInputLines[i]); /* Use degree symbol */ } if (startsWith(overWrite, "Replace")) while (Overlay.size != 0) Overlay.remove; if ((remSlices > 0) && !endsWith(overWrite, "overlays")) labelRest = getBoolean("Add the same labels to this and next " + remSlices + " slices?"); else labelRest = false; if (is("Batch Mode") == false) setBatchMode(true); /* toggle batch mode back on */ /* save last used settings in user in preferences */ call("ij.Prefs.set", "fancy.textLabels.font.style", fontStyle); call("ij.Prefs.set", "fancy.textLabels.font", fontName); call("ij.Prefs.set", "fancy.textLabels.location", textLocChoice); call("ij.Prefs.set", "fancy.textLabels.output", overWrite); if (imageDepth == 24) { call("ij.Prefs.set", "fancy.textLabels.font.color", fontColor); call("ij.Prefs.set", "fancy.textLabels.outline.color", outlineColor); } else { call("ij.Prefs.set", "fancy.textLabels.font.gray", fontColor); call("ij.Prefs.set", "fancy.textLabels.outline.gray", outlineColor); if (endsWith(overWrite, "overlays")) { fontColor = ovFontColor; outlineColor = ovOutlineColor; call("ij.Prefs.set", "fancy.textLabels.font.color", fontColor); call("ij.Prefs.set", "fancy.textLabels.outline.color", outlineColor); } } textOutNumber = 0; textInputLinesText = newArray(textChoiceLines); setFont(fontName, fontSize, fontStyle); longestStringWidth = 0; for (i = 0; i < textChoiceLines; i++) { if (textInputLines[i] != "-blank-") { textInputLinesText[i] = "" + cleanLabel(textInputLines[i]); textOutNumber = i + 1; /* This allows you to have blank lines between lines but not at the end */ if (getStringWidth(textInputLinesText[i]) > longestStringWidth) longestStringWidth = getStringWidth(textInputLines[i]); } } if (textOutNumber == 0) restoreExit("No text for labels"); /* Make sure all text fits image width */ shrinkX = imageWidth / longestStringWidth; fontSize = fontSize * minOf(1, shrinkX); longestStringWidth = longestStringWidth * minOf(1, shrinkX); /* determine font color intensities settings for antialiased tweak */ fontColorArray = getColorArrayFromColorName(fontColor); Array.getStatistics(fontColorArray, fontIntMean); fontInt = floor(fontIntMean); fontFactor = fontSize / 100; if (!noOutline) { outlineColorArray = getColorArrayFromColorName(outlineColor); Array.getStatistics(outlineColorArray, outlineIntMean); outlineInt = floor(outlineIntMean); outlineStroke = round(fontFactor * outlineStroke); } if (!noShadow) { negAdj = 0.5; /* negative offsets appear exaggerated at full displacement */ if (shadowDrop < 0) shadowDrop *= negAdj; if (shadowDisp < 0) shadowDisp *= negAdj; if (shadowBlur < 0) shadowBlur *= negAdj; shadowDrop = round(fontFactor * shadowDrop); shadowDisp = round(fontFactor * shadowDisp); shadowBlur = round(fontFactor * shadowBlur); if (offsetX < (shadowDisp + shadowBlur + 1)) offsetX = (shadowDisp + shadowBlur + 1); /* make sure shadow does not run off edge of image */ if (offsetY < (shadowDrop + shadowBlur + 1)) offsetY = (shadowDrop + shadowBlur + 1); } if (fontStyle == "unstyled") fontStyle = ""; /* */ linesSpace = lineSpacing * textOutNumber * fontSize; if (textLocChoice == "Top Left") { selEX = offsetX; selEY = offsetY; if (just == "auto") just = "left"; } else if (textLocChoice == "Top Right") { selEX = imageWidth - longestStringWidth - offsetX; selEY = offsetY; if (just == "auto") just = "right"; } else if (textLocChoice == "Top Center") { selEX = round((imageWidth - longestStringWidth) / 2); selEY = offsetY; if (just == "auto") just = "center"; } else if (textLocChoice == "Center") { selEX = round((imageWidth - longestStringWidth) / 2); selEY = round((imageHeight - linesSpace) / 2 + fontSize); if (just == "auto") just = "center"; } else if (textLocChoice == "Bottom Left") { selEX = offsetX; selEY = imageHeight - (offsetY + linesSpace) + fontSize; if (just == "auto") just = "left"; } else if (textLocChoice == "Bottom Right") { selEX = imageWidth - longestStringWidth - offsetX; selEY = imageHeight - (offsetY + linesSpace) + fontSize; if (just == "auto") just = "right"; } else if (textLocChoice == "Bottom Center") { selEX = round((imageWidth - longestStringWidth) / 2); selEY = imageHeight - (offsetY + linesSpace) + fontSize; if (just == "auto") just = "center"; } else if (textLocChoice == "Center of New Selection") { if (is("Batch Mode")) setBatchMode(false); /* Does not accept interaction while batch mode is on */ setTool("rectangle"); msgtitle = "Location for the text labels..."; msg = "Draw a box in the image where you want to center the text labels..."; waitForUser(msgtitle, msg); getSelectionBounds(orSelEX, orSelEY, orSelEWidth, orSelEHeight); /* this set for restore */ restoreSelection = getBoolean("Restore this selection at the end of the macro?"); if (is("Batch Mode") == false) setBatchMode(true); /* toggle batch mode back on */ getSelectionBounds(selEX, selEY, selEWidth, selEHeight); /* this set to change */ } if (endsWith(textLocChoice, "election")) { if (line) shrinkF = selLineLength / longestStringWidth; // if (line) shrinkY = minOf(1,imageHeight/linesSpace); else { shrinkX = minOf(1, selEWidth / longestStringWidth); shrinkY = minOf(1, selEHeight / linesSpace); shrinkF = minOf(shrinkX, shrinkY); } shrunkFont = shrinkF * fontSize; if (shrinkF < 1) { Dialog.create("Shrink Text"); Dialog.addCheckbox("Text will not fit inside selection; Reduce font size from " + fontSize + "?", true); Dialog.addNumber("Choose new font size; font size for fit =", round(shrunkFont)); Dialog.show; reduceFontSize = Dialog.getCheckbox(); shrunkFont = Dialog.getNumber(); shrinkF = shrunkFont / fontSize; } else reduceFontSize = false; if (reduceFontSize) { fontSize = shrunkFont; linesSpace = shrinkF * linesSpace; longestStringWidth = shrinkF * longestStringWidth; fontFactor = fontSize / 100; if (!noOutline) { if (outlineStroke > 1) outlineStroke = maxOf(1, round(fontFactor * outlineStroke)); else outlineStroke = round(fontFactor * outlineStroke); } if (!noShadow) { if (shadowDrop > 1) shadowDrop = maxOf(1, round(fontFactor * shadowDrop)); else shadowDrop = round(fontFactor * shadowDrop); if (shadowDisp > 1) shadowDisp = maxOf(1, round(fontFactor * shadowDisp)); else shadowDisp = round(fontFactor * shadowDisp); if (shadowBlur > 1) shadowBlur = maxOf(1, round(fontFactor * shadowBlur)); else shadowBlur = round(fontFactor * shadowBlur); } } selEX = selEX + round((selEWidth / 2) - longestStringWidth / 2); selEY = selEY + round((selEHeight / 2) - (linesSpace / 2) + fontSize); if (just == "auto") { if (selEX < imageWidth * 0.4) just = "left"; else if (selEX > imageWidth * 0.6) just = "right"; else just = "center"; } } run("Select None"); if (selEY <= 1.5 * fontSize) selEY += fontSize; if (selEX < offsetX) selEX = offsetX; endX = selEX + longestStringWidth; if ((endX + offsetX) > imageWidth) selEX = imageWidth - longestStringWidth - offsetX; textLabelX = selEX; textLabelY = selEY; /* Now offset from line for line label */ if ((selEType >= 5) && (selEType < 7) && textAboveLine) { textLabelX += round(cos(textRot / (180 / PI)) * 0.75 * fontSize); textLabelX += round(sin(textRot / (180 / PI)) * 0.75 * fontSize); } setColorFromColorName("white"); roiManager("show none"); if (startsWith(overWrite, "New")) { if (slices == 1) run("Duplicate...", "title=" + getTitle() + "+text"); else run("Duplicate...", "title=" + getTitle() + "+text duplicate"); } workingImage = getTitle(); if (is("Batch Mode") == false) setBatchMode(true); /* toggle batch mode back on */ setFont(fontName, fontSize, fontStyle); textLabelLineY = textLabelY; /* Create Label Mask */ if (!notFancy) { newImage("label_mask", "8-bit black", imageWidth, imageHeight, 1); roiManager("deselect"); run("Select None"); setColor("white"); for (i = 0; i < textOutNumber; i++) { if (textInputLines[i] != "-blank-") { if (just == "left") drawString(textInputLinesText[i], textLabelX, textLabelLineY); else if (just == "right") drawString(textInputLinesText[i], textLabelX + (longestStringWidth - getStringWidth(textInputLinesText[i])), textLabelLineY); else drawString(textInputLinesText[i], textLabelX + (longestStringWidth - getStringWidth(textInputLinesText[i])) / 2, textLabelLineY); textLabelLineY += lineSpacing * fontSize; } else textLabelLineY += lineSpacing * fontSize; } textLabelLineY = textLabelY; selectWindow("label_mask"); setThreshold(0, 128); setOption("BlackBackground", false); run("Convert to Mask"); /* AKA Make Binary" */ if (endsWith(overWrite, "verlays")) { selectWindow(originalImage); fontColorHex = getHexColorFromColorName(fontColor); outlineColorHex = getHexColorFromColorName(outlineColor); run("Select None"); getSelectionFromMask("label_mask"); if (textRot != 0) run("Rotate...", " angle=&textRot"); getSelectionBounds(selMaskX, selMaskY, selMaskWidth, selMaskHeight); setSelectionLocation(selMaskX + shadowDisp, selMaskY + shadowDrop); dilation = outlineStroke + maxOf(1, round(shadowBlur / 2)); run("Enlarge...", "enlarge=&dilation pixel"); if (textChoiceLines < 2) run("Interpolate", "interval=&shadowBlur smooth"); /* Does not work for multiple lines */ List.setMeasurements; bgGray = List.getValue("Mean"); List.clear(); if (imageDepth == 16 || imageDepth == 32) bgGray = round(bgGray / 256); shadowHex = "#" + "" + String.pad(toHex(255 * (100 - shadowDarkness) / 100), 2) + "000000"; setSelectionName("Fancy Text Label Shadow"); Overlay.addSelection(shadowHex, outlineStroke, shadowHex); run("Select None"); getSelectionFromMask("label_mask"); if (textRot != 0) run("Rotate...", " angle=&textRot"); run("Enlarge...", "enlarge=&outlineStroke pixel"); setSelectionName("Fancy Text Label Outline"); Overlay.addSelection(outlineColorHex, outlineStroke, outlineColorHex); /* Note that when the image is viewed at anything other than 100% the overlays will not appear to be lined up correctly */ run("Select None"); getSelectionFromMask("label_mask"); if (textRot != 0) { run("Rotate...", "angle=&textRot"); setSelectionName("Fancy Rotated Text Labels"); } else setSelectionName("Fancy Text Labels"); Overlay.addSelection(outlineColorHex, outlineStroke, fontColorHex); Overlay.show; } else { if (textRot != 0) { newImage("rot_label_mask", "8-bit black", imageWidth, imageHeight, 1); setColorFromColorName("white"); getSelectionFromMask("label_mask"); run("Rotate...", "angle=&textRot"); fill(); run("Select None"); run("Convert to Mask"); /* AKA Make Binary" */ run("Invert"); active_Label_Mask = "rot_label_mask"; } else active_Label_Mask = "label_mask"; /* Create drop shadow if desired */ if (shadowDrop != 0 || shadowDisp != 0 || shadowBlur != 0) { showStatus("Creating drop shadow for labels . . . "); createShadowDropFromMask7Safe(active_Label_Mask, shadowDrop, shadowDisp, shadowBlur, shadowDarkness, outlineStroke); } for (s = 0; s < remSlices + 1; s++) { showProgress(-s / remSlices); if (isOpen("shadow") && shadowDarkness > 0) imageCalculator("Subtract", workingImage, "shadow"); else if (isOpen("shadow") && (shadowDarkness < 0)) imageCalculator("Add", workingImage, "shadow"); run("Select None"); /* Create outline around text */ selectWindow(workingImage); getSelectionFromMask(active_Label_Mask); // if (textRot!=0) run("Rotate...", "angle=&textRot"); getSelectionBounds(maskX, maskY, null, null); outlineStrokeOffset = minOf(round(shadowDisp / 2), round(maxOf(0, (outlineStroke / 2) - 1))); setSelectionLocation(maskX + outlineStrokeOffset, maskY + outlineStrokeOffset); /* Offset selection to create shadow effect */ run("Enlarge...", "enlarge=&outlineStroke pixel"); setBackgroundFromColorName(outlineColor); run("Clear", "slice"); outlineStrokeOffsetMod = outlineStrokeOffset / 2; run("Enlarge...", "enlarge=&outlineStrokeOffsetMod pixel"); run("Gaussian Blur...", "sigma=&outlineStrokeOffsetMod"); run("Select None"); /* Create text */ getSelectionFromMask(active_Label_Mask); setBackgroundFromColorName(fontColor); run("Clear", "slice"); run("Enlarge...", "enlarge=1 pixel"); run("Gaussian Blur...", "sigma=0.75"); run("Unsharp Mask...", "radius=1 mask=0.75"); if (raised || recessed) { outlineRGBs = getColorArrayFromColorName(outlineColor); Array.getStatistics(outlineRGBs, null, null, outlineColorMean, null); textRGBs = getColorArrayFromColorName(fontColor); Array.getStatistics(textRGBs, null, null, textColorMean, null); if (outlineColorMean > textColorMean) { if (raised && !recessed) { raised = false; recessed = true; } else if (!raised && recessed) { raised = true; recessed = false; } } fontLineWidth = getStringWidth("!"); rAlpha = fontLineWidth / 40; if (raised) { getSelectionFromMask("label_mask"); if (!noOutline) run("Enlarge...", "enlarge=1 pixel"); run("Convolve...", "text1=[ " + createConvolverMatrix("raised", fontLineWidth) + " ]"); if (rAlpha > 0.33) run("Gaussian Blur...", "sigma=&rAlpha"); run("Select None"); } if (recessed) { getSelectionFromMask("label_mask"); if (!noOutline && !raised) run("Enlarge...", "enlarge=1 pixel"); run("Enlarge...", "enlarge=1 pixel"); run("Convolve...", "text1=[ " + createConvolverMatrix("recessed", fontLineWidth) + " ]"); if (rAlpha > 0.33) run("Gaussian Blur...", "sigma=rAlpha"); run("Select None"); } } run("Select None"); if (labelRest == false) remSlices = 0; else run("Next Slice [>]"); } } closeImageByTitle("shadow"); closeImageByTitle("label_mask"); closeImageByTitle("rot_label_mask"); } else { selectWindow(workingImage); IJ.log(fontColor + " " + getHexColorFromColorName(fontColor)); setColor("" + getHexColorFromColorName(fontColor)); // setColor(#); for (s = 0; s < remSlices + 1; s++) { showProgress(-s / remSlices); textLabelLineY = textLabelY; for (i = 0; i < textOutNumber; i++) { if (textInputLines[i] != "-blank-") { if (endsWith(overWrite, "verlays")) { // setColor("" + getHexColorFromColorName(fontColor)); if (just == "left") Overlay.drawStringtextInputLinesText[i], textLabelX, textLabelLineY, textRot); else if (just == "right") Overlay.drawString(textInputLinesText[i], textLabelX + (longestStringWidth - getStringWidth(textInputLinesText[i])), textLabelLineY, textRot); else Overlay.drawString(textInputLinesText[i], textLabelX + (longestStringWidth - getStringWidth(textInputLinesText[i])) / 2, textLabelLineY, textRot); Overlay.show; } else { if (just == "left") drawString(textInputLinesText[i], textLabelX, textLabelLineY); else if (just == "right") drawString(textInputLinesText[i], textLabelX + (longestStringWidth - getStringWidth(textInputLinesText[i])), textLabelLineY); else drawString(textInputLinesText[i], textLabelX + (longestStringWidth - getStringWidth(textInputLinesText[i])) / 2, textLabelLineY); } textLabelLineY += lineSpacing * fontSize; } else textLabelLineY += lineSpacing * fontSize; } if (labelRest == false) remSlices = 0; else run("Next Slice [>]"); } } selectWindow(workingImage); if (startsWith(overWrite, "New")) { if ((lastIndexOf(originalImage, ".")) > 0) workingImageNameWOExt = unCleanLabel(substring(workingImage, 0, lastIndexOf(workingImage, "."))); else workingImageNameWOExt = unCleanLabel(workingImage); rename(workingImageNameWOExt + "+text"); } restoreSettings; run("Select None"); setBatchMode("exit & display"); if (endsWith(textLocChoice, "election") && restoreSelection) { if (selEType == 0) makeRectangle(orSelEX, orSelEY, orSelEWidth, orSelEHeight); if (selEType == 1) makeOval(orSelEX, orSelEY, orSelEWidth, orSelEHeight); if ((selEType >= 5) && (selEType < 7)) { makeLine(orSelEX1, orSelEY1, orSelEX2, orSelEY2); if (getBoolean("Create flattened image with line selection")) { run("Add Selection..."); /* By adding selection to overlay this also works with the overlay label */ run("Flatten"); } } } else run("Select None"); showStatus("Fancy Text Labels Finished"); call("java.lang.System.gc"); } /* !!!! Note the macro title in the startup includes the shortcut [F7] !!!! */ macro "Add Additional Geometries to Table [F7]" { lMacro = "Add_Unit-Scaled_Extended_Geometries_to_Results_v231013.ijm"; /* Better to use manual label in case macro is called from startup */ requires("1.52m"); /*Uses the new ROI.getFeretPoints released in 1.52m */ saveSettings(); fullFName = getInfo("image.filename"); selectResultsWindow(true); tableTitle = Table.title; nTable = Table.size; if (isOpen(tableTitle)) selectWindow(tableTitle); else if (isOpen("Results")) { selectWindow("Results"); tableTitle = Table.title; nTable = Table.size; } else exit("No Results table to work with"); Table.rename(tableTitle, tableTitle); /* Weird kludge for weird residual table name issue */ if (nTable == 0) exit("No Table to work with"); tableHeadings = Table.headings; tableColumns = split(tableHeadings); columnsL = lengthOf(tableColumns); infinitySym = fromCharCode(0x221E); infinityCount = 0; for (i = 0; i < columnsL; i++) { for (j = 0; j < nTable; j++) { if (Table.get(tableColumns[i], j) == "-Infinity") { Table.set(tableColumns[i], j, infinitySym); infinityCount++; } } } defaultAllHeadings = Table.allHeadings; defaultAllColumns = split(defaultAllHeadings); nDResults = defaultAllColumns.length; nHResults = tableColumns.length; nMissingResults = nDResults - nHResults; missingResults = ""; if (nMissingResults > 0) { for (i = 0; i < nDResults; i++) { dAC = defaultAllColumns[i]; if (dAC != "Ch" && dAC != "Frame" && dAC != "MinThr" && dAC != "MaxThr") { if (indexOfArray(tableColumns, defaultAllColumns[i], -1) < 0) missingResults += defaultAllColumns[i] + ", "; } } if (missingResults != "") showMessageWithCancel("The table does not have " + missingResults + ": Continue?"); } userPath = getInfo("user.dir"); prefsDelimiter = "|"; if (isOpen(tableTitle)) selectWindow(tableTitle); else { selectResultsWindow(true); tableTitle = Table.title; nTable = Table.size; IJ.log("Added additional geometric parameters to: " + tableTitle + " " + nTable); } /* Check table for embedded scale */ tableScale = false; if (Table.size > 0) { tablePW = Table.get("PixelWidth", 0); /* This value embedded in the table by some ASC macros */ tablePAR = Table.get("PixelAR", 0); /* This value embedded in the table by some ASC macros */ tableUnit = Table.getString("Unit", 0); /* This value embedded in the table by some ASC macros */ if (tablePW != NaN && tablePAR != NaN && tableUnit != "null" && tableUnit != NaN) { tableScale = true; pixelWidth = parseFloat(tablePW); /* Makes sure this is not a string in the imported table */ pixelAR = parseFloat(tablePAR); /* Makes sure this is not a string in the imported table */ pixelHeight = pixelWidth / pixelAR; unit = tableUnit; } } if (!tableScale) { if (nImages > 0) getPixelSize(unit, pixelWidth, pixelHeight); else { Dialog.create("No images open, please enter unit values"); Dialog.addRadioButtonGroup("Perhaps there should be an open image?", newArray("continue", "exit"), 2, 1, "continue"); IJ.log("No images were open, expanded Feret coordinates will not be added to table."); Dialog.addNumber("pixel width", 1, 10, 10, "units"); Dialog.addNumber("pixel height", 1, 10, 10, "units"); unitChoices = newArray("m", "cm", "mm", "µm", "microns", "nm", "Å", "pm", "inches"); Dialog.addChoice("units", unitChoices, "pixels"); Dialog.show; if (Dialog.getRadioButton == "exit") restoreExit; pixelWidth = Dialog.getNumber; pixelHeight = Dialog.getNumber; unit = Dialog.getChoice; } } pixelAR = pixelWidth / pixelHeight; lcf = (pixelWidth + pixelHeight) / 2; unitLabel = "\(" + unit + "\)"; supminus = "^-"; /* the character code fromCharCode(0x207B) does not seem to work as exported to Excel and when called into other macros */ supone = fromCharCode(0x00B9); /* UTF-16 (hex) C/C++/Java source code "\u00B9" */ suptwo = fromCharCode(0x00B2); /* UTF-16 (hex) C/C++/Java source code "\u00B2" */ supthree = fromCharCode(0x00B2); /* UTF-16 (hex) C/C++/Java source code "\u00B3" */ sqroot = fromCharCode(0x221A); /* UTF-16 (hex) */ degreeSym = fromCharCode(0x00B0); /* UTF-16 (hex) degree symbol */ html = "" + "Additional Aspect Ratios:
" + " "AR Bounding Rect": Aspect ratio from bounding rectangle.
" + " "AR Feret": Aspect ratio Feret diameters\( max\/min\).
" + "Additional Feret Diameter derived geometries:
" + " "Feret Coordinates": Coordinates for both min and max Feret diameters.
" + ""Compactness_Feret" (using Feret diameter as maximum diameter),
" + "0-90√ resolved angles: for "Angle" and "Feret Angle"
" + "Interfacial density (assuming each interface is shared by two objects - e.g. grain boundary density).
" + "CircToEllipse Tilt: Angle that a circle would have to be tilted to match measured ellipse.
" + "Pixel coordinates: Coordinates and bounding box values converted to pixels.
" + "Area equivalent diameter\(AKA Heywood diameter\):
" + "       The "diameter" of an object obtained from the area assuming a circular geometry.
" + "Perimeter equivalent diameter: Calculated from the perimeter assuming a circular geometry.
" + "Spherical equivalent diameter: Calculated from the volume of a sphere (Russ page 182)
" + "       but using the mean projected Feret diameters to calculate the volume.
" + "Fiber geometries:
" + "      Snake ribbon thickness estimates from repeating half-annulus (Lee & Jablonski LTSW'94).
" + "Fiber widths estimated obtained from the fiber length from [1] page 189.
" + "Fiber length from fiber width (Lee and Jablonski LTSW'94; modified from the formula in [2] Page 612.
" + "Fiber lengths from Russ formulae.
" + "Volumetric estimates from projections obtained from the formulae in [1] 189.
" + "Additional shape factors:
" + ""Convexity": using the calculated elliptical fit to obtain a convex perimeter, https://imagej.net/Shape_Filter
" + ""Elongation" = 1 - 1/Bounding Rectangle Aspect Ratio see https://imagej.net/Shape_Filter
" + ""Roundnesss_cAR" Circularity corrected by aspect ratio,
" + "       Takashimizu and Iiyoshi Progress in Earth and Planetary Science (2016) 3:2
" + "       https://doi.org/10.1186/s40645-015-0078-x
" + " "Thinnes ratio" inverse of circularity see https://imagej.net/Shape_Filter
" + " "Extent ratio" object area/bounding rectangle area
" + " "Curl" Fiber length/length of bounding box, i.e. how much the fiber is "curled-up"
" + "Square geometries relevant to diamond indent hardness measurement:
" + "Sqr_Diag_A = √\(2*Area\) for a square with vertical diagonal this length should match the bounding box height
" + "Squarity: for a perfect square these values should approach 1:
" + " Squarity_AP = 1-|1-\(16*Area\)/Perimeter²| \(perhaps too sensitive to perimeter roughness\)
" + " Squarity_AF = 1-|1-Feret/\(A*√2\)|
" + " Squarity_Ff = 1-|1-√2/Feret_AR|
" + "Hexagonal geometries more appropriate to close-packed structures than ellipses:
" + "HexPerimeter = 6 * HxgnSide
" + "HxgnSide =√\(\(2*Area\)/\(3*√3\)\)
" + "HexPerimeter = 6 * HxgnSide
" + "Hexagonal Shape Factor "HSF" = |\(P²\/Area-13.856\)|
" + "       Hexagonal Shape Factor from Behndig et al. https://iovs.arvojournals.org/article.aspx?articleid=2122939
" + "       and Collin and Grabsch (1982) https://doi.org/10.1111/j.1755-3768.1982.tb05785.x
" + "Hexagonal Shape Factor Ratio "HSFR" = |\(13.856/\(P²\/Area\)\)|
as above but expressed as a ratio like circularity, with 1 being an ideal hexagon.
" + "HexPerimeter = 6 * HxgnSide
" + "Hexagonality = 6 * HxgnSide/Perimeter
" + "Full Feret coordinate listing using new Roi.getFeretPoints macro function added in ImageJ 1.52m.
" + "Preferences are automatically saved and retrieved from the IJ_prefs file so that favorite geometries can be retained.
" + "[1] John C. Russ, Computer Assisted Microscopy.
" + "[2] John C. Russ, Image Processing Handbook 7th Ed.
"; resultsNeeded = newArray("AR", "Area", "Width", "Height", "Feret", "minFeret", "Circ.", "X", "Y", "XM", "YM", "BX", "BY", "Perim.", "Major", "Minor", "Angle", "FeretAngle"); missing = 0; resultsMissing = ""; for (i = 0; i < resultsNeeded.length; i++) { if (indexOfArray(tableColumns, resultsNeeded[i], -1) < 0) { resultsMissing += resultsNeeded[i] + ", "; missing += 1; } } if (missing > 0) restoreExit("" + missing + " required columns " + resultsMissing + " are missing"); /* Check for co-centric objects (should find twice-analyzed) */ XMs = Table.getColumn("XM"); YMs = Table.getColumn("YM"); XMsRank = Array.rankPositions(XMs); concentrics = newArray(); for (i = 1; i < nTable - 1; i++) { if ((XMs[XMsRank[i]] == XMs[XMsRank[i + 1]]) && (YMs[XMsRank[i]] == YMs[XMsRank[i + 1]])) concentrics = Array.concat(concentrics, XMsRank[i]); } lConcentrics = lengthOf(concentrics); if (lConcentrics > 0) { IJ.log("Warning: there are " + lConcentrics + " objects:"); Array.print(concentrics); } /* The measurements are split into groups for organization purposes and then recombined for simplicity of use with Dialog.addCheckboxGroup */ analysesI = newArray("Image_Scale", "Unit", "ObjectN", "ROI_name", "Image_Name"); /* Information */ analysesF = newArray("AR_Bounding_Rect", "AR_Feret", "Roundness_Feret", "Compactness_Feret", "Feret_Coords"); analysesA = newArray("Angle_0-90", "FeretAngle_0-90", "CircToEllipse_Tilt"); analysesPx = newArray("X\(px\)", "Y\(px\)", "XM\(px\)", "YM\(px\)", "BX\(px\)", "BY\(px\)", "Bounding_Rect_W\(px\)", "Bounding_Rect_H\(px\)"); analysesD = newArray("D_Area_CircEquiv", "D_Perim_CircEquiv", "Dsph_equiv"); analysesB = newArray("FiberThk_Snake", "Fiber_Thk_Russ1", "Fiber_Thk_Russ2", "Fiber_Lngth_Snake", "Fiber_Lngth_Russ1", "Fiber_Lngth_Russ2", "Fiber_Snake_Curl", "Fiber_Russ1_Curl", "Fiber_Russ2_Curl", "AR_Fiber_Snake", "AR_Fiber_Russ1", "AR_Fiber_Russ2"); analysesS = newArray("Sqr_Diag_A", "Squarity_AP", "Squarity_AF", "Squarity_Ff"); analysesH = newArray("Hxgn_Side", "Hxgn_Perim", "Hxgn_Shape_Factor", "Hxgn_Shape_Factor_R", "Hexagonality"); analysesM = newArray("Convexity", "Elongation", "Roundness_cAR", "Interfacial_Density", "Thinnes_Ratio", "Extent"); analysesV = newArray("Vol_Pointed_Spheroid", "Vol_Discus_Spheroid"); analyses1 = Array.concat(analysesI, analysesF, analysesA); analyses3 = Array.concat(analysesD, analysesB, analysesS, analysesH, analysesM, analysesV); defaultOffs = newArray("Blank", "Fiber_Thk_Russ1", "Fiber_Lngth_Russ1", "AR_Fiber_Russ1", "Fiber_Russ1_Curl", "Vol_Pointed_Spheroid", "Vol_Discus_Spheroid"); if (lcf != 1) { analyses = Array.concat(analyses1, analysesPx, analyses3); prefsNameKey = "ascExtGeoPrefs_LCF."; } else { analyses = Array.concat(analyses1, analyses3); prefsNameKey = "ascExtGeoPrefs."; } analyses = Array.concat("Blank", analyses); /* An empty column - mostly as a static zero array index for later */ defaultAnalyses = newArray(); for (i = 0, j = 0; i < lengthOf(analyses); i++) if (indexOfArray(defaultOffs, analyses[i], -1) < 0) { defaultAnalyses[j] = analyses[i]; j++; } prefsAnalysesSelected = call("ij.Prefs.get", prefsNameKey + "AnalysesSelected", "Empty"); if (prefsAnalysesSelected != "Empty") { /* clean up old prefs */ prefsAnalyses = call("ij.Prefs.set", prefsNameKey + "Analyses", ""); prefsAnalysesOn = call("ij.Prefs.set", prefsNameKey + "AnalysesOn", ""); /* End cleanup */ if (prefsAnalysesSelected != "None") chosenAnalyses = split(prefsAnalysesSelected, prefsDelimiter); else chosenAnalyses = newArray(); } else chosenAnalyses = defaultAnalyses; chosenResultCheck = newArray(lengthOf(analyses)); chosenResultCheck = Array.fill(chosenResultCheck, false); for (i = 0; i < lengthOf(chosenResultCheck); i++) { if (indexOfArray(chosenAnalyses, analyses[i], -1) >= 0) chosenResultCheck[i] = true; } checkboxGroupColumns = 5; checkboxGroupRows = round(analyses.length / checkboxGroupColumns) + 1; /* Add +1 to make sure that there are enough cells */ /* Check for information-poor columns */ intColsAll = newArray("Mean", "StdDev", "Mode", "Min", "Max", "IntDen", "Median", "Skew", "Kurt", "%Area", "RawIntDen", "Slice"); intColsAllN = intColsAll.length; intCols = newArray(); intColsVals = newArray(); for (i = 0; i < intColsAllN; i++) { if (indexOf("\t" + tableHeadings, "\t" + intColsAll[i] + "\t") > -1) { vals = Table.getColumn(intColsAll[i]); Array.getStatistics(vals, min, max, nul, nul); if (min == max || (endsWith(min, "Infinity")) && endsWith(max, "Infinity")) { intCols = Array.concat(intCols, intColsAll[i]); intColsVals = Array.concat(intColsVals, intColsAll[i] + "\(" + min + "\)"); } } } intColsN = intCols.length; if (intColsN > 0) intColsText = arrayToString(intColsVals, ", "); Dialog.create("Select Extended Geometrical Analyses: " + lMacro); Dialog.setInsets(-5, 20, 0); Dialog.addMessage("Image file name: " + fullFName); Dialog.setInsets(-5, 20, 0); Dialog.addMessage("Results table name: \"" + tableTitle + "\", with " + nTable + " row\(s\) and " + lConcentrics + " concentric objects"); // if (lConcentrics)>0) Dialog.addMessage(lContric"Results table name: \"" + tableTitle + "\", with " + nTable + " row\(s\)"); if (tableScale) { getPixelSize(iUnit, iPixelWidth, iPixelHeight); Dialog.setInsets(-5, 20, 0); Dialog.addMessage("Pixel Width = " + iPixelWidth + ", Pixel AR = " + iPixelWidth / iPixelHeight + ", Unit = " + iUnit + " - - - Active image scale"); Dialog.setInsets(-5, 20, 0); Dialog.addMessage("Pixel Width = " + parseFloat(tablePW) + ", Pixel AR = " + parseFloat(tablePAR) + ", Unit = " + tableUnit + " - - - Scale imported from embedded table data"); } else { Dialog.setInsets(-5, 20, 0); Dialog.addMessage("Pixel Width = " + pixelWidth + ", Pixel AR = " + pixelWidth / pixelHeight + ", Unit = " + unit + " - - - Image scale used"); } if (pixelAR != 1) Dialog.addMessage("IMPORTANT: The pixels are not reported as square, these extended geometries have not been tested for this condition."); if (roiManager("count") != nTable || nImages == 0) { iFeretC = indexOfArray(analyses, "Feret_Coords", -1); if (iFeretC >= 0) Array.deleteIndex(analyses, iFeretC); Dialog.addMessage("Extended Feret Coordinates series not available as they require ROIs and an open image", 12, "#782F40"); } Dialog.addCheckboxGroup(checkboxGroupRows, checkboxGroupColumns, analyses, chosenResultCheck); Dialog.addRadioButtonGroup("Override above selections with:", newArray("No", "Select All", "Default Analyses"), 1, 3, "No"); Dialog.addRadioButtonGroup("Set preferences for next run:", newArray("Use Current Selection", "Select All", "Select None", "Default Analyses"), 1, 5, "Use Current Selection"); if (nTable > 1 && intColsN > 0) { Dialog.setInsets(20, 20, 0); /* (top, left, bottom) */ Dialog.addMessage("The following option removes certain columns but ONLY if all the values \(''\) in the column are identical"); Dialog.addCheckbox("Discard: " + intColsText, false); } Dialog.addHelp(html); Dialog.show(); for (i = 0, j = 0; i < analyses.length; i++) { if (Dialog.getCheckbox()) { chosenAnalyses[j] = analyses[i]; j++; } } overrideSelection = Dialog.getRadioButton(); if (overrideSelection == "Select All") chosenAnalyses = analyses; /* Select All */ else if (overrideSelection == "Default Analyses") chosenAnalyses = defaultAnalyses; /* Select defaults */ nextRunSettings = Dialog.getRadioButton(); if (nTable > 1 && intColsN > 0) columnCleanup = Dialog.getCheckbox(); else columnCleanup = false; selectWindow(tableTitle); setBatchMode(true); /* batch mode on*/ if (columnCleanup) { for (i = 0; i < intColsN; i++) { Table.deleteColumn(intCols[i]); wait(30); Table.update; wait(30); } if (intColsN > 1) IJ.log("Columns " + intColsText + " contained a single value \(''\) and were deleted"); else IJ.log("Column " + intColsText + " contained a single value \(''\) and was deleted"); } analysesSelectedKey = prefsNameKey + "AnalysesSelected"; if (lengthOf(chosenAnalyses) < 1) restoreExit("No analyses chosen"); chosenAnalysesString = arrayToString(chosenAnalyses, prefsDelimiter); if (nextRunSettings == "Default Analyses") call("ij.Prefs.set", analysesSelectedKey, arrayToString(defaultAnalyses, prefsDelimiter)); else if (nextRunSettings == "Select All") call("ij.Prefs.set", analysesSelectedKey, arrayToString(analyses, prefsDelimiter)); else if (nextRunSettings == "Select None") call("ij.Prefs.set", analysesSelectedKey, "None"); else call("ij.Prefs.set", analysesSelectedKey, chosenAnalysesString); blankCheck = indexOf(chosenAnalysesString, "Blank"); if (blankCheck >= 0) tableSetColumnValue("Blank", ""); /* "Blank should be the 1st array value so this is the only one that can be zero */ if (nImages > 0 && indexOf(chosenAnalysesString, "Image_Name") >= 0) { tableSetColumnValue("Image_Name", fullFName); } if (lcf != 1) { tableSetColumnValue("lcf", lcf); if (indexOf(chosenAnalysesString, "Image_Scale") >= 0) { tableSetColumnValue("Unit", unit); tableSetColumnValue("PixelWidth", pixelWidth); tableSetColumnValue("PixelAR", pixelAR); } if (indexOf(chosenAnalysesString, "X\(px\)") >= 0) { Table.applyMacro("X_px = X/lcf"); } if (indexOf(chosenAnalysesString, "Y\(px\)") >= 0) { Table.applyMacro("Y_px = Y/lcf"); } if (indexOf(chosenAnalysesString, "XM\(px\)") >= 0) { Table.applyMacro("XM_px = XM/lcf"); } if (indexOf(chosenAnalysesString, "YM\(px\)") >= 0) { Table.applyMacro("YM_px = YM/lcf"); } if (indexOf(chosenAnalysesString, "BX\(px\)") >= 0) { Table.applyMacro("BX_px = round(BX/lcf)"); } if (indexOf(chosenAnalysesString, "BY\(px\)") >= 0) { Table.applyMacro("BY_px = round(BY/lcf)"); } if (indexOf(chosenAnalysesString, "Bounding_Rect_W\(px\)") >= 0) { Table.applyMacro("BoxW_px = round(Width/lcf)"); } if (indexOf(chosenAnalysesString, "Bounding_Rect_H\(px\)") >= 0) { Table.applyMacro("BoxH_px = round(Height/lcf)"); } Table.deleteColumn("lcf"); } wait(10); Table.update; if ((roiManager("count") == nTable) && indexOf(chosenAnalysesString, "ROI_name") >= 0) { for (i = 0; i < nTable; i++) { roiName = call("ij.plugin.frame.RoiManager.getName", i); Table.set("ROI_name", i, roiName); } roiManager("deselect"); } if ((roiManager("count") == nTable) && nImages > 0 && indexOf(chosenAnalysesString, "Feret_Coords") >= 0) { for (i = 0; i < nTable; i++) { roiManager("select", i); Roi.getFeretPoints(x, y); Table.set("FeretX", i, x[0]); Table.set("FeretY", i, y[0]); Table.set("FeretX2", i, x[1]); Table.set("FeretY2", i, y[1]); Table.set("MinFeretX", i, x[2]); Table.set("MinFeretY", i, y[2]); Table.set("MinFeretX2", i, d2s(round(x[3]), 0)); Table.set("MinFeretY2", i, d2s(round(y[3]), 0)); } roiManager("deselect"); } selectWindow(tableTitle); Table.update; if (indexOf(chosenAnalysesString, "ObjectN") >= 0) { Table.setColumn("ObjectN", Array.slice(Array.getSequence(nTable + 1), 1, nTable + 1)); } /* Add ObjectN+1 column for labels */ bSCode = "BS = minOf(Width, Height); BL = maxOf(Width, Height)"; Table.applyMacro(bSCode); Table.update; /* Note that the AR reported by ImageJ is the ratio of the fitted ellipse major and minor axes. */ if (indexOf(chosenAnalysesString, "CircToEllipse_Tilt") >= 0) { Table.applyMacro("Cir_to_El_Tilt = (180/PI) * acos(1/AR)"); } /* The angle a circle (cylinder in 3D) would be tilted to appear as an ellipse with this AR */ if (indexOf(chosenAnalysesString, "AR_Bounding_Rect") >= 0) { Table.applyMacro("AR_Box = BL/BS"); } /* Bounding rectangle aspect ratio */ if (indexOf(chosenAnalysesString, "AR_Feret") >= 0) { Table.applyMacro("AR_Feret = Feret/MinFeret"); } /* adds fitted ellipse aspect ratio. */ if (indexOf(chosenAnalysesString, "Roundness_Feret") >= 0) { Table.applyMacro("Rnd_Feret = 4*Area/(PI * pow(Feret, 2))"); } /* Adds Roundness, using Feret as maximum diameter (IJ Analyze uses ellipse major axis */ if (indexOf(chosenAnalysesString, "Compactness_Feret") >= 0) { Table.applyMacro("Compact_Feret = (sqrt(Area*4/PI))/Feret"); } /* Adds Compactness, using Feret as maximum diameter */ if (indexOf(chosenAnalysesString, "Elongation") >= 0) { Table.applyMacro("Elongation = 1-(minOf(Height/Width, Width/Height))"); } /* Elongation see https://imagej.net/Shape_Filter */ if (indexOf(chosenAnalysesString, "Thinnes_Ratio") >= 0) { Table.applyMacro("Thinnes_Ratio = 1/Circ_"); } /* adds Thinnes ratio. */ if (indexOf(chosenAnalysesString, "Sqr_Diag_A") >= 0) { Table.applyMacro("Sqr_Diag_A = (sqrt(Area*2))"); } /* Adds diagonal of square based on area */ if (indexOf(chosenAnalysesString, "Squarity_AP") >= 0) { Table.applyMacro("Squarity_AP = 1-abs(1-(16*Area/(pow(Perim_, 2))))"); } /* Adds Squarity_AP value, should be 1 for perfect square */ if (indexOf(chosenAnalysesString, "Squarity_AF") >= 0) { Table.applyMacro("Squarity_AF = 1-abs(1-(Feret/(sqrt(2*Area))))"); } /* Adds Squarity_AF value, should be 1 for perfect square */ if (indexOf(chosenAnalysesString, "Squarity_Ff") >= 0) { Table.applyMacro("Squarity_Ff = 1-abs(1-(AR_Feret/sqrt(2)))"); } /* Adds Squarity_Ff value, should be 1 for perfect square */ // wait(delay); Table.update; if (indexOf(chosenAnalysesString, "Fiber") >= 0) { fiberCode = "P2 = pow(Perim_, 2); Fbr_Th_Snk_Units = 1/PI*(Perim_-(sqrt(P2-4*PI*Area))); Fbr_Th_Rss1_Units = Area/((0.5*Perim_)-(2*(Area/Perim_)));" + "Fbr_Th_Rss2_Units = Area/(0.3181*Perim_+sqrt(0.033102*P2-0.41483*Area)); Fbr_L_Snk_Units = Area/Fbr_Th_Snk_Units; Fbr_L_Rss1_Units = (0.5*Perim_)-(2*(Area/Perim_));" + "Fbr_L_Rss2_Units = 0.3181*Perim_+sqrt(0.033102*P2-0.41483*Area)"; Table.applyMacro(fiberCode); /* Fbr_Th_Snk_Units: Round end ribbon thickness from repeating up/down half-annulus - think snake or perhaps Loch Ness Monster Lee & Jablonski LTSW'94 Devils Head Resort. */ /* Fbr_Th_Rss1_Units: Fiber width from fiber length from John C. Russ Computer Assisted Microscopy page 189. */ /* Fbr_Th_Rss2_Units: Fiber width from Fiber Length from John C. Russ Computer Assisted Microscopy page 189. */ } Table.update; if (indexOf(chosenAnalysesString, "Angle_0-90") >= 0) { Table.applyMacro("Angle_0to90 = abs(Angle-90)"); } if (indexOf(chosenAnalysesString, "FeretAngle_0-90") >= 0) { Table.applyMacro("FeretAngle_0to90 = abs(FeretAngle-90)"); } if (indexOf(chosenAnalysesString, "Convexity") >= 0) { /* Perimeter of fitted ellipse from Ramanujan's first approximation */ Table.applyMacro("Convexity = (PI * ((3*(Major/2 + Minor/2)) - sqrt((3*Major/2 + Minor/2)*(Major/2 + 3*Minor/2))))/Perim_"); } /* Convexity using the calculated elliptical fit to obtain a convex perimeter */ if (indexOf(chosenAnalysesString, "Roundness_cAR") >= 0) { Table.applyMacro("Rndnss_cAR = Circ_ + 0.913 - (0.826261 + 0.337479 * AR-0.335455 * pow(AR, 2) + 0.103642 * pow(AR, 3) - 0.0155562 * pow(AR, 4) + 0.00114582 * pow(AR, 5) - 0.0000330834 * pow(AR, 6))"); /* Circularity corrected by aspect ratio roundness: https://doi.org/10.1186/s40645-015-0078-x */ } Table.update; if (indexOf(chosenAnalysesString, "D_Area_CircEquiv") >= 0) { Table.applyMacro("Da_Equiv_Units = 2*(sqrt(Area/PI))"); } /* Darea-equiv (AKA Heywood diameter) - remember no spaces allowed in label. */ if (indexOf(chosenAnalysesString, "D_Perim_CircEquiv") >= 0) { Table.applyMacro("Dp_Equiv_Units = Perim_/PI"); } /* Adds new perimeter-equivalent Diameter column to end of results table - remember no spaces allowed in label. */ if (indexOf(chosenAnalysesString, "Dsph_equiv") >= 0) { Table.applyMacro("Dsph_Equiv_Units = exp((log(6*Area*(Feret+MinFeret)/(2*PI)))/3)"); } /* Adds diameter based on a sphere - Russ page 182 but using the mean Feret diameters to calculate the volume */ if (indexOf(chosenAnalysesString, "Fiber") >= 0) { if (indexOf(chosenAnalysesString, "Fiber_Snake_Curl") >= 0) { Table.applyMacro("Fbr_Snk_Crl = BL/Fbr_L_Snk_Units"); } /* Adds Curl for Fiber 1 calculated and bounding length */ if (indexOf(chosenAnalysesString, "Fiber_Russ1_Curl") >= 0) { Table.applyMacro("Fbr_Rss1_Crl = BL/Fbr_L_Rss1_Units"); } /* Adds Curl for Fiber Russ 1 calculated and bounding length */ if (indexOf(chosenAnalysesString, "Fiber_Russ2_Curl") >= 0) { Table.applyMacro("Fbr_Rss2_Crl = BL/Fbr_L_Rss2_Units"); } /* Adds Curl for Fiber Russ 2 calculated and bounding length */ if (indexOf(chosenAnalysesString, "AR_Fiber_Snake") >= 0) { Table.applyMacro("AR_Fbr_Snk = Fbr_L_Snk_Units/Fbr_Th_Snk_Units"); } /* Aspect ratio from fiber length approximation 1. */ if (indexOf(chosenAnalysesString, "AR_Fiber_Russ1") >= 0) { Table.applyMacro("AR_Fbr_Russ1 = Fbr_L_Rss1_Units/Fbr_Th_Rss1_Units"); } /* Aspect ratio from fiber length approximation 2. */ if (indexOf(chosenAnalysesString, "AR_Fiber_Russ2") >= 0) { Table.applyMacro("AR_Fbr_Russ2 = Fbr_L_Rss2_Units/Fbr_Th_Rss2_Units"); } /* aspect ratio from fiber length approximation*/ } Table.update; /* Calculates Interface Density (e.g. grain boundary density based on the interfaces between objects being shared. */ if (indexOf(chosenAnalysesString, "Interfacial_Density") >= 0) { Table.applyMacro("IntfcD = Perim_/(2*Area)"); /* Adds new IntD column to end of results table - remember no spaces allowed in label. */ tableColumnRenameOrReplace("IntfcD", "Intfc_D" + "\(" + unit + supminus + supone + "\)"); // GL = d2s(G, 4); /* Reduce Decimal places for labeling. */ } Table.update; if (indexOf(chosenAnalysesString, "Extent") >= 0) { Table.applyMacro("Extent = Area/(BL * BS)"); } /* adds Extent ratio, which is the object area/bounding rectangle area */ if (indexOf(chosenAnalysesString, "Hex") >= 0 || indexOf(chosenAnalysesString, "Hxg") >= 0) { Table.applyMacro("Ps2A = P2/Area"); Table.applyMacro("HSFideal = 8 * sqrt(3)"); /* Collin and Grabsch (1982) https://doi.org/10.1111/j.1755-3768.1982.tb05785.x */ if (indexOf(chosenAnalysesString, "Hxgn_Side") >= 0) { Table.applyMacro("Hxgn_Side_Units = sqrt((2*Area)/(3*sqrt(3)))"); } /* adds the length of each hexagonal side */ if (indexOf(chosenAnalysesString, "Hxgn_Perim") >= 0) { Table.applyMacro("Hxgn_Perim_Units = 6 * Hxgn_Side_Units"); } /* adds total perimeter of hexagon */ if (indexOf(chosenAnalysesString, "Hxgn_Shape_Factor") >= 0) { Table.applyMacro("HSF = abs(Ps2A-HSFideal)"); } /* Hexagonal Shape Factor from Behndig et al. https://iovs.arvojournals.org/article.aspx?articleid=2122939 */ if (indexOf(chosenAnalysesString, "Hxgn_Shape_Factor_R") >= 0) { Table.applyMacro("HSFR = abs(HSFideal/Ps2A)"); } /* Hexagonal Shape Factor as ratio to the ideal HSF, the value for a perfect hexagon is 1 */ if (indexOf(chosenAnalysesString, "Hexagonality") >= 0) { Table.applyMacro("Hexagonality = 6*Hxgn_Side_Units/Perim_"); } /* adds a term to indicate accuracy of hexagon approximation */ Table.update; Table.deleteColumn("Ps2A"); Table.update; Table.deleteColumn("HSFideal"); Table.update; } if (indexOf(chosenAnalysesString, "Vol_Pointed_Spheroid") >= 0) { Table.applyMacro("VolPtdSphr = (PI/6) * Feret * pow(MinFeret, 2)"); /* adds prolate ellipsoid (an American football) volume: Hilliard 1968, Russ p. 189 */ tableColumnRenameOrReplace("VolPtdSphr", "Vol_PtdSphr" + "\(" + unit + supthree + "\)"); } if (indexOf(chosenAnalysesString, "Vol_Discus_Spheroid") >= 0) { Table.applyMacro("VolDiscus = (PI/6) * MinFeret * pow(Feret, 2)"); /* adds oblate ellipsoid (a discus) volume: Hilliard 1968, Russ p. 189 */ tableColumnRenameOrReplace("VolDiscus", "Vol_Discus" + "\(" + unit + supthree + "\)"); } tableDeleteColumn("P2"); tableDeleteColumn("BS"); tableDeleteColumn("BL"); finalColumns = split(Table.headings, "\t"); for (i = 0; i < finalColumns.length; i++) { cN = finalColumns[i]; if (indexOf(cN, "_px") > 0) tableColumnRenameOrReplace(cN, replace(cN, "_px", "\(px\)")); if (indexOf(cN, "_Units") > 0) tableColumnRenameOrReplace(cN, replace(cN, "_Units", unitLabel)); if (indexOf(cN, "_0to90") > 0) tableColumnRenameOrReplace(cN, replace(cN, "0to90", "\(0-90" + degreeSym + "\)")); } if (infinityCount > 0) { for (i = 0; i < columnsL; i++) { for (j = 0; j < nTable; j++) { if (Table.get(tableColumns[i], j) == infinitySym) { Table.set(tableColumns[i], j, "-Infinity"); infinityCount -= 1; if (infinityCount == 0) { j = nTable; i = columnsL; } } } } } Table.update; tableHeadings = Table.headings; tableColumns = split(Table.headings, "\t"); setBatchMode("exit & display"); /* exit batch mode */ showStatus("Additional Geometries Macro Finished for: " + nTable + " Objects, " + lengthOf(tableColumns) + " columns"); Table.rename(tableTitle, "Results"); /* column headers export not supported for tables otherwise :-( */ beep(); wait(300); beep(); wait(300); beep(); restoreSettings; call("java.lang.System.gc"); /* End of Add_Unit-Scaled_Extended_Geometries_to_Results */ } /* !!!! Note the macro title in the startup includes the shortcut [F12] !!!! */ macro "Set Selection or Trim [F12]" { macroL = "Set_Selection_or_Trim_v251106.ijm"; delimiter = "|"; prefsNameKey = "ascSetSelection."; prefsParaKey = prefsNameKey + "Parameters"; prefsValKey = prefsNameKey + "Values"; prefsPara = call("ij.Prefs.get", prefsParaKey, "None"); prefsVal = "" + call("ij.Prefs.get", prefsValKey, "None"); prefsParas = split(prefsPara, delimiter); prefsVals = split(prefsVal, delimiter); orImageID = getImageID(); selectionTypeNames = newArray("rectangle", "oval", "polygon", "freehand", "traced", "straight line", "segmented line", "freehand line", "angle", "composite"); /* ASC message theme */ infoColor = "#006db0"; /* Honolulu blue */ instructionColor = "#798541"; /* green_dark_modern (121,133,65) AKA Wasabi */ infoWarningColor = "#ff69b4"; /* pink_modern AKA hot pink */ infoFontSize = 12; if (prefsParas.length != prefsVals.length) { Dialog.create("Prefs mismatch: " + macroL); Dialog.addMessage(prefsParas.length + " Preference Parameters", infoFontSize, infoWarningColor); Dialog.addMessage(prefsVals.length + " Preference Values", infoFontSize, infoWarningColor); options = newArray("Reset prefs", "Continue", "Exit"); Dialog.addRadioButtonGroup("Options:", options, 3, 1, "Reset prefs"); Dialog.show(); choice = Dialog.getRadioButton; if (choice == "Exit") exit("Goodbye"); else if (choice == "Reset prefs") { prefsParas = newArray("none"); prefsVals = newArray("none"); call("ij.Prefs.set", prefsParaKey, "none"); call("ij.Prefs.set", prefsValKey, "none"); } } selName = "Auto-generated_name"; getDimensions(imageWidth, imageHeight, channels, slices, frames); imageD = 0.5 * (imageWidth + imageHeight); orAR = imageWidth / imageHeight; objectsBounds = false; if (Table.size > 0) { if (Table.get("BX\(px\)", 0) >= 0 && Table.get("BY\(px\)", 0) >= 0 && Table.get("BoxW\(px\)", 0) >= 0 && Table.get("BoxH\(px\)", 0) >= 0) { objectsBounds = true; BXpxs = Table.getColumn("BX\(px\)"); Array.getStatistics(BXpxs, BXpxsMin, BXpxsMax, null, null); iBXpxsMax = indexOfArray(BXpxs, BXpxsMax, -1); BWpxs = BXpxsMax + Table.get("BoxW\(px\)", iBXpxsMax) - BXpxsMin; BYpxs = Table.getColumn("BY\(px\)"); Array.getStatistics(BYpxs, BYpxsMin, BYpxsMax, null, null); iBYpxsMax = indexOfArray(BYpxs, BYpxsMax, -1); BHpxs = BYpxsMax + Table.get("BoxH\(px\)", iBYpxsMax) - BYpxsMin; WHDiff = BWpxs - BHpxs; } } selType = selectionType(); /* Provide default or previous values */ stdDims = newArray(128, 256, 384, 512, 768, 1024, 1280, 1536, 1920, 2048, 2304, 3072, 3840, 4096, imageWidth, imageHeight); if (selType >= 0) { getSelectionBounds(selX, selY, selWidth, selHeight); startX = selX; startY = selY; newW = round(selWidth); newH = round(selHeight); orSelAR = selWidth / selHeight; oldSelName = selectionName; if (oldSelName != "") selName = oldSelName; // run("Select None"); if (selType == 1) selTypeName = "Oval"; else selTypeName = "Rectangle"; if ((indexOfArray(stdDims, newW, -1)) < 0 && newW < imageWidth) stdDims = Array.concat(stdDims, newW); if ((indexOfArray(stdDims, newH, -1)) < 0 && newH < imageHeight) stdDims = Array.concat(stdDims, newH); } else { selTypeName = getPrefsFromParallelArrays(prefsParas, prefsVals, "selTypeName", "Rectangle"); startX = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "startX", 0)); startY = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "startY", 0)); newH = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "newH", imageHeight)); newW = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "newW", imageWidth)); } stdDims = Array.sort(stdDims); for (i = 0; i < stdDims.length; i++) { if (stdDims[i] >= imageWidth) { stdDims = Array.trim(stdDims, minOf(stdDims.length, i + 1)); i = stdDims.length; } } stdDimsSt = newArray(""); for (i = 0; i < stdDims.length; i++) stdDimsSt[i] = d2s(stdDims[i], 0); aspectR = parseFloat(getPrefsFromParallelArrays(prefsParas, prefsVals, "aspectR", newW / newH)); selAR = getPrefsFromParallelArrays(prefsParas, prefsVals, "selAR", "4:3"); if (aspectR >= 1) orientation = getPrefsFromParallelArrays(prefsParas, prefsVals, "orientation", "landscape"); else orientation = getPrefsFromParallelArrays(prefsParas, prefsVals, "orientation", "portrait"); if (is("binary")) stdDims2 = newArray("fraction", "non-background"); else stdDims2 = newArray("fraction"); fractW = parseFloat(getPrefsFromParallelArrays(prefsParas, prefsVals, "fractW", 0.33333)); fractH = parseFloat(getPrefsFromParallelArrays(prefsParas, prefsVals, "fractH", 0.33333)); startFractX = parseFloat(getPrefsFromParallelArrays(prefsParas, prefsVals, "startFractX", (1 - fractW) / 2)); startFractY = parseFloat(getPrefsFromParallelArrays(prefsParas, prefsVals, "startFractY", (1 - fractH) / 2)); trimR = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "trimR", 0)); trimL = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "trimL", 0)); trimT = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "trimT", 0)); trimB = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "trimB", 0)); rotS = parseFloat(getPrefsFromParallelArrays(prefsParas, prefsVals, "rotS", 0)); // selName = getPrefsFromParallelArrays(prefsParas,prefsVals,"selName",selName); /* not in use, too annoying */ addOverlay = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "addOverlay", false)); addROI = parseInt(getPrefsFromParallelArrays(prefsParas, prefsVals, "addROI", false)); saveSelection = getPrefsFromParallelArrays(prefsParas, prefsVals, "saveSelection", false); cropSelection = getPrefsFromParallelArrays(prefsParas, prefsVals, "cropSelection", false); dupSelection = getPrefsFromParallelArrays(prefsParas, prefsVals, "dupSelection", false); lastSelectionPath = getPrefsFromParallelArrays(prefsParas, prefsVals, "selectionPath", "None"); /* End of Default/Previous value section */ Dialog.create("Selection choices \(" + macroL + "\)"); if (selType >= 0) selectionText = "Original Selection"; else selectionText = "Center of Image"; selectionTypes = newArray("Rectangle", "Oval", "Restore Sel. \(Ctrl+E\)", "All \(Ctrl+A\)", "Auto BB \(bounding box\)", "Tight BB \(experimental\)"); if (selType >= 0) selectionTypes = Array.concat(selectionTypes, "Existing within BB", "None \(Ctrl+A\)", "Inverse"); if (lastSelectionPath != "None") selectionTypes = Array.concat(selectionTypes, "Restore Last Saved"); if (objectsBounds) { Dialog.addMessage("Object\(s\) bounding box: X1: " + BXpxsMin + ", Y1: " + BYpxsMin + ", Width: " + BWpxs + ", Height: " + BHpxs + ", Width-Height: " + WHDiff + "\nBounding box overrides other options if selected", infoFontSize, infoColor); selectionTypes = Array.concat(selectionTypes, "Bounding Box"); } iST = indexOfArray(selectionTypes, selTypeName, 0); Dialog.addRadioButtonGroup("Selection type \('Restore Sel.' and 'Auto BB' options create selections and exit\):", selectionTypes, round(selectionTypes.length / 3), 3, selectionTypes[iST]); if (objectsBounds) { Dialog.addNumber("Selection width expansion if bounding box selected", 0, 0, 6, "pixels"); Dialog.addNumber("Selection height expansion if bounding box selected", 0, 0, 6, "pixels"); } Dialog.setInsets(5, 20, 0); /* top, left, bottom, addNumber defaults: 5,0,3 (first field) or 0,0,3,0 */ Dialog.addNumber("Auto-BB buffer \(expansion after auto\):", 0, 1, 4, "0.5 * \(width+height\) in %"); Dialog.setInsets(0, 50, 0); Dialog.addMessage("Tight BB \(bounding box\): Rectangular selection that excludes the background", infoFontSize, infoColor); Dialog.setInsets(5, 20, 0); Dialog.addNumber("Tight BB: Intensity tolerance:", 0.01, 3, 4, "% \(will be slow for large values\)"); Dialog.setInsets(3, 20, 0); Dialog.addNumber("Tight BB: Limits:", 20, 0, 3, "% \(necessary for annotation bars\)"); stdDimsF = Array.concat(stdDimsSt, stdDims2); stdDimsFL = lengthOf(stdDimsF); iDefDimsF = indexOfArray(stdDimsF, newW, stdDimsFL - 1); buttonGroupTxt1 = "Selection width \(image width = " + imageWidth; if (selType >= 0) buttonGroupTxt1 += " , original selection width = " + newW; if (selType >= 0) buttonGroupTxt1 += " , original selection height = " + newH; buttonGroupTxt1 += "\):"; if (iDefDimsF < stdDims.length) Dialog.addRadioButtonGroup(buttonGroupTxt1, stdDimsF, floor(stdDimsFL / 9) + 1, 9, stdDimsF[iDefDimsF]); else Dialog.addRadioButtonGroup("Fixed selection widths \(image width = " + imageWidth + ", image height = " + imageHeight + "\):", stdDimsF, floor(stdDimsFL / 9) + 1, 9, stdDimsF[iDefDimsF]); Dialog.addNumber("Manual width entry:", "", 0, 10, "pixels \(change to override above\)"); Dialog.addNumber("Corner arc for rounded rectangles", NaN, 0, 6, "pixels"); Dialog.setInsets(0, 50, 0); if (selType >= 0) Dialog.addMessage("The 'fraction' option uses fractions of current selection for size and location \(new dialog\)", infoFontSize, infoColor); else Dialog.addMessage("The 'fraction' option uses fractions of current image for size and location \(new dialog\)", infoFontSize, infoColor); // Dialog.setInsets(3, 20, 0); if (selType >= 0) ctrType = "selection"; else ctrType = "image"; if (selType >= 0) { Dialog.addMessage("Trim \(negative values\ or expand selection, leave all as zero to use preset above", infoFontSize, instructionColor); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Left trim/expand", 0, 0, 8, "last trim: " + trimL); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Top trim/expand:", 0, 0, 8, "last trim: " + trimT); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Right trim/expand:", 0, 0, 8, "last trim: " + trimR); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Bottom trim/exp:", 0, 0, 8, "last trim: " + trimB); aspectRatios = newArray("1:1", "4:3", "golden", "16:9", "selection", "window", "entry"); } else { Dialog.addMessage("Override with set coordinates or trim \(negative\), leave all as zero to use preset above", infoFontSize, instructionColor); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Upper left X / -left trim", 0, 0, 8, "leave as 0 to to center on " + ctrType); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Upper left X / -left trim", 0, 0, 8, "leave as 0 to to center on " + ctrType); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Upper left Y / -top trim:", 0, 0, 8, "leave as 0 to to center on " + ctrType); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Width / -right trim", 0, 0, 8, "0 for preset above. Negative = trim"); Dialog.setInsets(0, 0, 0); Dialog.addNumber("Height / -bottom trim", 0, 0, 8, "\"0\": Uses AR below. Negative = trim"); aspectRatios = newArray("1:1", "4:3", "golden", "16:9", "window", "entry"); } iAR = indexOfArray(aspectRatios, selAR, 1); Dialog.addRadioButtonGroup("Aspect Ratio \(AR does not alter 'fraction', 'trim' or 'non-background' widths\) \['window' = " + d2s(orAR, 3) + ":1\]:", aspectRatios, 1, lengthOf(aspectRatios), aspectRatios[iAR]); if (selType >= 0) { Dialog.setInsets(3, 20, 10); /* top, left, bottom, addCheckbox defaults: 15,20,0 (first checkbox) or 0,20,0 */ Dialog.addCheckbox("Correct image distortion to AR above \(selection w/h is " + orSelAR + "\) by shrinking longest dimension?", false); } Dialog.addCheckbox("Orientation is Landscape \(else Portrait\)", true); Dialog.addNumber("Selection rotation:", rotS, 5, 5, "degrees"); Dialog.setInsets(0, 0, 0); Dialog.addString("Selection name:", selName, 30); checkBoxGroup1Labels = newArray("Add selection to overlay", "Add selection to ROI manager", "Save selection in image folder", "Crop to selection", "Duplicate selection"); checkBoxGroup1Defaults = newArray(addOverlay, addROI, saveSelection, cropSelection, dupSelection); Dialog.setInsets(3, 20, 3); Dialog.addCheckboxGroup(2, 3, checkBoxGroup1Labels, checkBoxGroup1Labels); Dialog.setInsets(3, 10, 10); /* top, left, bottom, addMessage defaults: 0,20,0 (empty string) or 10,20,0 */ Dialog.addMessage("Use the arrow keys or mouse to move the selection after macro completion", infoFontSize, instructionColor); Dialog.show(); selTypeName = Dialog.getRadioButton(); if (objectsBounds) { bBEnlX = Dialog.getNumber(); bBEnlY = Dialog.getNumber(); } aBuffer = Dialog.getNumber(); tBBTolerancePc = Dialog.getNumber(); tBBLimitsPc = Dialog.getNumber(); selSelWidth = Dialog.getRadioButton(); manEntry = Dialog.getNumber(); if (manEntry != NaN && manEntry > 0) selSelWidth = manEntry; roundingArc = Dialog.getNumber; // print ("manEntry="+manEntry+",selSelWidth="+selSelWidth); startX = Dialog.getNumber; startY = Dialog.getNumber; newW = Dialog.getNumber; newH = Dialog.getNumber; selAR = Dialog.getRadioButton(); if (selType >= 0) correctAR = Dialog.getCheckbox(); else correctAR = false; if (Dialog.getCheckbox()) newOr = "landscape"; else newOr = "portrait"; rotS = Dialog.getNumber(); selName = Dialog.getString(); if (selName == "") selName = "Auto-generated_name"; else if (selName == "Auto-generated_name") selName = selTypeName + ": width = " + selSelWidth; /* checkBoxGroup1 */ addOverlay = Dialog.getCheckbox(); addROI = Dialog.getCheckbox(); saveSelection = Dialog.getCheckbox(); cropSelection = Dialog.getCheckbox(); dupSelection = Dialog.getCheckbox(); if (selAR == "1:1") aspectR = 1; else if (selAR == "4:3") aspectR = 3 / 4; else if (selAR == "golden") aspectR = 1.61803398875; else if (selAR == "16:9") aspectR = 16 / 9; else if (selAR == "window") aspectR = orAR; else if (selAR == "entry") { Dialog.create("AR menu"); Dialog.addNumber("Set aspect ratio:", aspectR); Dialog.addMessage("Height will be adjusted", infoFontSize, infoColor); Dialog.show(); aspectR = Dialog.getNumber(); } else aspectR = -1; if (newOr == "landscape") aspectR = maxOf(aspectR, 1 / aspectR); else aspectR = minOf(aspectR, 1 / aspectR); if (selType >= 0 && correctAR && aspectR > 0) { arR = orSelAR / aspectR; if (arR != 1) { arCTitle = "" + stripKnownExtensionFromString(getTitle()) + "_arC"; // getDimensions(imageWidth, imageHeight, null, null, null); newImageWidth = imageWidth; newImageHeight = imageHeight; run("Select None"); if (arR > 1) newImageWidth = imageWidth / arR; else newImageHeight = imageHeight * arR; if (slices > 1) run("Scale...", "x=- y=- z=1.0 width=&newImageWidth height=&newImageHeight depth=&slices interpolation=Bicubic average process create title=&arCTitle"); else run("Scale...", "x=- y=- width=&newImageWidth height=&newImageHeight interpolation=Bicubic average create title=&arCTitle"); selectImage(orImageID); run("Restore Selection"); } call("java.lang.System.gc"); /* force a garbage collection */ exit; } if (startsWith(selTypeName, "None") || startsWith(selTypeName, "Restore Sel") || startsWith(selTypeName, "All") || startsWith(selTypeName, "Auto") || startsWith(selTypeName, "Inverse") || startsWith(selTypeName, "Existing") || startsWith(selTypeName, "Tight")) { if (startsWith(selTypeName, "None")) run("Select None"); if (startsWith(selTypeName, "Restore Sel")) run("Restore Selection"); else if (startsWith(selTypeName, "All")) run("Select All"); else if (startsWith(selTypeName, "Auto")) { run("Select None"); run("Select Bounding Box (guess background color)"); if (aBuffer > 0) { enlargePix = maxOf(2, round(aBuffer * imageD / 100)); run("Enlarge...", "enlarge=&enlargePix pixel"); } } else if (startsWith(selTypeName, "Existing")) run("Select Bounding Box (guess background color)"); else if (startsWith(selTypeName, "Inverse")) run("Make Inverse"); else if (startsWith(selTypeName, "Tight")) { showStatus("Finding tight bounding box"); tightBoundingBox(tBBTolerancePc, tBBLimitsPc); showStatus("Found tight bounding box"); if (aBuffer > 0) { enlargePix = maxOf(2, round(aBuffer * imageD / 100)); /* assume that if any umber is put in then at least 1 pixel buffer is desired */ run("Enlarge...", "enlarge=&enlargePix pixel"); } } if (dupSelection && selectionType() >= 0) { cropTitle = "" + stripKnownExtensionFromString(getTitle()) + "_crop"; run("Duplicate...", "title=&cropTitle duplicate"); } if (cropSelection && selectionType() >= 0) { rename("" + stripKnownExtensionFromString(getTitle()) + "_crop"); run("Crop"); /* assume that if any umber is put in then at least 1 pixel buffer is desired */ } updateDisplay(); exit; } else if (selTypeName == "Bounding Box") { if (roundingArc != NaN) makeRectangle(BXpxsMin - floor(bBEnlX / 2), BYpxsMin - floor(bBEnlY / 2), BWpxs + bBEnlX, BHpxs + bBEnlY, roundingArc); else makeRectangle(BXpxsMin - floor(bBEnlX / 2), BYpxsMin - floor(bBEnlY / 2), BWpxs + bBEnlX, BHpxs + bBEnlY); } else if (startsWith(selTypeName, "Restore Sel")) { run("Restore Selection"); exit; } else if (startsWith(selTypeName, "Restore Last")) { open(lastSelectionPath); newSelType = selectionType(); exit; } else { if (selTypeName == "Oval") newSelType = 1; else newSelType = 0; } if (selType < 0 && selSelWidth != "fraction") { if (newW > 0 && newH > 0) { aspectR = newW / newH; selSelWidth = newW; /* just so it is not a "fraction" */ } if (newH < 0 || newW < 0 || startX < 0 || startY < 0) selSelWidth = "trim"; else { if (newH > 0 || newW > 0 || startX > 0 || startY > 0) { if (newW == 0 && newH > 0) { newW1 = round(aspectR * newH); if (newW1 > imageWidth) { newW = imageWidth; newH = round(newH * imageWidth / newW1); print("Selection limited by image width to " + newW + " x " + newH); } else newW = newW1; } else if (newW > 0 && newH == 0) { newH1 = round(newW / aspectR); if (newH1 > imageHeight) { newH = imageHeight; newW = round(newW * imageHeight / newH1); print("Selection limited by image height to " + newW + " x " + newH); } else newH = newH1; } // selSelWidth = newW; // selSelHeight = newH; newSelHeight = newH; newSelWidth = newW; } else if (newW == 0 && newH == 0) { newSelWidth = parseFloat(selSelWidth); if (newSelWidth / aspectR < imageHeight) newSelHeight = round(minOf(imageHeight, newSelWidth / aspectR)); else { newSelHeight = imageHeight; newSelWidth = round(imageHeight * aspectR); print("Selection limited by image height to " + newSelHeight + " x " + newSelWidth); } } else { newSelHeight = imageHeight; newSelWidth = round(newSelHeight * aspectR); } selSelWidth = "values"; /* overrides "trim" */ } } else { if (newH != 0 || newW != 0 || startX != 0 || startY != 0) selSelWidth = "trim"; else if (selSelWidth != "fraction") { newSelWidth = minOf(imageWidth, parseFloat(selSelWidth)); newSelHeight = round(newSelWidth / aspectR); if (newSelHeight > imageHeight) { newSelHeight = imageHeight; newSelWidth = round(newSelHeight * aspectR); print("Selection limited by image height to " + newSelWidth + " x " + newSelHeight); } } } if (selSelWidth == "fraction" || selSelWidth == "non-background" || selSelWidth == "trim") { if (selSelWidth == "fraction") { Dialog.create("Fraction of original image or selection dimensions"); if (selType != -1) Dialog.addMessage("Original selection type = " + selectionTypeNames[selType], infoFontSize, infoColor); Dialog.addMessage("New selection type = " + selectionTypeNames[newSelType]); Dialog.addNumber("Fraction of width", fractW); Dialog.addNumber("Fraction of height", fractW); Dialog.addNumber("Start X Fraction of width", (1 - fractW) / 2); Dialog.addNumber("Start Y Fraction of height", (1 - fractW) / 1); Dialog.show(); fractW = minOf(1, Dialog.getNumber); fractH = minOf(1, Dialog.getNumber); startFractX = minOf(1, Dialog.getNumber); startFractY = minOf(1, Dialog.getNumber); if (selType != -1) { w = maxOf(1, round(selWidth * fractW)); h = maxOf(1, round(selHeight * fractH)); x = maxOf(0, round(selX + selWidth * startFractX)); if ((x + w) > imageWidth) x = imageWidth - w; y = maxOf(0, round(selY + selHeight * startFractY)); if ((y + h) > imageWidth) y = imageHeight - h; if (newSelType == 0) { if (roundingArc != NaN) makeRectangle(x, y, w, h, roundingArc); else makeRectangle(x, y, w, h); } else makeOval(x, y, w, h); } else { w = round(imageWidth * fractW); h = round(imageHeight * fractH); x = maxOf(0, round(imageWidth * startFractX)); if ((x + w) > imageWidth) x = imageWidth - w; y = maxOf(0, round(imageHeight * startFractY)); if ((y + h) > imageWidth) y = imageHeight - h; if (newSelType == 0) { if (roundingArc != NaN) makeRectangle(x, y, w, h, roundingArc); else makeRectangle(x, y, w, h); } else makeOval(x, y, w, h); } } else if (selSelWidth == "non-background") { run("Create Selection"); run("To Bounding Box"); Dialog.create("Expand non-background selection dialog"); Dialog.addNumber("Enlarge selection by:", 0, 0, 5, "pixels"); Dialog.show; enlargeP = Dialog.getNumber; if (enlargeP != 0) run("Enlarge...", "enlarge=&enlargeP pixel"); } else if (selSelWidth == "trim") { trimL = startX; trimT = startY; trimR = newW; trimB = newH; if (newSelType == 0) { if (roundingArc != NaN) makeRectangle(selX - trimL, selY - trimT, selWidth + trimL + trimR, selHeight + trimT + trimB, roundingArc); else makeRectangle(makeRectangle(selX - trimL, selY - trimT, selWidth + trimL + trimR, selHeight + trimT + trimB); } } } else { startX = maxOf(0, round((imageWidth - newSelWidth) / 2)); startY = maxOf(0, round((imageHeight - newSelHeight) / 2)); if (newSelType == 0) { if (roundingArc != NaN) makeRectangle(startX, startY, newSelWidth, newSelHeight, roundingArc); else makeRectangle(startX, startY, newSelWidth, newSelHeight); } else makeOval(startX, startY, newSelWidth, newSelHeight); } if (rotS != 0) run("Rotate...", " angle=&rotS"); if (addOverlay || addROI) { Dialog.create("Overlay/ROI options \(" + macroL + "\)"); grayChoices = newArray("white", "black", "off-white", "off-black", "lightGray", "gray", "darkGray"); colorChoicesStd = newArray("red", "green", "blue", "cyan", "magenta", "yellow", "pink", "orange", "violet"); colorChoicesMaterials = newArray("bronze", "antique_bronze", "brass", "dull_brass", "brick", "chrome", "copper", "aged_copper", "dusky_copper", "light_copper", "garnet", "burnished_gold", "gold", "slate_gray", "titanium", "vault_garnet", "plaza_brick", "vault_gold"); colorChoicesMod = newArray("aqua_modern", "blue_accent_modern", "blue_dark_modern", "blue_modern", "blue_honolulu", "gray_modern", "green_dark_modern", "green_modern", "green_modern_accent", "green_spring_accent", "orange_modern", "pink_modern", "purple_modern", "red_modern", "tan_modern", "violet_modern", "yellow_modern"); colorChoicesNeon = newArray("jazzberry_jam", "radical_red", "wild_watermelon", "outrageous_orange", "supernova_orange", "atomic_tangerine", "neon_carrot", "sunglow", "laser_lemon", "electric_lime", "screamin'_green", "magic_mint", "blizzard_blue", "dodger_blue", "shocking_pink", "razzle_dazzle_rose", "hot_magenta"); colorChoicesFSU = newArray("stadium_night", "westcott_water", "legacy_blue"); /* Materials colors moved to Materials set */ colorChoices = Array.concat(grayChoices, colorChoicesStd, colorChoicesMaterials, colorChoicesMod, colorChoicesNeon, colorChoicesFSU); overlayLineWidth = round(call("ij.Prefs.get", prefsNameKey + "overlayLineWidth", "1")); Dialog.addNumber("Overlay/ROI line width", overlayLineWidth, 0, 4, "pixels"); overlayLineColor = call("ij.Prefs.get", prefsNameKey + "overlayLineColor", "Yellow"); Dialog.addChoice("Overly/ROI line color", colorChoices, overlayLineColor); overlayLineOpacity = call("ij.Prefs.get", prefsNameKey + "overlayLineOpacity", 100); Dialog.addSlider("Overlay/ROI line opacity", 0, 100, overlayLineOpacity); overlayFillColor = call("ij.Prefs.get", prefsNameKey + "overlayFillColor", "Yellow"); Dialog.addChoice("Overlay/ROI fill color", colorChoices, overlayFillColor); overlayFillOpacity = call("ij.Prefs.get", prefsNameKey + "overlayFillOpacity", 100); Dialog.addSlider("Overlay/ROI fill opacity", 0, 100, overlayFillOpacity); overlayGroup = round(call("ij.Prefs.get", prefsNameKey + "overlayGroup", 0)); Dialog.addNumber("Overlay/ROI group", overlayGroup, 0, 4, ""); Dialog.show; overlayLineWidth = Dialog.getNumber(); call("ij.Prefs.set", prefsNameKey + "overlayLineWidth", overlayLineWidth); overlayLineColor = Dialog.getChoice(); call("ij.Prefs.set", prefsNameKey + "overlayLineColor", overlayLineColor); overlayLineOpacity = Dialog.getNumber(); call("ij.Prefs.set", prefsNameKey + "overlayLineOpacity", overlayLineOpacity); overlayFillColor = Dialog.getChoice(); call("ij.Prefs.set", prefsNameKey + "overlayFillColor", overlayFillColor); overlayFillOpacity = Dialog.getNumber(); call("ij.Prefs.set", prefsNameKey + "overlayFillOpacity", overlayFillOpacity); overlayGroup = Dialog.getNumber(); overlayLineColorHex = getHexColorFromColorName(overlayLineColor); if (overlayLineOpacity < 100) { alpha = "#" + String.pad(toHex(255 * overlayLineOpacity / 100), 2); overlayLineColorHex = replace(overlayLineColorHex, "#", alpha); } overlayFillColorHex = getHexColorFromColorName(overlayFillColor); if (overlayFillOpacity < 100) { alpha = "#" + String.pad(toHex(255 * overlayFillOpacity / 100), 2); overlayFillColorHex = replace(overlayFillColorHex, "#", alpha); } Roi.setGroup(overlayGroup); if (selName == "Auto-generated_name") selName += " " + "col: " + overlayLineColor + " fill: = " + overlayFillColor; if (selName != "") Roi.setName(selName); if (addOverlay) { Overlay.addSelection(overlayLineColorHex, overlayLineWidth, overlayFillColorHex); Overlay.show; } if (addROI) { roiManager("Add"); // roiManager("select", roiManager("size")-1); // Roi.setStrokeColor(replace(overlayLineColorHex, "#", "")); // Roi.setStrokeWidth(overlayLineWidth); // Roi.setFillColor(replace(overlayFillColorHex, "#", "")); } } if (saveSelection) { selectionPath = getDirectory("image"); if (!File.isDirectory(selectionPath)) selectionPath = getDirectory("Choose a Directory to save the selection information in"); name = getInfo("image.filename"); if (name != 0) fileName = stripKnownExtensionFromString(name); else fileName = File.nameWithoutExtension; if (name != 0) fileName = stripKnownExtensionFromString(getTitle); if (name != 0) name = "Selection-" + getDateTimeCode(); selectionPath += fileName + "_selection.roi"; saveAs("selection", selectionPath); } else selectionPath = "None"; /* required for prefs */ setSelectionsParasSt = "macroName|aspectR|fractW|fractH|startFractX|startFractY|iDefDimsF|newH|newW|selAR|selTypeName|newSelType|orientation|startX|startY|trimR|trimL|trimT|trimB|rotS|selName|addOverlay|addROI|saveSelection|cropSelection|dupSelection|selectionPath"; /* string of parameters separated by | delimiter - make sure first entry is NOT a number to avoid NaN errors */ setSelectionValues = newArray(macroL, aspectR, fractW, fractH, startFractX, startFractY, iDefDimsF, newH, newW, selAR, selTypeName, newSelType, orientation, startX, startY, trimR, trimL, trimT, trimB, rotS, selName, addOverlay, addROI, saveSelection, cropSelection, dupSelection, selectionPath); /* array of corresponding to parameter list (in the same order) */ setSelectionValuesSt = arrayToString(setSelectionValues, "|"); /* Create string of values from values array */ call("ij.Prefs.set", prefsParaKey, setSelectionsParasSt); // print(setSelectionsParasSt); call("ij.Prefs.set", prefsValKey, setSelectionValuesSt); // print(setSelectionValuesSt); getSelectionBounds(selX, selY, selWidth, selHeight); showStatus("X1: " + selX + ", Y1: " + selY + ", W: " + selWidth + ", H: " + selHeight + " selected"); if (dupSelection && selectionType() >= 0) { cropTitle = "" + stripKnownExtensionFromString(getTitle()) + "_crop"; run("Duplicate...", "title=&cropTitle duplicate"); } if (cropSelection && selectionType() >= 0) { rename("" + stripKnownExtensionFromString(getTitle()) + "_crop"); run("Crop"); /* assume that if any umber is put in then at least 1 pixel buffer is desired */ } updateDisplay(); call("java.lang.System.gc"); /* End of Set Selection or Trim macro */ } macro "Threshold by Selection or ROIs [F8]" { /* Applies auto-thresholds to using selected ares for sampling of to ROIs individually. v230822: 1st version, based on Threshold_Each_ROI_Individually_v230821.ijm Peter J. Lee, Florida State University v230824-5: Added a few more starting options. v230828: Replaced composite selection option with band selection. v230903: Fixed missing parenthesis and combined processing loop. v230904: Added center-circle option. Fixed 'newTItle' typo. v230908: Fixed function getTitleWOKnownExtension use. v231017-8: Attempts to fix some histogram plot issues. */ macroL = "Threshold_by_selection_or_ROIs_v231018.ijm"; oTitle = getTitle(); if (oTitle.length <= 5) { rename(getString("Enter full name of image:", oTitle)); oTitle = getTitle(); if (oTitle.length <= 5) exit("Try something longer than 5 characters"); } newTitle = "" + getTitleWOKnownExtension() + "_Thresh"; maxInt = 255; leq = fromCharCode(0x2264); geq = fromCharCode(0x2265); plusminus = fromCharCode(0x00B1); getPixelSize(unit, pixelWidth, pixelHeight); lcf = (pixelWidth + pixelHeight) / 2; dup = false; selType = selectionType(); if ((selType >= 0 && selType < 5) || selType == 9) selArea = true; else selArea = false; if (bitDepth == 24) { if (is("grayscale")) { newTitle += "_Gray"; run("Select None"); run("Duplicate...", newTitle); dup = true; run("8-bit"); if (selArea) { run("Restore Selection"); selectWindow(oTitle); run("Restore Selection"); selectWindow(newTitle); } } else { Dialog.create("Color threshold options: " + macroL); colorOptions = newArray("Channel threshold: red", "Channel threshold: green", "Channel threshold: blue", "Launch color thresholder"); Dialog.addRadioButtonGroup("This macro is designed for 8 and 16 bit images", colorOptions, 5, 1, colorOptions[0]); Dialog.show(); colorOption = Dialog.getRadioButton(); if (startsWith(colorOption, "Channel")) { cCol = substring(colorOption, lastIndexOf(colorOption, ": ") + 2); newTitle += "_Split"; run("Select None"); preID = getImageID(); run("Duplicate...", newTitle); dup = true; if (getTitle() != newTitle) rename(newTitle); run("Split Channels"); if (cCol != "red") close(newTitle + " \(red*"); if (cCol != "green") close(newTitle + " \(green*"); if (cCol != "blue") close(newTitle + " \(blue*"); newTitle += " \(" + cCol + "\)"; if (selArea) { selectWindow(newTitle); run("Restore Selection"); selectImage(preID); run("Restore Selection"); selectWindow(newTitle); } } else if (startsWith(colorOption, "Launch")) { run("Record..."); /* You may want to record the color threshold action */ run("Color Threshold..."); exit; } } } bDepth = bitDepth; if (bDepth != 16 && bDepth != 8) exit("This macro has not been tested for bit depth: " + bDepth); if (bDepth == 16) maxInt = 65535; nROIs = roiManager("count"); rangeOptions = newArray("Base threshold on entire image", "Auto-select bounding box and base threshold on this area", "Base threshold on center circle \(diam. 30% of image height\)"); if (selArea) rangeOptions = Array.concat("Auto-threshold all based on selection \(current\) *", rangeOptions); else rangeOptions = Array.concat("Select new area to base auto-threshold on \(new requester\)", rangeOptions); if (nROIs > 1) rangeOptions = Array.concat("Threshold each ROI individually", rangeOptions); else if (nROIs == 1) { rangeOptions = Array.concat(rangeOptions, "Try to split ROI for individual threshold", "Base auto-threshold on ROI"); if (!selArea) roiManager("Select", 0); roiManager("Show All"); roiManager("Select", 0); } infoColor = "#0076B6"; infoWarningColor = "#ff69b4"; infoFontSize = 12; Dialog.create("Threshold selection range options: " + macroL); if (!dup) { Dialog.addMessage("Original image title: " + oTitle, infoFontSize - 2, infoColor); Dialog.addString("New image name:", newTitle, minOf(50, newTitle.length + 5)); } Dialog.setInsets(0, 50, -5); Dialog.addRadioButtonGroup("Threshold range options:", rangeOptions, rangeOptions.length, 1, rangeOptions[0]); if (selArea) { Dialog.setInsets(0, 60, 15); Dialog.addMessage("* = Selected area can be modified later \(reapply 'Auto' in 'Threshold' window\)", infoFontSize, infoColor); } Dialog.addNumber("Expand/Contract selection for auto-threshold basis", 0, 0, 3, "pixels"); Dialog.addNumber("Create hollow selection band about selection or ROI above: " + plusminus, 0, 0, 5, "pixels \(leave blank for 'no'\)"); Dialog.addMessage("Band creation occurs after any enlargement or contraction", infoFontSize, infoColor); Dialog.show(); if (!dup) newTitle = Dialog.getString(); rangeOption = Dialog.getRadioButton(); expandSel = Dialog.getNumber(); bandN = Dialog.getNumber(); bandByUnit = 2 * bandN * lcf; if (!dup) { if (selType >= 0) run("Select None"); run("Duplicate...", newTitle); if (getTitle() != newTitle) rename(newTitle); if (selType >= 0) run("Restore Selection"); dup = true; } selectWindow(newTitle); if (startsWith(rangeOption, "Select new")) { selectionTool = call("ij.Prefs.get", "asc.thresholdRange.selectionTool", "polygon"); setTool(selectionTool); waitForUser("Select (ideally 2-phase) area to base auto-threshold values on\nUse shift key to sample multiple areas"); selectionTool = call("ij.Prefs.set", "asc.thresholdRange.selectionTool", IJ.getToolName); rangeOption = "Auto-threshold all based on selection"; } else if (startsWith(rangeOption, "Base threshold on entire")) { run("Select None"); rangeOption = "Auto-threshold all based on selection"; } else if (startsWith(rangeOption, "Base threshold on center")) { run("Select None"); iH = Image.height; iW = Image.width; cX = iW / 2 - 0.15 * iH; cY = 0.35 * iH; makeOval(cX, cY, 0.3 * iH, 0.3 * iH); rangeOption = "Auto-threshold all based on selection"; } else if (startsWith(rangeOption, "Auto-select")) { run("Select Bounding Box (guess background color)"); rangeOption = "Auto-threshold all based on selection"; } else if (rangeOption == "Base auto-threshold on ROI") { roiManager("select", 0); rangeOption = "Auto-threshold all based on selection"; } else if (startsWith(rangeOption, "Try to split ROI")) { roiManager("Select", 0); compoName = Roi.getName; roiManager("Split"); nROIs = roiManager("count"); if (nROIs == 1) rangeOption = "Auto-threshold all based on selection"; } selectWindow(newTitle); if (startsWith(rangeOption, "Auto-threshold all based on selection")) { if (expandSel != 0) run("Enlarge...", "enlarge=&expandSel pixel"); if (bandN != 0) run("Make Band...", "band=" + bandByUnit); run("Threshold..."); exit; } splitCompROIsFlag = false; if (nROIs > 0) { selectedROIs = newArray(); if (nROIs == 1) { splitCompo = getBoolean("There is only one ROI", "Attempt to split composite ROIs into individual ROIs", "Exit"); if (!splitCompo) exit; else { for (i = 0; i < nROIs; i++) { roiManager("Select", i); if (Roi.getName != compoName) selectedROIs = Array.concat(i); } splitCompROIsFlag = true; } } else { selectedROIs = Array.getSequence(nROIs); for (i = 0; i < nROIs; i++) { roiManager("Select", i); selectedROINames = Array.concat(Roi.getName); } } roiManager("Select", selectedROIs); roiManager("Combine"); selTypeB = selectionType(); if (bandN > 0 && ((selTypeB >= 0 && selTypeB < 5) || selTypeB == 9)) { bandByUnit = 2 * bandN * lcf; run("Enlarge...", "enlarge=" + 0 - bandN + " pixel"); run("Make Band...", "band=" + bandByUnit); } selectWindow(newTitle); getStatistics(areaAll, meanAll, minAll, maxAll, stdAll, histogramAll); setAutoThreshold("Default"); getThreshold(lowerThresh, upperThresh); autoThreshMessage = "Default auto-threshold range for ROIs: " + lowerThresh + " - " + upperThresh; resetThreshold; } else { selTypeB = selectionType(); if (bandN > 0 && ((selTypeB >= 0 && selTypeB < 5) || selTypeB == 9)) { bandByUnit = 2 * bandN * lcf; run("Enlarge...", "enlarge=" + 0 - bandN + " pixel"); run("Make Band...", "band=" + bandByUnit); } } if (bitDepth == 16) multInt = maxAll / 256; else multInt = 1; maxLocs = Array.findMaxima(histogramAll, 500); if (maxLocs.length == 0) maxMessage = " No histogram maxima found"; else { maxMessage = "\(sorted with descending strength\) "; for (i = 0; i < maxLocs.length; i++) maxMessage += d2s(multInt * maxLocs[i], 0) + " "; } xValues = Array.getSequence(256); minLocs = Array.findMinima(histogramAll, 500); if (minLocs.length == 0) minMessage = " No histogram minima found"; else { minMessage = ""; for (i = 0; i < minLocs.length; i++) minMessage += d2s(multInt * minLocs[i], 0) + " "; if (multInt != 1) for (i = 0; i < 255; i++) xValues[i] *= multInt; } if (maxLocs.length > 0) { histogramAll = arrayTrimMaxEnds(maxLocs, histogramAll, 0, 255); xValues = arrayTrimMaxEnds(maxLocs, xValues, 0, 255); } if (expandSel != 0) previewExpand = abs(expandSel); else previewExpand = 10; run("Enlarge...", "enlarge=" + previewExpand + " pixel"); getStatistics(areaOutside, meanOutside, minOutside, maxOutside, stdOutside, histogramOutside); run("Select None"); xLabel = "Intensities"; if (xValues.length != 256) xLabel += " \(range end peaks ignored\)"; maxOutsideLocs = Array.findMaxima(histogramOutside, 500); if (maxLocs.length > 0) histogramOutside = arrayTrimMaxEnds(maxLocs, histogramOutside, 0, 255); /* use maxLocs for "all" histogram for the axis legend to match */ for (i = 0; i < histogramOutside.length; i++) histogramOutside[i] -= histogramAll[i]; Plot.create("Histogram of ROIs \(red=ROIs, blue=ROIs + " + previewExpand + " pixel expansion ring\(", xLabel, "Counts", xValues, histogramAll); Plot.setColor("blue"); Plot.add("bar", xValues, histogramOutside); Plot.addLegend("red: ROIs\nblue: ROIs + " + previewExpand + " pixel expansion ring"); Plot.setColor("red"); Plot.setLineWidth(2); Plot.setFrameSize(320, 400); Plot.setFormatFlags("11000100110111"); Plot.show(); if (meanAll < meanOutside) darkBackground = false; else darkBackground = true; setOption("BlackBackground", false); Dialog.create("ROI Threshold options: " + macroL); Dialog.addMessage("Auto-thresholding method will be added as suffix", infoFontSize, infoColor); methods = getList("threshold.methods"); Dialog.addRadioButtonGroup("Choose auto thresholding method", methods, 3, 6, "Default"); Dialog.addNumber("Percentile value", 50, 0, 4, "% \(higher values, few pixels remain\)"); Dialog.addCheckbox("Dark background?", darkBackground); Dialog.addNumber("Threshold region:", expandSel, 0, 6, "pixel expansion around ROI"); Dialog.addMessage("Restrict range for auto-thresholding:________________\n" + autoThreshMessage, 12, infoFontSize); Dialog.setInsets(-5, 30, 0); if (indexOf(rangeOption, "ROI") >= 0) Dialog.addMessage("For ROIs: Mean intensity = " + meanAll + "\nPeak locations:" + maxMessage + "\nValley locations: " + minMessage, infoFontSize, infoColor); Dialog.addNumber("Lower limit \(" + leq + " set to white\)", round(minAll), 0, 8, "minimum shown"); Dialog.addNumber("Upper limit \(" + geq + " set to black\)", round(maxAll), 0, 8, "maximum shown"); Dialog.setInsets(5, 20, 0); Dialog.addCheckbox("PreProcess each area with CLAHE?", false); Dialog.setInsets(5, 5, 0); Dialog.addNumber("CLAHE blocksize", 127, 0, 6, "pixels"); Dialog.setInsets(5, 5, 0); Dialog.addNumber("CLAHE maximum", 3, 2, 6, "slope"); Dialog.setInsets(10, 20, -5); Dialog.addCheckbox("Close all Histograms after running macro", true); if (splitCompROIsFlag) Dialog.addRadioButtonGroup("Composite ROI manager options:", newArray("Leave all ROIs", "Remove original composite ROI", "Remove component ROIs"), 3, 1, "Remove original composite ROI"); diagnosticOptions = newArray("Verbose output to log", "Plot all ROI histograms"); diagnosticChecks = newArray(false, false); Dialog.addCheckboxGroup(1, 2, diagnosticOptions, diagnosticChecks); Dialog.addCheckbox("Batchmode off", false); Dialog.show(); threshCommand = Dialog.getRadioButton(); newTitle2 = newTitle + "_" + threshCommand; percentile = Dialog.getNumber(); darkBackground = Dialog.getCheckbox(); expandSel = Dialog.getNumber(); lowerLimit = Dialog.getNumber; upperLimit = Dialog.getNumber; preCLAHE = Dialog.getCheckbox(); blockSize = Dialog.getNumber(); maxSlope = Dialog.getNumber(); if (Dialog.getCheckbox()) close("Histo*"); if (splitCompROIsFlag) splitOption = Dialog.getRadioButton(); verbose = Dialog.getCheckbox(); plotAll = Dialog.getCheckbox(); batchmodeOff = Dialog.getCheckbox(); if (verbose) IJ.log("Mean intensities: ROIs = " + meanAll + "; Outside of ROIs = " + meanOutside); if (darkBackground) threshCommand += " dark"; threshCommand += " light"; if (preCLAHE) newTitle2 += "RoiClahe-bs" + blockSize + "mS" + maxSlope; if (startsWith(threshCommand, "Percent") && percentile != 50) { if (round(percentile) != percentile) newTitle2 += d2s(percentile, 2); else newTitle2 += d2s(percentile, 0); } if (!batchmodeOff) setBatchMode(true); if (!dup) { selectWindow(oTitle); // roiManager("Deselect"); run("Select None"); run("Duplicate...", "title=" + newTitle2); } else { selectWindow(newTitle); rename(newTitle2); } if (isOpen("Histogram of " + oTitle)) { selectWindow("Histogram of " + oTitle); close(); } if (bandN > 0) bandByUnit = 2 * bandN * lcf; backupFlag = false; if (expandSel != 0 || bandN > 0) { if (!splitCompROIsFlag) { /* Backup original ROIs to composite */ backupPath = getDir("temp") + "BkpRoiSet.zip"; tempROIs = File.exists(backupPath); if (tempROIs) null = File.delete(backupPath); roiManager("Save", getDir("temp") + "BkpRoiSet.zip"); backupFlag = true; } } nSelROIs = selectedROIs.length; selectWindow(newTitle2); run("Scale to Fit"); for (i = 0; i < nSelROIs; i++) { showProgress(i, nSelROIs); showStatus("Processing ROI#" + i + 1 + " out of " + nSelROIs); roiManager("Deselect"); run("Select None"); roiManager("Select", selectedROIs[i]); if (expandSel != 0) run("Enlarge...", "enlarge=&expandSel pixel"); if (bandN > 0) { run("Enlarge...", "enlarge=" + 0 - bandN + " pixel"); run("Make Band...", "band=" + bandByUnit); } roiManager("Update"); roiManager("Deselect"); run("Select None"); roiManager("Select", selectedROIs[i]); Roi.getContainedPoints(xPoints, yPoints); if (plotAll) { getStatistics(null, null, null, null, null, roiHistogram); roiHistogram = Array.trim(roiHistogram, 255); roiHistogram = Array.deleteIndex(roiHistogram, 0); eachROIHistTitle = "Histogram of each ROI_" + newTitle; if (i == 0) { Plot.create(eachROIHistTitle, "Intensity", "Counts", roiHistogram); Plot.show() } else if (isOpen(eachROIHistTitle)) { selectWindow(eachROIHistTitle); Plot.add("bar", roiHistogram); Plot.setColor(randomHexColor()); } } selectWindow(newTitle2); nPoints = xPoints.length; ints = newArray; for (j = 0; j < nPoints; j++) ints[j] = getPixel(xPoints[j], yPoints[j]); if (preCLAHE) run("Enhance Local Contrast (CLAHE)", "blocksize=&blockSize histogram=256 maximum=&maxSlope mask=*None*"); if (lowerLimit != round(minAll) || upperLimit != round(maxAll) || (startsWith(threshCommand, "Percent") && percentile != 50)) { for (j = 0; j < nPoints && lowerLimit != round(minAll); j++) if (ints[j] <= lowerLimit) setPixel(xPoints[j], yPoints[j], maxInt); for (j = 0; j < nPoints && upperLimit != round(maxAll); j++) if (ints[j] >= upperLimit) setPixel(xPoints[j], yPoints[j], 0); } if (startsWith(threshCommand, "Percent") && percentile != 50) { rankedIs = Array.rankPositions(ints); iCutOff = round(nPoints * percentile / 100); for (j = 0; j < iCutOff; j++) setPixel(xPoints[rankedIs[j]], yPoints[rankedIs[j]], maxInt); for (j = iCutOff; j < nPoints; j++) setPixel(xPoints[rankedIs[j]], yPoints[rankedIs[j]], 0); } else { run("Make Inverse"); run("Copy"); roiManager("Select", selectedROIs[i]); setAutoThreshold(threshCommand); getThreshold(lowerThresh, upperThresh); if (verbose) IJ.log("ROI#" + selectedROIs[i] + "\t Lower Auto Threshold: " + lowerThresh + "\t Upper Threshold: " + upperThresh); resetThreshold; if (nPoints == 0) exit("There are zero pixels in ROI " + selectedROIs[i]); if (upperThresh == maxInt) { for (j = 0; j < nPoints; j++) { if (ints[j] >= lowerThresh) setPixel(xPoints[j], yPoints[j], maxInt); else setPixel(xPoints[j], yPoints[j], 0); } } else { for (j = 0; j < nPoints; j++) { if (ints[j] <= upperThresh) setPixel(xPoints[j], yPoints[j], maxInt); else setPixel(xPoints[j], yPoints[j], 0); } } run("Paste"); run("Select None"); } } if (!batchmodeOff) setBatchMode("exit and display"); roiManager("Deselect"); run("Select None"); selectWindow(newTitle2); roiManager("Show All with labels"); Dialog.create("Post-processing of threshold copy"); postClearOptions = newArray("Leave Gray", "White outside ROIs", "Black outside ROIs"); if (expandSel != 0) { postClearOptions = Array.concat(postClearOptions, "White outside expanded ROIs", "Black outside expanded ROIs"); defaultAction = "White outside expanded ROIs"; } else defaultAction = "White outside ROIs"; dCols = minOf(4, lengthOf(postClearOptions)); dRows = Math.ceil(lengthOf(postClearOptions) / dCols); Dialog.addRadioButtonGroup("Clear actions", postClearOptions, dRows, dCols, defaultAction); binaryOps = newArray("Convert thresholded image to binary \(ignored if 'Leave Gray' is selected above\)", "Invert binary image \(ignored if 'binary' is selected above\)"); binaryChecks = newArray(true, false); Dialog.addCheckboxGroup(2, 1, binaryOps, binaryChecks); Dialog.show(); clearAction = Dialog.getRadioButton(); binaryConvert = Dialog.getCheckbox(); invertBinary = Dialog.getCheckbox(); selectWindow(newTitle2); if (!endsWith(clearAction, "Gray")) { roiManager("Select", selectedROIs); roiManager("Combine"); run("Make Inverse"); oBG = Color.background; setBackgroundColor(0, 0, 0); if (endsWith(clearAction, "expanded ROIs")) { run("Clear"); if (startsWith(clearAction, "White")) run("Invert"); } else { if (expandSel != 0) { roiManager("Deselect"); roiManager("Delete"); tempROIs = File.exists(backupPath); if (tempROIs) roiManager("Open", backupPath); backupFlag = false; roiManager("Select", selectedROIs); roiManager("Combine"); run("Make Inverse"); } run("Clear"); if (startsWith(clearAction, "White")) run("Invert"); } Color.setBackground(oBG); roiManager("Deselect"); run("Select None"); } else binaryConvert = false; if (binaryConvert) { run("Select None"); run("Convert to Mask"); if (is("Inverting LUT")) run("Invert LUT"); if (binaryConvert) run("Invert"); } if (splitCompROIsFlag) { if (startsWith(splitOption, "Remove original")) roiManager("Select", 0); else if (startsWith(splitOption, "Remove component")) roiManager("Select", selectedROIs); if (!startsWith(splitOption, "Leave")) roiManager("Delete"); } else if (backupFlag) { roiManager("Deselect"); roiManager("Delete"); tempROIs = File.exists(backupPath); if (tempROIs) { roiManager("Open", backupPath); if (tempROIs) null = File.delete(backupPath); } } /* End of "Threshold by Selection or ROI" {*/ } /* ( 8(|) ( 8(|) All ASC Functions @@@@@:-) @@@@@:-) */ function addImageToStack3(stackName, x, y) { /* v231102: simple version with background fill - requires a base image/stack with the name stackName that already has the necessary dimensions to accommodate images are of different sizes v231116: Version 3 adds placement option Allows pasting of selected area. v231127: Removed 'while' to speed things up. */ functionL = "addImageToStack3_v231127"; if (isOpen(stackName)) { run("Copy"); selectWindow(stackName); run("Add Slice"); run("Select All"); run("Clear", "slice"); Image.paste(x, y); if (selectionType == 0) run("Select None"); } else exit("'" + stackName + "' base stack not found \(" + functionL + "\)"); } function AddMCsToResultsTable() { /* Based on "MCentroids.txt" Morphological centroids by thinning assumes white particles: G. Landini https://imagej.net/doku.php?id=plugin:morphology:morphological_operators_for_imagej:start http://www.mecourse.com/landinig/software/software.html Modified to add coordinates to Results Table: Peter J. Lee NHMFL 7/20-29/2016 v180102 Fixed typos and updated functions. v180104 Removed unnecessary changes to settings. v180312 Add minimum and maximum morphological radii. v180602 Add 0.5 pixels to output co-ordinates to match X, Y, XM and YM system for ImageJ results v190802 Updated distance measurement to use more compact pow function. v220707 Uses toWhiteBGBinary instead of binary[-]Check. Use duplicate image to retain color. v220818 Updated for checkForROIs function */ workingTitle = getTitle(); if (!checkForPlugin("morphology_collection")) exit("Exiting: Gabriel Landini's morphology suite is needed to run this function."); toWhiteBGBinary(workingTitle); /* Makes sure image is binary and sets to white background, black objects */ roiOriginalCount = checkForRoiManager(); /* This macro uses ROIs and a Results table that matches in count */ addRadii = getBoolean("Do you also want to add the min and max M-Centroid radii to the Results table?"); batchMode = is("Batch Mode"); /* Store batch status mode before toggling */ if (!batchMode) setBatchMode(true); /* Toggle batch mode on if previously off */ start = getTime(); getPixelSize(unit, pixelWidth, pixelHeight); lcf = (pixelWidth + pixelHeight) / 2; mcImageWidth = getWidth(); mcImageHeight = getHeight(); showStatus("Looping through all " + roiOriginalCount + " objects for morphological centers . . ."); for (i = 0; i < roiOriginalCount; i++) { showProgress(-i, roiManager("count")); selectWindow(workingTitle); roiManager("select", i); if (addRadii) run("Interpolate", "interval=1"); getSelectionCoordinates(xPoints, yPoints); /* place border coordinates in array for radius measurements - Wayne Rasband: http://imagej.1557.x6.nabble.com/List-all-pixel-coordinates-in-ROI-td3705127.html */ Roi.getBounds(Rx, Ry, Rwidth, Rheight); setResult("ROIctr_X\(px\)", i, Rx + Rwidth / 2); setResult("ROIctr_Y\(px\)", i, Ry + Rheight / 2); Roi.getContainedPoints(RPx, RPy); /* This includes holes when ROIs are used, so no hole filling is needed */ newImage("Contained Points", "8-bit black", Rwidth, Rheight, 1); /* Give each sub-image a unique name for debugging purposes */ for (j = 0; j < lengthOf(RPx); j++) setPixel(RPx[j] - Rx, RPy[j] - Ry, 255); selectWindow("Contained Points"); run("BinaryThin2 ", "kernel_a='0 2 2 0 1 1 0 0 2 ' kernel_b='0 0 2 0 1 1 0 2 2 ' rotations='rotate 45' iterations=-1 white"); for (j = 0; j < lengthOf(RPx); j++) { if ((getPixel(RPx[j] - Rx, RPy[j] - Ry)) == 255) { centroidX = RPx[j]; centroidY = RPy[j]; setResult("mc_X\(px\)", i, centroidX + 0.5); /* Add 0.5 pixel to correct pixel coordinates to center of pixel */ setResult("mc_Y\(px\)", i, centroidY + 0.5); setResult("mc_offsetX\(px\)", i, getResult("X", i) / lcf - (centroidX + 0.5)); setResult("mc_offsetY\(px\)", i, getResult("Y", i) / lcf - (centroidY + 0.5)); j = lengthOf(RPx); /* one point and done */ } } closeImageByTitle("Contained Points"); if (addRadii) { /* Now measure min and max radii from M-Centroid */ rMin = Rwidth + Rheight; rMax = 0; for (j = 0; j < (lengthOf(xPoints)); j++) { dist = sqrt(pow(centroidX - xPoints[j], 2) + pow(centroidY - yPoints[j], 2)); if (dist < rMin) { rMin = dist; rMinX = xPoints[j]; rMinY = yPoints[j]; } if (dist > rMax) { rMax = dist; rMaxX = xPoints[j]; rMaxY = yPoints[j]; } } if (rMin == 0) rMin = 0.5; /* Correct for 1 pixel width objects and interpolate error */ setResult("mc_minRadX", i, rMinX + 0.5); /* Add 0.5 pixel to correct pixel coordinates to center of pixel */ setResult("mc_minRadY", i, rMinY + 0.5); setResult("mc_maxRadX", i, rMaxX + 0.5); setResult("mc_maxRadY", i, rMaxY + 0.5); setResult("mc_minRad\(px\)", i, rMin); setResult("mc_maxRad\(px\)", i, rMax); setResult("mc_AR", i, rMax / rMin); if (lcf != 1) { setResult('mc_minRad' + "\(" + unit + "\)", i, rMin * lcf); setResult('mc_maxRad' + "\(" + unit + "\)", i, rMax * lcf); } } } updateResults(); run("Select None"); if (!batchMode) setBatchMode(false); /* Toggle batch mode off */ showStatus("MC Function Finished: " + roiManager("count") + " objects analyzed in " + (getTime() - start) / 1000 + "s."); beep(); wait(400); beep(); wait(800); beep(); call("java.lang.System.gc"); } function arrayToString(array, delimiter) { /* 1st version April 2019 PJL v190722 Modified to handle zero length array v220307 += restored for else line*/ string = ""; for (i = 0; i < array.length; i++) { if (i == 0) string += array[0]; else string += delimiter + array[i]; } return string; } function arrayTrimMaxEnds(maximaLocs, inputArray, lowest, highest) { /* v230821: 1st version Peter J. Lee Applied Superconductivity Center */ if (maximaLocs[0] == lowest || maximaLocs[0] == highest) { if (maximaLocs[0] == lowest) { if (maximaLocs[1] == highest) inputArray = Array.deleteIndex(inputArray, inputArraylength - 1); inputArray = Array.deleteIndex(inputArray, 0); } else { inputArray = Array.deleteIndex(inputArray, inputArray.length - 1); if (maximaLocs[1] == lowest) inputArray = Array.deleteIndex(inputArray, 0); } } return inputArray; } function autoCalculateDecPlacesFromValueOnly(value) { dP = 0; /* default */ valueSci = d2s(value, -1); iExp = indexOf(valueSci, "E"); valueExp = parseInt(substring(valueSci, iExp + 1)); if (valueExp >= 2) dP = 0; if (valueExp < 2) dP = 2 - valueExp; if (valueExp < -5) dP = -1; /* Scientific Notation */ if (valueExp >= 4) dP = -1; /* Scientific Notation */ return dP; } function autoCalculateDecPlaces(dP, min, max, numberOfLabels) { /* v180316 4 input version */ step = (max - min) / numberOfLabels; stepSci = d2s(step, -1); iExp = indexOf(stepSci, "E"); stepExp = parseInt(substring(stepSci, iExp + 1)); if (stepExp < 0) dP = -1 * stepExp + 1; if (stepExp < -7) dP = -1; /* Scientific Notation */ if (stepExp >= 0) dP = 1; if (stepExp >= 2) dP = 0; if (stepExp >= 5) dP = -1; /* Scientific Notation */ return dP; } function autoCalculateDecPlaces3(min, max, intervals) { /* v210428 3 variable version */ step = (max - min) / intervals; stepSci = d2s(step, -1); iExp = indexOf(stepSci, "E"); stepExp = parseInt(substring(stepSci, iExp + 1)); if (stepExp < -7) dP = -1; /* Scientific Notation */ else if (stepExp < 0) dP = -1 * stepExp + 1; else if (stepExp >= 5) dP = -1; /* Scientific Notation */ else if (stepExp >= 2) dP = 0; else if (stepExp >= 0) dP = 1; return dP; } function autoCropGuessBackgroundSafe() { if (is("Batch Mode") == true) setBatchMode(false); /* toggle batch mode off */ run("Auto Crop (guess background color)"); /* not reliable in batch mode */ if (is("Batch Mode") == false) setBatchMode(true); /* toggle batch mode back on */ } function checkForFont(fontName) { /* v190103 first version */ fontExists = false; systemFonts = getFontList(); for (i = 0; i < systemFonts.length; i++) { if (systemFonts[i] == fontName) { fontExists = true; i = systemFonts.length; } } return fontExists; } function checkForPlugin(pluginName) { /* v161102 changed to true-false v180831 some cleanup v210429 Expandable array version v220510 Looks for both class and jar if no extension is given v220818 Mystery issue fixed, no longer requires restoreExit. v240313 Added spaces after commas. */ pluginCheck = false; if (getDirectory("plugins") == "") IJ.log("Failure to find any plugins!"); else { pluginDir = getDirectory("plugins"); if (lastIndexOf(pluginName, ".") == pluginName.length - 1) pluginName = substring(pluginName, 0, pluginName.length - 1); pExts = newArray(".jar", ".class"); knownExt = false; for (j = 0; j < lengthOf(pExts); j++) if (endsWith(pluginName, pExts[j])) knownExt = true; pluginNameO = pluginName; for (j = 0; j < lengthOf(pExts) && !pluginCheck; j++) { if (!knownExt) pluginName = pluginName + pExts[j]; if (File.exists(pluginDir + pluginName)) { pluginCheck = true; showStatus(pluginName + "found in: " + pluginDir); } else { pluginList = getFileList(pluginDir); subFolderList = newArray; for (i = 0, subFolderCount = 0; i < lengthOf(pluginList); i++) { if (endsWith(pluginList[i], "/")) { subFolderList[subFolderCount] = pluginList[i]; subFolderCount++; } } for (i = 0; i < lengthOf(subFolderList); i++) { if (File.exists(pluginDir + subFolderList[i] + "\\" + pluginName)) { pluginCheck = true; showStatus(pluginName + " found in: " + pluginDir + subFolderList[i]); i = lengthOf(subFolderList); } } } } } return pluginCheck; } function checkForPluginNameContains(pluginNamePart) { /* v180831 1st version to check for partial names so avoid versioning problems ... v220722 Uses File.separator and adds .class v230912 This version is case insensitive and does NOT require restoreExit. NOTE: underlines are NOT converted to spaces in names */ pluginCheck = false; pluginNamePart = toLowerCase(pluginNamePart); fS = File.separator; pluginDir = getDirectory("plugins"); if (pluginDir == "") IJ.log("Failure to find any plugins!"); else { pluginFolderList = getFileList(pluginDir); subFolderList = newArray(); pluginList = newArray(); for (l = 0; l < pluginFolderList.length; l++) { if (endsWith(pluginFolderList[l], fS)) subFolderList = Array.concat(subFolderList, pluginFolderList[l]); else if (endsWith(pluginFolderList[l], "/")) subFolderList = Array.concat(subFolderList, pluginFolderList[l]); /* File.separator does not seem to be working here */ else if (endsWith(toLowerCase(pluginFolderList[l]), ".jar") || endsWith(toLowerCase(pluginFolderList[l]), ".class")) pluginList = Array.concat(pluginList, toLowerCase(pluginFolderList[l])); } /* First check root plugin folder */ for (i = 0; i < lengthOf(pluginList) && !pluginCheck; i++) { if (indexOf(pluginList[i], pluginNamePart) >= 0) pluginCheck = true; } /* If not in the root try the subfolders */ if (!pluginCheck) { for (i = 0; i < subFolderList.length && !pluginCheck; i++) { subFolderPluginList = getFileList(pluginDir + subFolderList[i]); for (k = 0; k < subFolderPluginList.length; k++) subFolderPluginList[k] = toLowerCase(subFolderPluginList[k]); for (j = 0; j < subFolderPluginList.length && !pluginCheck; j++) { if (endsWith(subFolderPluginList[j], ".jar") || endsWith(subFolderPluginList[j], ".class")) if (indexOf(subFolderPluginList[j], pluginNamePart) >= 0) pluginCheck = true; } } } } return pluginCheck; } function checkForResults() { /* v220706: More friendly to Results tables not called "Results" v230720: Does not initially open ROI Manager. */ if (isOpen("ROI Manager")) { nROIs = roiManager("count"); if (nROIs == 0) close("ROI Manager"); } else nROIs = 0; tSize = Table.size; if (tSize > 0) oTableTitle = Table.title; nRes = nResults; if (nRes == 0 && tSize > 0) { oTableTitle = Table.title; renameTable = getBoolean("There is no Results table but " + oTableTitle + "has " + tSize + "rows:", "Rename to Results", "No, I will take may chances"); if (renameTable) { Table.rename(oTableTitle, "Results"); nRes = nResults; } } if (getInfo("window.type") != "ResultsTable" && nRes <= 0) { Dialog.create("No Results to Work With"); Dialog.addMessage("This macro requires a Results table to analyze.\n \nThere are " + nRes + " results.\nThere are " + nROIs + " ROIs."); Dialog.addRadioButtonGroup("No Results to Work With:", newArray("Run Analyze-particles to generate table", "Import Results table", "Exit"), 2, 1, "Run Analyze-particles to generate table"); Dialog.show(); actionChoice = Dialog.getRadioButton(); if (actionChoice == "Exit") restoreExit("Goodbye, your previous setting will be restored."); else if (actionChoice == "Run Analyze-particles to generate table") { if (roiManager("count") != 0) { roiManager("deselect") roiManager("delete"); } setOption("BlackBackground", false); run("Analyze Particles..."); /* Let user select settings */ } else { open(File.openDialog("Select a Results Table to import")); Table.rename(Table.title, "Results"); } } } function checkForRoiManager() { /* v161109 adds the return of the updated ROI count and also adds dialog if there are already entries just in case . . v180104 only asks about ROIs if there is a mismatch with the results v190628 adds option to import saved ROI set v210428 include thresholding if necessary and color check v211108 Uses radio-button group. NOTE: Requires ASC restoreExit function, which assumes that saveSettings has been run at the beginning of the macro v220706: Table friendly version v220816: Enforces non-inverted LUT as well as white background and fixes ROI-less analyze. Adds more dialog labeling. v230126: Does not change foreground or background colors. v230130: Cosmetic improvements to dialog. v230720: Does not initially open ROI Manager. v231211: Adds option to measure ROIs. */ functionL = "checkForRoiManager_v231211"; if (isOpen("ROI Manager")) { nROIs = roiManager("count"); if (nROIs == 0) close("ROI Manager"); } else nROIs = 0; nRes = nResults; tSize = Table.size; if (nRes == 0 && tSize > 0) { oTableTitle = Table.title; renameTable = getBoolean("There is no Results table but " + oTableTitle + "has " + tSize + "rows:", "Rename to Results", "No, I will take may chances"); if (renameTable) { Table.rename(oTableTitle, "Results"); nRes = nResults; } } if (nROIs == 0 || nROIs != nRes) { Dialog.create("ROI mismatch options: " + functionL); Dialog.addMessage("This macro requires that all objects have been loaded into the ROI manager.\n \nThere are " + nRes + " results.\nThere are " + nROIs + " ROIs", 12, "#782F40"); mismatchOptions = newArray(); if (nROIs == 0) mismatchOptions = Array.concat(mismatchOptions, "Import a saved ROI list"); else mismatchOptions = Array.concat(mismatchOptions, "Replace the current ROI list with a saved ROI list"); if (nRes == 0) mismatchOptions = Array.concat(mismatchOptions, "Import a Results Table \(csv\) file"); else mismatchOptions = Array.concat(mismatchOptions, "Clear Results Table and import saved csv"); if (nRes == 0 && nROIs > 0) mismatchOptions = Array.concat(mismatchOptions, "Measure all the ROIs"); mismatchOptions = Array.concat(mismatchOptions, "Clear ROI list and Results Table and reanalyze \(overrides above selections\)"); if (!is("binary")) Dialog.addMessage("The active image is not binary, so it may require thresholding before analysis"); mismatchOptions = Array.concat(mismatchOptions, "Get me out of here, I am having second thoughts . . ."); Dialog.addRadioButtonGroup("How would you like to proceed:_____", mismatchOptions, lengthOf(mismatchOptions), 1, mismatchOptions[0]); Dialog.show(); mOption = Dialog.getRadioButton(); if (startsWith(mOption, "Sorry")) restoreExit("Sorry this did not work out for you."); if (startsWith(mOption, "Replace")) { roiPath = File.openDialog("Select ROI set to open"); if (File.exists(roiPath)) { roiManager("reset"); roiManager("Open", roiPath); } } else if (startsWith(mOption, "Measure all")) { roiManager("Deselect"); roiManager("Measure"); nRes = nResults; } else if (startsWith(mOption, "Clear ROI list and Results Table and reanalyze")) { if (!is("binary")) { if (is("grayscale") && bitDepth() > 8) { proceed = getBoolean(functionL + ": Image is grayscale but not 8-bit, convert it to 8-bit?", "Convert for thresholding", "Get me out of here"); if (proceed) run("8-bit"); else restoreExit(functionL + ": Goodbye, perhaps analyze first?"); } if (bitDepth() == 24) { colorThreshold = getBoolean(functionL + ": Active image is RGB, so analysis requires thresholding", "Color Threshold", "Convert to 8-bit and threshold"); if (colorThreshold) run("Color Threshold..."); else run("8-bit"); } if (!is("binary")) { /* Quick-n-dirty threshold if not previously thresholded */ getThreshold(t1, t2); if (t1 == -1) { run("Auto Threshold", "method=Default"); run("Convert to Mask"); if (is("Inverting LUT")) run("Invert LUT"); if (getPixel(0, 0) == 0) run("Invert"); } } } if (is("Inverting LUT")) run("Invert LUT"); /* Make sure black objects on white background for consistency */ cornerPixels = newArray(getPixel(0, 0), getPixel(0, 1), getPixel(1, 0), getPixel(1, 1)); Array.getStatistics(cornerPixels, cornerMin, cornerMax, cornerMean, cornerStdDev); if (cornerMax != cornerMin) restoreExit("Problem with image border: Different pixel intensities at corners"); /* Sometimes the outline procedure will leave a pixel border around the outside - this next step checks for this. i.e. the corner 4 pixels should now be all black, if not, we have a "border issue". */ if (cornerMean == 0) run("Invert"); if (isOpen("ROI Manager")) roiManager("reset"); if (isOpen("Results")) { selectWindow("Results"); run("Close"); } // run("Analyze Particles..."); /* Letting users select settings does not create ROIs ¯\_(?)_/¯ */ run("Analyze Particles...", "display clear include add"); nROIs = roiManager("count"); nRes = nResults; if (nResults != roiManager("count")) restoreExit(functionL + ": Results \(" + nRes + "\) and ROI Manager \(" + nROIs + "\) counts still do not match!"); } else { if (startsWith(mOption, "Import a saved ROI")) { if (isOpen("ROI Manager")) roiManager("reset"); msg = functionL + ": Import ROI set \(zip file\), click \"OK\" to continue to file chooser"; showMessage(msg); pathROI = File.openDialog(functionL + ": Select an ROI file set to import"); roiManager("open", pathROI); } if (startsWith(mOption, "Import a Results")) { if (isOpen("Results")) { selectWindow("Results"); run("Close"); } msg = functionL + ": Import Results Table: Click \"OK\" to continue to file chooser"; showMessage(msg); open(File.openDialog(functionL + ": Select a Results Table to import")); Table.rename(Table.title, "Results"); } } } nROIs = roiManager("count"); if (nROIs == 0) close("ROI Manager"); nRes = nResults; /* Used to check for ROIs:Results mismatch */ if (nROIs == 0 || nROIs != nRes) restoreExit(functionL + ": Goodbye, there are " + nROIs + " ROIs and " + nRes + " results; your previous settings will be restored."); return roiManager("count"); /* Returns the new count of entries */ } function checkForUnits() { /* With CZSEM check Version /* v161108 (adds inches to possible reasons for checking calibration) This version requires these functions: ***** checkForPlugin, setScaleFromCZSemHeader, restoreExit ***** NOTE: restoreExit REQUIRES previous run of saveSettings v180820: Checks for CZ header before offering to use it. v200508: Simplified v200925: Checks also for unit = pixels v230524: Added options for X vs Y scales. v230801: pixelAR limits used instead of actual pixel dimensions. v230808: Allows inches. v240313 Added spaces after commas. */ functionL = "checkForUnits_v230808"; getPixelSize(unit, pixelWidth, pixelHeight); pixAR = pixelWidth / pixelHeight; if (pixAR > 1.001 || pixAR < 0.999 || pixelWidth == 1 || unit == "" || startsWith(unit, "inch") || unit == "pixels") { rescaleChoices = newArray("Define new units for this image", "Make no changes", "Exit this macro"); if (pixelWidth != pixelHeight) rescaleChoices = Array.concat("Set height scale to width scale", "Set width scale to height scale", rescaleChoices); Dialog.create("Suspicious Units: " + functionL); tiff = matches(getInfo("image.filename"), ".*[tT][iI][fF].*"); if (tiff && (checkForPlugin("tiff_tags.jar"))) { tag = call("TIFF_Tags.getTag", getDirectory("image") + getTitle, 34118); if (indexOf(tag, "Image Pixel Size = ") > 0) rescaleChoices = Array.concat("Set Scale from CZSEM header", rescaleChoices); } rescaleDialogLabel = "pixelHeight = " + pixelHeight + ", pixelWidth = " + pixelWidth + ", unit = " + unit + ": what would you like to do?"; if (startsWith(unit, "inch")) { dpi = 1 / pixelWidth; unit = "inches"; rescaleDialogLabel = "dpi = " + dpi + ", " + rescaleDialogLabel; rescaleChoices = Array.concat("Convert dpi to metric", rescaleChoices); } Dialog.addRadioButtonGroup(rescaleDialogLabel, rescaleChoices, rescaleChoices.length, 1, rescaleChoices[0]); Dialog.show(); rescaleChoice = Dialog.getRadioButton; if (rescaleChoice == "Define new units for this image") run("Set Scale..."); else if (startsWith(rescaleChoice, "Convert")) run("Set Scale...", "distance=" + 1 / (25.5 * pixelWidth) + " known=1 pixel=1 unit=mm"); else if (startsWith(rescaleChoice, "Exit this macro")) restoreExit("Goodbye"); else if (startsWith(rescaleChoice, "Set height")) run("Set Scale...", "distance=" + 1 / pixelWidth + " known=1 pixel=1 unit=&unit"); else if (startsWith(rescaleChoice, "Set width")) run("Set Scale...", "distance=" + 1 / pixelHeight + " known=1 pixel=1 unit=&unit"); else if (startsWith(rescaleChoice, "Set Scale from CZSEM")) { setScaleFromCZSemHeader(); getPixelSize(unit, pixelWidth, pixelHeight); if (pixelWidth != pixelHeight || pixelWidth == 1 || unit == "" || unit == "inches") setCZScale = false; if (!setCZScale) { Dialog.create("Still no standard units"); Dialog.addCheckbox("pixelWidth = " + pixelWidth + ": Do you want to define units for this image?", true); Dialog.show(); setScale = Dialog.getCheckbox; if (setScale) run("Set Scale..."); } } } } function cleanLabel(string) { /* ImageJ macro default file encoding (ANSI or UTF-8) varies with platform so non-ASCII characters may vary: hence the need to always use fromCharCode instead of special characters. v180611 added "degreeC". v200604 fromCharCode(0x207B) removed as superscript hyphen not working reliably. v220630 added degrees v220812 Changed Ångström unit code. v231005 Weird Excel characters added, micron unit correction. v240118 Weird EVO characters fixed. v260309 Recursive for spaces and underlines. */ string = replace(string, "\\^2", fromCharCode(178)); /* superscript 2 */ string = replace(string, "\\^3", fromCharCode(179)); /* superscript 3 UTF-16 (decimal) */ string = replace(string, "\\^-" + fromCharCode(185), "-" + fromCharCode(185)); /* superscript -1 */ string = replace(string, "\\^-" + fromCharCode(178), "-" + fromCharCode(178)); /* superscript -2 */ string = replace(string, "\\^-^1", "-" + fromCharCode(185)); /* superscript -1 */ string = replace(string, "\\^-^2", "-" + fromCharCode(178)); /* superscript -2 */ string = replace(string, "\\^-1", "-" + fromCharCode(185)); /* superscript -1 */ string = replace(string, "\\^-2", "-" + fromCharCode(178)); /* superscript -2 */ string = replace(string, "\\^-^1", "-" + fromCharCode(185)); /* superscript -1 */ string = replace(string, "\\^-^2", "-" + fromCharCode(178)); /* superscript -2 */ string = replace(string, "(? 0) string = replace(string, " ", " "); /* Replace double spaces with single spaces */ while(indexOf(string, "_") > 0) string = replace(string, "_", " "); /* Replace underlines with space as thin spaces (fromCharCode(0x2009)) not working reliably */ string = replace(string, "px", "pixels"); /* Expand pixel abbreviation */ string = replace(string, "degreeC", fromCharCode(0x00B0) + "C"); /* Degree symbol for dialog boxes */ // string = replace(string, " " + fromCharCode(0x00B0), fromCharCode(0x2009) + fromCharCode(0x00B0)); /* Replace normal space before degree symbol with thin space */ // string= replace(string, " °", fromCharCode(0x2009) + fromCharCode(0x00B0)); /* Replace normal space before degree symbol with thin space */ string = replace(string, "sigma", fromCharCode(0x03C3)); /* sigma for tight spaces */ string = replace(string, "plusminus", fromCharCode(0x00B1)); /* plus or minus */ string = replace(string, "degrees", fromCharCode(0x00B0)); /* plus or minus */ if (indexOf(string, "mý") > 1) string = substring(string, 0, indexOf(string, "mý") - 1) + getInfo("micrometer.abbreviation") + fromCharCode(178); /* Fixes for weird EVO character issue: */ oddEVOUs = newArray(fromCharCode(181) + "m", getInfo("micrometer.abbreviation"), fromCharCode(0x212B), fromCharCode(0x00B0)); for (i = 0; i < oddEVOUs.length; i++) if ((lastIndexOf(string, " ") + 2) == lastIndexOf(string, oddEVOUs[i])) string = substring(string, 0, lastIndexOf(string, " ") + 1) + substring(string, lastIndexOf(string, " ") + 2, string.length); /* End of EVO specific fix */ string = string.replace("µ", fromCharCode(181)); return string; } function closeImageByTitle(windowTitle) { /* Cannot be used with tables */ /* v181002: reselects original image at end if open v200925: uses "while" instead of if so it can also remove duplicates v230411: checks to see if any images open first. */ if (nImages > 0) { oIID = getImageID(); while (isOpen(windowTitle)) { selectWindow(windowTitle); close(); } if (isOpen(oIID)) selectImage(oIID); } } function closeNonImageByTitle(windowTitle) { /* v200925 uses "while" instead of "if" so that it can also remove duplicates */ while (isOpen(windowTitle)) { selectWindow(windowTitle); run("Close"); } } function closeResultsandClearROIs() { /* Close Results Table and Clear ROIs Peter J. Lee v161110 v190906 closes the ROI manager and unselects as well*/ if (isOpen("Results")) { selectWindow("Results"); run("Close"); } if (isOpen("ROI Manager")) { roiManager("reset"); close("Roi Manager"); } run("Select None"); } function closestValueFromArray(array, value, default) { /* v190912 1st version pjl. v240313 Added spaces after commas. */ closest = default; proximity = abs( default -value); for (i = 0; i < lengthOf(array); i++) { proxI = abs(array[i] - value); if (proxI < proximity) { closest = array[i]; proximity = proxI; } } return closest; } function countOverlaysByName(overlayNameSubstring) { /* v210817 1st version. v240313 Added spaces after commas. */ overlayCount = 0; if (Overlay.size > 0) { initialOverlaySize = Overlay.size; for (i = 0; i < slices; i++) { for (j = 0; j < initialOverlaySize; j++) { setSlice(i + 1); if (j < Overlay.size) { Overlay.activateSelection(j); overlaySelectionName = getInfo("selection.name"); if (indexOf(overlaySelectionName, overlayNameSubstring) >= 0) overlayCount++; } } } if (slices == 1 && channels > 1) { for (i = 0; i < channels; i++) { for (j = 0; j < initialOverlaySize; j++) { setChannel(i + 1); if (j < Overlay.size) { Overlay.activateSelection(j); overlaySelectionName = getInfo("selection.name"); if (indexOf(overlaySelectionName, overlayNameSubstring) >= 0) overlayCount++; } } } } } run("Select None"); return overlayCount; } function createConvolverMatrix(effect, thickness) { /* v230413: 1st version PJL Effects assumed: "Recessed or Raised". v240313 Added spaces after commas. */ matrixText = ""; matrixSize = maxOf(3, (1 + 2 * round(thickness / 10))); matrixLC = matrixSize - 1; matrixCi = matrixSize / 2 - 0.5; mFact = 1 / (matrixSize - 1); for (y = 0, c = 0; y < matrixSize; y++) { for (x = 0; x < matrixSize; x++) { if (x != y) { matrixText += " 0"; if (x == matrixLC) matrixText += "\n"; } else { if (x == matrixCi) matrixText += " 1"; else if (effect == "raised") { /* Otherwise assumed to be 'recessed' */ if (x < matrixCi) matrixText += " -" + mFact; else matrixText += " " + mFact; } else { if (x > matrixCi) matrixText += " -" + mFact; else matrixText += " " + mFact; } } } } matrixText += "\n"; return matrixText; } function createInnerShadowFromMask6(mask, iShadowDrop, iShadowDisp, iShadowBlur, iShadowDarkness) { /* Requires previous run of: imageDepth = bitDepth(); because this version works with different bitDepths v161115 calls four variables: drop, displacement blur and darkness v180627 and calls mask label v230405 resets background color after application. Checks font size before applying small font size tweak */ showStatus("Creating inner shadow for labels . . . "); newImage("inner_shadow", "8-bit white", imageWidth, imageHeight, 1); getSelectionFromMask(mask); orBG = Color.background; Color.setBackground("black"); run("Clear Outside"); getSelectionBounds(selMaskX, selMaskY, selMaskWidth, selMaskHeight); setSelectionLocation(selMaskX - iShadowDisp, selMaskY - iShadowDrop); run("Clear Outside"); getSelectionFromMask(mask); expansion = abs(iShadowDisp) + abs(iShadowDrop) + abs(iShadowBlur); if (expansion > 0) run("Enlarge...", "enlarge=&expansion pixel"); if (iShadowBlur > 0) run("Gaussian Blur...", "sigma=&iShadowBlur"); if (getValue("font.size") <= 12) run("Unsharp Mask...", "radius=0.5 mask=0.2"); /* A tweak to sharpen the effect for small font sizes */ imageCalculator("Max", "inner_shadow", mask); run("Select None"); /* The following are needed for different bit depths */ if (imageDepth == 16 || imageDepth == 32) run(imageDepth + "-bit"); run("Enhance Contrast...", "saturated=0 normalize"); run("Invert"); /* Create an image that can be subtracted - this works better for color than Min */ divider = (100 / abs(iShadowDarkness)); run("Divide...", "value=÷r"); Color.setBackground(orBG); } function createShadowDropFromMask7(mask, oShadowDrop, oShadowDisp, oShadowBlur, oShadowDarkness, oStroke) { /* Requires previous run of: imageDepth = bitDepth(); because this version works with different bitDepths v161115 calls five variables: drop, displacement blur and darkness v180627 adds mask label to variables, v190108 v230418 removed '&'s */ showStatus("Creating drop shadow for labels . . . "); newImage("shadow", "8-bit black", imageWidth, imageHeight, 1); getSelectionFromMask(mask); getSelectionBounds(selMaskX, selMaskY, selMaskWidth, selMaskHeight); setSelectionLocation(selMaskX + oShadowDisp, selMaskY + oShadowDrop); setBackgroundColor(255, 255, 255); if (oStroke > 0) run("Enlarge...", "enlarge=" + oStroke + " pixel"); /* Adjust shadow size so that shadow extends beyond stroke thickness */ run("Clear"); run("Select None"); if (oShadowBlur > 0) { run("Gaussian Blur...", "sigma=" + oShadowBlur); run("Unsharp Mask...", "radius=" + oShadowBlur + " mask=0.4"); /* Make Gaussian shadow edge a little less fuzzy */ } /* Now make sure shadow or glow does not impact outline */ getSelectionFromMask(mask); if (oStroke > 0) run("Enlarge...", "enlarge=&oStroke pixel"); setBackgroundColor(0, 0, 0); run("Clear"); run("Select None"); /* The following are needed for different bit depths */ if (imageDepth == 16 || imageDepth == 32) run(imageDepth + "-bit"); run("Enhance Contrast...", "saturated=0 normalize"); divider = (100 / abs(oShadowDarkness)); run("Divide...", "value=÷r"); } function createShadowDropFromMask7Safe(mask, oShadowDrop, oShadowDisp, oShadowBlur, oShadowDarkness, oStroke) { /* Requires previous run of: imageDepth = bitDepth(); because this version works with different bitDepths v161115 calls five variables: drop, displacement blur and darkness v180627 adds mask label to variables v230405 resets background color after application v230418 removed '&'s */ showStatus("Creating drop shadow for labels . . . "); newImage("shadow", "8-bit black", imageWidth, imageHeight, 1); getSelectionFromMask(mask); getSelectionBounds(selMaskX, selMaskY, selMaskWidth, selMaskHeight); setSelectionLocation(selMaskX + oShadowDisp, selMaskY + oShadowDrop); orBG = Color.background; Color.setBackground("white"); if (oStroke > 0) run("Enlarge...", "enlarge=" + oStroke + " pixel"); /* Adjust shadow size so that shadow extends beyond stroke thickness */ run("Clear"); run("Select None"); if (oShadowBlur > 0) { run("Gaussian Blur...", "sigma=" + oShadowBlur); run("Unsharp Mask...", "radius=" + oShadowBlur + " mask=0.4"); /* Make Gaussian shadow edge a little less fuzzy */ } /* Now make sure shadow or glow does not impact outline */ getSelectionFromMask(mask); if (oStroke > 0) run("Enlarge...", "enlarge=" + oStroke + " pixel"); Color.setBackground("black"); run("Clear"); run("Select None"); /* The following are needed for different bit depths */ if (imageDepth == 16 || imageDepth == 32) run(imageDepth + "-bit"); run("Enhance Contrast...", "saturated=0 normalize"); divider = (100 / abs(oShadowDarkness)); run("Divide...", "value=" + divider); Color.setBackground(orBG); } function expandLabel(str) { /* Expands abbreviations typically used for compact column titles v200604 fromCharCode(0x207B) removed as superscript hyphen not working reliably v211102-v211103 Some more fixes and updated to match latest extended geometries v220808 replaces ° with fromCharCode(0x00B0) v230106 Added a few separation abbreviations v230109 Reorganized to prioritize all standard IJ labels and make more consistent. Also introduced string.replace and string.substring */ requires("1.52t"); /* for string.replace */ /* standard IJ labels */ if (str == "Angle") str = "Ellipse Angle"; else if (str == "AR") str = "Aspect Ratio \(ellipse fit\)"; else if (str == "AR_Box") str = "Aspect Ratio \(bounding rectangle\)"; else if (str == "AR_Feret") str = "Aspect Ratio \(Feret\)"; else if (str == "BX") str = "Bounding Rectangle X Start"; else if (str == "BY") str = "Bounding Rectangle Y Start"; else if (str == "Circ.") str = "Circularity "; else if (str == "Elongation") str = "Elongation \(of bounding rectangle\)"; else if (str == "Feret") str = "Feret's Diameter"; else if (str == "FeretX") str = "Feret X Start"; else if (str == "FeretX2") str = "Feret X End"; else if (str == "FeretY") str = "Feret Y Start"; else if (str == "FeretY2") str = "Feret Y End"; else if (str == "Heigth") str = "Bounding Rectangle Height"; else if (str == "Major") str = "Major Ellipse Axis Length"; else if (str == "Minor") str = "Minor Ellipse Axis Length"; else if (str == "MinFeret") str = "Minimum Feret's Diameter"; else if (str == "MinFeretX") str = "Minimum Feret Start \(x\)"; else if (str == "MinFeretY") str = "Minimum Feret Start \(y\)"; else if (str == "MinFeretX2") str = "Minimum Feret End \(x\)"; else if (str == "MinFeretY2") str = "Minimum Feret End \(y\)"; else if (str == "Perim.") str = "Perimeter "; else if (str == "Round") str = "Roundness \(from area and major ellipse axis\)"; else if (str == "Rnd_Feret") str = "Roundness \(from maximal Feret's diameter\)"; else if (str == "Sqr_Diag_A") str = "Diagonal of Square \(from area\)"; else if (str == "X") str = "Centroid \(x\)"; else if (str == "Y") str = "Centroid \(y\)"; else { /* additional ASC geometries */ str = str.replace(fromCharCode(0x00B0), "degrees"); str = str.replace("0-90_degrees", "0-90" + fromCharCode(0x00B0)); /* An exception to the above*/ str = str.replace("0-90degrees", "0-90" + fromCharCode(0x00B0)); /* An exception to the above*/ str = str.replace("_cAR", "\(Corrected by Aspect Ratio\) "); str = str.replace("AR_", "Aspect Ratio: "); str = str.replace("BoxH", "Bounding Rectangle Height "); str = str.replace("BoxW", "Bounding Rectangle Width "); str = str.replace("Cir_to_El_Tilt", "Circle Tilt \(tilt of curcle to match measured ellipse\) "); str = str.replace(" Crl ", " Curl "); str = str.replace("Compact_Feret", "Compactness \(from Feret axis\) "); str = str.replace("Da_Equiv", "Diameter \(from circle area\) "); str = str.replace("Dp_Equiv", "Diameter \(from circle perimeter\) "); str = str.replace("Dsph_Equiv", "Diameter \(from spherical Feret diameter\) "); str = str.replace("Da", "Diameter \(from circle area\) "); str = str.replace("Dp", "Diameter \(from circle perimeter\) "); str = str.replace("equiv", "Equivalent "); str = str.replace("FeretAngle", "Feret's Angle "); str = str.replace("Feret's Angle 0to90", "Feret's Angle \(0-90" + fromCharCode(0x00B0) + "\)"); /* fixes a precious labelling inconsistency */ str = str.replace("Fbr", "Fiber "); str = str.replace("FiberThAnn", "Fiber Thickness \(from annulus\) "); str = str.replace("FiberLAnn", "Fiber Length (\from annulus\) "); str = str.replace("FiberLR", "Fiber Length R "); str = str.replace("HSFR", "Hexagon Shape Factor Ratio "); str = str.replace("HSF", "Hexagon Shape Factor "); str = str.replace("Hxgn_", "Hexagon: "); str = str.replace("Intfc_D", "Interfacial Density "); str = str.replace("MinSepNNROI", "Minimum Separation NN ROI "); str = str.replace("MinSepROI", "Minimum ROI Separation "); str = str.replace("MinSepThisROI", "Minimum Separation this ROI "); str = str.replace("MinSep", "Minimum Separation "); str = str.replace("NN", "Nearest Neighbor "); str = str.replace("ObjectN", "Object Number "); str = str.replace("Perim.", "Perimeter "); if (indexOf(str, "Perimeter") != indexOf(str, "Perim")) str.replace("Perim", "Perimeter "); str = str.replace("Perimetereter", "Perimeter "); /* just in case above failed */ str = str.replace("Snk", "\(Snake\) "); str = str.replace("Raw Int Den", "Raw Interfacial Density "); str = str.replace("Rndnss", "Roundness "); str = str.replace("Rnd_", "Roundness: "); str = str.replace("Rss1", "/(Russ Formula 1/) "); str = str.replace("Rss1", "/(Russ Formula 2/) "); str = str.replace("Sqr_", "Square: "); str = str.replace("Squarity_AP", "Squarity \(from area and perimeter\) "); str = str.replace("Squarity_AF", "Squarity \(from area and Feret\) "); str = str.replace("Squarity_Ff", "Squarity \(from Feret\) "); str = str.replace(" Th ", " Thickness "); str = str.replace("ThisROI", " this ROI "); str = str.replace("Vol_", "Volume: "); if (str == "Width") str = "Bounding Rectangle Width"; str = str.replace("XM", "Center of Mass \(x\)"); str = str.replace("XY", "Center of Mass \(y\)"); str = str.replace(fromCharCode(0x00C2), ""); /* Remove mystery  */ str = str.replace(fromCharCode(0x2009), " "); } while (indexOf(str, "_") >= 0) str = str.replace("_", " "); while (indexOf(str, " ") >= 0) str = str.replace(" ", " "); while (endsWith(str, " ")) str = str.substring(0, lengthOf(str) - 1); return str; } function extractMag(string) { /* Guesses magnification from string - used for ASC set scale macro. v190213 - 1st version v190214 - "noMatch" unless numeric return v190222 - adds "k" suffix for thousand v190528 - Completely revised to return mag if followed by "x" etc. as well as preceded. */ magChars = ""; firstX = indexOf(string, "x"); if (firstX < 0) firstX = indexOf(string, "X"); if (firstX < 0) firstX = indexOf(string, fromCharCode(0x00D7)); if (firstX < 0) magString = "noMatch"; else { if (firstX < lengthOf(string)) { postString = substring(string, firstX + 1); for (i = 1; i < (lengthOf(postString)); i++) { magChar = substring(postString, 0, 1); if (matches(magChar, ".*[0-9].*") || (magChar == "k" && lengthOf(magChars) > 0)) { magChars += magChar; postString = substring(postString, 1); } else i = lengthOf(string); } if (lengthOf(magChars) == 0) { preString = substring(string, 0, firstX); for (i = 1; i < firstX + 1; i++) { magChar = substring(preString, lengthOf(preString) - 1); if (matches(magChar, ".*[0-9].*") || (magChar == "k" && lengthOf(magChars) == 0)) { magChars = magChar + magChars; preString = substring(preString, 0, lengthOf(preString) - 1); } else i = lengthOf(string); } } } } if (lengthOf(magChars) == 0) magString = "noMatch"; else magString = magChars; return magString; } function extractTIFFHeaderInfoToArray(tag, beginTag, endTag, endBuffer, lfRep, tabRep, nulRep, default) { /* v220801 1st version tag is the string to scan for text. buffer is to expand the clipped range around the known tags. v220803 REQUIRES zapGremlins function v220805 Changed to works from previously imported string so that string can be reused. v221207 Looks for NUL character to define end of header (requires v221207 or later version of zapGremins) and adds separation space as input parameter v260306 Minor tweaks. */ iEndTag = lastIndexOf(tag, endTag); if (iEndTag >= 0) { iEndTag = minOf(endBuffer + iEndTag, tag.length); tag = String.trim(substring(tag, 0, iEndTag)); iStartTag = indexOf(tag, beginTag); if (iStartTag >= 0) { tag = String.trim(substring(tag, maxOf(0, iStartTag))); tag = zapGremlins(tag, lfRep, tabRep, nulRep, true); iEndOfLine = indexOf(tag, nulRep); if (iEndOfLine < 1) { tagEndString = substring(tag, lengthOf(tag) - endBuffer, lengthOf(tag)); iEndOfLine = lengthOf(tag) - endBuffer + indexOf(tagEndString, "\n"); } tag = substring(tag, 0, iEndOfLine); headerArray = split(tag, "\n"); return headerArray; } } else return default; } function getBestAvailableFont(type) { /* v190103 first version Defaults to SansSerif types if "Serif" not specified */ if (type == "Serif") faveFontList = newArray("Merriweather Black", "Alegreya Black", "Times New Roman Bold", "Times Bold", "Calisto MT Bold", "Book Antiqua Bold", "Roboto Slab", "Serif"); else faveFontList = newArray("Open Sans ExtraBold", "Fira Sans ExtraBold", "Arial Black", "Poppins Black", "Montserrat Black", "Lato Black", "Roboto Black", "Tahoma Bold", "Calibri Bold", "Helvetica", "Goldman Sans Black", "Goldman Sans", "SansSerif"); for (i = 0; i < faveFontList.length; i++) { if (checkForFont(faveFontList[i])) { bestFont = faveFontList[i]; i = faveFontList.length; } } return bestFont; } /* Color Functions */ function getColorArrayFromColorName(colorName) { /* v180828 added Fluorescent Colors v181017-8 added off-white and off-black for use in gif transparency and also added safe exit if no color match found v191211 added Cyan v211022 all names lower-case, all spaces to underscores v220225 Added more hash value comments as a reference v220706 restores missing magenta v230130 Added more descriptions and modified order. v230908: Returns "white" array if not match is found and logs issues without exiting. v240123: Removed duplicate entries: Now 53 unique colors. v240709: Added 2024 FSU-Branding Colors. Some reorganization. Now 60 unique colors. v260202: Added 12 (mostly metallic) "Materials" colors. Now 72 unique colors. v260213: red_n_modern becomes red_modern and old red_modern becomes brick; */ functionL = "getColorArrayFromColorName_v260217"; cA = newArray(255, 255, 255); /* defaults to white */ if (colorName == "white") cA = newArray(255, 255, 255); else if (colorName == "black") cA = newArray(0, 0, 0); else if (colorName == "off-white") cA = newArray(245, 245, 245); else if (colorName == "off-black") cA = newArray(10, 10, 10); else if (colorName == "lightGray") cA = newArray(192, 192, 192); else if (colorName == "gray") cA = newArray(127, 127, 127); else if (colorName == "darkGray") cA = newArray(64, 64, 64); else if (colorName == "red") cA = newArray(255, 0, 0); else if (colorName == "green") cA = newArray(0, 255, 0); /* #00FF00 AKA Lime green */ else if (colorName == "blue") cA = newArray(0, 0, 255); else if (colorName == "cyan") cA = newArray(0, 255, 255); else if (colorName == "yellow") cA = newArray(255, 255, 0); else if (colorName == "magenta") cA = newArray(255, 0, 255); /* #FF00FF */ else if (colorName == "pink") cA = newArray(255, 192, 203); else if (colorName == "violet") cA = newArray(127, 0, 255); else if (colorName == "orange") cA = newArray(255, 165, 0); /* Excel Modern + */ else if (colorName == "aqua_modern") cA = newArray(75, 172, 198); /* #4bacc6 AKA "Viking" aqua */ else if (colorName == "blue_accent_modern") cA = newArray(79, 129, 189); /* #4f81bd */ else if (colorName == "blue_dark_modern") cA = newArray(31, 73, 125); /* #1F497D */ else if (colorName == "blue_honolulu") cA = newArray(0, 118, 182); /* Honolulu Blue #006db0 */ else if (colorName == "blue_modern") cA = newArray(58, 93, 174); /* #3a5dae */ else if (colorName == "gray_modern") cA = newArray(83, 86, 90); /* bright gray #53565A */ else if (colorName == "green_dark_modern") cA = newArray(121, 133, 65); /* Wasabi #798541 */ else if (colorName == "green_modern") cA = newArray(155, 187, 89); /* #9bbb59 AKA "Chelsea Cucumber" */ else if (colorName == "green_modern_accent") cA = newArray(214, 228, 187); /* #D6E4BB AKA "Gin" */ else if (colorName == "green_spring_accent") cA = newArray(0, 255, 102); /* #00FF66 AKA "Spring Green" */ else if (colorName == "orange_modern") cA = newArray(247, 150, 70); /* #f79646 tan hide, light orange */ else if (colorName == "pink_modern") cA = newArray(255, 105, 180); /* hot pink #ff69b4 */ else if (colorName == "purple_modern") cA = newArray(128, 100, 162); /* blue-magenta, purple paradise #8064A2 */ else if (colorName == "red_modern") cA = newArray(227, 24, 55); else if (colorName == "tan_modern") cA = newArray(238, 236, 225); else if (colorName == "violet_modern") cA = newArray(76, 65, 132); else if (colorName == "yellow_modern") cA = newArray(247, 238, 69); /* FSU */ else if (colorName == "garnet") cA = newArray(120, 47, 64); /* #782F40 */ else if (colorName == "gold") cA = newArray(206, 184, 136); /* #CEB888 */ else if (colorName == "gulf_sands") cA = newArray(223, 209, 167); /* #DFD1A7 */ else if (colorName == "stadium_night") cA = newArray(16, 24, 32); /* #101820 */ else if (colorName == "westcott_water") cA = newArray(92, 184, 178); /* #5CB8B2 */ else if (colorName == "vault_garnet") cA = newArray(166, 25, 46); /* #A6192E */ else if (colorName == "legacy_blue") cA = newArray(66, 85, 99); /* #425563 */ else if (colorName == "plaza_brick") cA = newArray(66, 85, 99); /* #572932 */ else if (colorName == "vault_gold") cA = newArray(255, 199, 44); /* #FFC72C */ /* Materials */ else if (colorName == "bronze") cA = newArray(205, 127, 50); /* #CD7F32 */ else if (colorName == "antique_bronze") cA = newArray(102, 93, 30); /* #665D1E */ else if (colorName == "brass") cA = newArray(181, 166, 66); /* #B5A642 */ else if (colorName == "brick") cA = newArray(192, 80, 77); else if (colorName == "dull_brass") cA = newArray(142, 124, 80); /* #8E7C50 */ else if (colorName == "burnished_gold") cA = newArray(133, 109, 77); /* #856D4D */ else if (colorName == "chrome") cA = newArray(229, 228, 226); /* #E5E4E2 */ else if (colorName == "copper") cA = newArray(184, 115, 51); /* #B87333 */ else if (colorName == "aged_copper") cA = newArray(110, 58, 7); /* #6E3A07 */ else if (colorName == "dusky_copper") cA = newArray(110, 59, 59); /* #6E3B3B */ else if (colorName == "light_copper") cA = newArray(218, 138, 103); /* #DA8A67 */ else if (colorName == "slate_gray") cA = newArray(112, 128, 144); /* #708090 */ else if (colorName == "titanium") cA = newArray(135, 134, 129); /* #878681 */ /* Fluorescent Colors https://www.w3schools.com/colors/colors_crayola.asp */ else if (colorName == "radical_red") cA = newArray(255, 53, 94); /* #FF355E */ else if (colorName == "jazzberry_jam") cA = newArray(165, 11, 94); else if (colorName == "wild_watermelon") cA = newArray(253, 91, 120); /* #FD5B78 */ else if (colorName == "shocking_pink") cA = newArray(255, 110, 255); /* #FF6EFF Ultra Pink */ else if (colorName == "razzle_dazzle_rose") cA = newArray(238, 52, 210); /* #EE34D2 */ else if (colorName == "hot_magenta") cA = newArray(255, 0, 204); /* #FF00CC AKA Purple Pizzazz */ else if (colorName == "outrageous_orange") cA = newArray(255, 96, 55); /* #FF6037 */ else if (colorName == "supernova_orange") cA = newArray(255, 191, 63); /* FFBF3F Supernova Neon Orange*/ else if (colorName == "sunglow") cA = newArray(255, 204, 51); /* #FFCC33 */ else if (colorName == "neon_carrot") cA = newArray(255, 153, 51); /* #FF9933 */ else if (colorName == "atomic_tangerine") cA = newArray(255, 153, 102); /* #FF9966 */ else if (colorName == "laser_lemon") cA = newArray(255, 255, 102); /* #FFFF66 "Unmellow Yellow" */ else if (colorName == "electric_lime") cA = newArray(204, 255, 0); /* #CCFF00 */ else if (colorName == "screamin'_green") cA = newArray(102, 255, 102); /* #66FF66 */ else if (colorName == "magic_mint") cA = newArray(170, 240, 209); /* #AAF0D1 */ else if (colorName == "blizzard_blue") cA = newArray(80, 191, 230); /* #50BFE6 Malibu */ else if (colorName == "dodger_blue") cA = newArray(9, 159, 255); /* #099FFF Dodger Neon Blue */ else IJ.log(colorName + " not found in " + functionL + ": Color defaulted to white"); return cA; } function setBackgroundFromColorName(colorName) { colorArray = getColorArrayFromColorName(colorName); setBackgroundColor(colorArray[0], colorArray[1], colorArray[2]); } function setColorFromColorName(colorName) { colorArray = getColorArrayFromColorName(colorName); setColor(colorArray[0], colorArray[1], colorArray[2]); } function setForegroundColorFromName(colorName) { colorArray = getColorArrayFromColorName(colorName); setForegroundColor(colorArray[0], colorArray[1], colorArray[2]); } /* Hex conversion below adapted from T.Ferreira, 20010.01 https://imagej.net/doku.php?id=macro:rgbtohex */ function getHexColorFromColorName(colorNameString) { /* v231207: Uses IJ String.pad instead of function: pad */ colorArray = getColorArrayFromColorName(colorNameString); r = toHex(colorArray[0]); g = toHex(colorArray[1]); b = toHex(colorArray[2]); hexName = "#" + "" + String.pad(r, 2) + "" + String.pad(g, 2) + "" + String.pad(b, 2); return hexName; } function getLutsList() { /* v180723 added check for preferred LUTs v210430 expandable array version v211029 added cividis.lut to LUT favorites v220113 added cividis-asc-linearlumin */ defaultLuts = getList("LUTs"); Array.sort(defaultLuts); lutsDir = getDirectory("LUTs"); /* A list of frequently used LUTs for the top of the menu list . . . */ preferredLutsList = newArray("Your favorite LUTS here", "cividis-asc-linearlumin", "cividis", "viridis-linearlumin", "silver-asc", "mpl-viridis", "mpl-plasma", "Glasbey", "Grays"); preferredLuts = newArray; /* Filter preferredLutsList to make sure they are available . . . */ for (i = 0, countL = 0; i < preferredLutsList.length; i++) { for (j = 0; j < defaultLuts.length; j++) { if (preferredLutsList[i] == defaultLuts[j]) { preferredLuts[countL] = preferredLutsList[i]; countL++; j = defaultLuts.length; } } } lutsList = Array.concat(preferredLuts, defaultLuts); return lutsList; /* Required to return new array */ } function hexLutColors() { /* v231207: Uses String.pad instead of function: pad */ getLut(reds, greens, blues); hexColors = newArray(256); for (i = 0; i < 256; i++) { r = toHex(reds[i]); g = toHex(greens[i]); b = toHex(blues[i]); hexColors[i] = "" + String.pad(r, 2) + "" + String.pad(g, 2) + "" + String.pad(b, 2); } return hexColors; } /* End of ASC-BAR Color Functions */ function fancyTextOverImage3(imageName, textMask, fontColor, outlineColor, shadowDrop, shadowDisp, shadowBlur, shadowDarkness, outlineStroke, effect) { /* Place text over image in a way that stands out" Requires: functions: createShadowDropFromMask7Safe requires createConvolverMatrix function v260420 includes image name variables */ selectWindow(textMask); run("Duplicate...", "title=label_mask"); setThreshold(0, 128); setOption("BlackBackground", false); run("Convert to Mask"); /* Create drop shadow if desired */ if (shadowDrop!=0 || shadowDisp!=0) createShadowDropFromMask7Safe("label_mask", shadowDrop, shadowDisp, shadowBlur, shadowDarkness, outlineStroke); /* Apply drop shadow or glow */ if (isOpen("shadow") && (shadowDarkness>0)) imageCalculator("Subtract", workingImage, "shadow"); if (isOpen("shadow") && (shadowDarkness<0)) /* Glow */ imageCalculator("Add", workingImage, "shadow"); run("Select None"); /* Create outline around text */ getSelectionFromMask("label_mask"); getSelectionBounds(maskX, maskY, null, null); outlineStrokeOffset = maxOf(0, (outlineStroke/2)-1); setSelectionLocation(maskX + outlineStrokeOffset, maskY + outlineStrokeOffset); /* Offset selection to create shadow effect */ run("Enlarge...", "enlarge=" + outlineStroke + " pixel"); setBackgroundFromColorName(outlineColor); run("Clear", "slice"); run("Enlarge...", "enlarge=" + outlineStrokeOffset + " pixel"); run("Gaussian Blur...", "sigma=" + outlineStrokeOffset); run("Select None"); /* Create text */ getSelectionFromMask("label_mask"); setBackgroundFromColorName(fontColor); run("Clear", "slice"); run("Select None"); /* Create inner shadow if requested */ if (effect=="raised" || effect=="recessed"){ /* 'raised' and 'recessed' cannot be combined in this macro */ fontLineWidth = getStringWidth("!"); rAlpha = fontLineWidth/40; getSelectionFromMask("label_mask"); if(outlineStroke>0) run("Enlarge...", "enlarge=1 pixel"); run("Convolve...", "text1=[ " + createConvolverMatrix(effect, fontLineWidth) + " ]"); if (rAlpha>0.33) run("Gaussian Blur...", "sigma=" + rAlpha); run("Select None"); } /* The following steps smooth the interior of the text labels */ selectWindow(textMask); getSelectionFromMask("label_mask"); run("Make Inverse"); run("Invert"); run("Select None"); imageCalculator("Min", imageName, textMask); closeImageByTitle("shadow"); closeImageByTitle("inner_shadow"); closeImageByTitle("label_mask"); } function findAppPath(appName, appEx, defaultPath) { /* v210921 1st version: appName is assumed to be the app folder name, appEx is the executable, default is the default return value v211018: assumes specific executable path stored in prefs Prints message rather than exits when app not found v211213: fixed defaultPath error v211214: Adds additional location as packaged within a Fiji/IJ distribution v220121: Changed fS line v230803: Replaced getDir with getDirectory for ImageJ 1.54g9 */ functionL = "findAppPath_v230803"; fS = File.separator; ijPath = getDirectory("imagej"); appsPath = substring(ijPath, 0, lengthOf(ijPath) - 1); appsPath = substring(appsPath, 0, lastIndexOf(appsPath, fS)); appFound = false; prefsName = "asc.external.paths." + toLowerCase(appName) + "." + appEx; appPath = call("ij.Prefs.get", prefsName, defaultPath); appLoc = "" + fS + appName + fS + appEx; cProg = "C:" + fS + "Program Files"; defAppPaths = newArray(cProg + fS + "Utilities" + appLoc, cProg + " \(x86\)" + fS + "Utilities" + appLoc, cProg + appLoc, cProg + " \(x86\)" + appLoc, appsPath + appLoc, ijPath + "Apps" + appLoc); if (!File.exists(appPath)) { for (i = 0; i < lengthOf(defAppPaths); i++) { if (File.exists(defAppPaths[i])) { appPath = defAppPaths[i]; call("ij.Prefs.set", prefsName, appPath); appFound = true; i = lengthOf(defAppPaths); } } } else appFound = true; if (appFound == false) { Dialog.create("Find location of " + appEx + " version: " + functionL); Dialog.addMessage(appEx + " can provide additional functionality to this macro\nbut has not been found in the expected locations"); Dialog.addCheckbox("Get me out of here, I don't want to try and find " + appName + ", whatever that is", false); Dialog.addFile("Locate " + appEx + ":", "C:" + fS + "Program Files"); Dialog.addMessage("If found, the location will be saved in prefs for future use:\n" + prefsName); Dialog.show; if (Dialog.getCheckbox) appFound = false; else { appPath = Dialog.getString(); if (!File.exists(appPath)) IJ.log(appEx + " not found"); else { call("ij.Prefs.set", prefsName, appPath); appFound = true; } } } if (appFound) return appPath; else return defaultPath; } function getDateTimeCode() { /* v211014 based on getDateCode v170823 */ getDateAndTime(year, month, dayOfWeek, dayOfMonth, hour, minute, second, msec); month = month + 1; /* Month starts at zero, presumably to be used in array */ if (month < 10) monthStr = "0" + month; else monthStr = "" + month; if (dayOfMonth < 10) dayOfMonth = "0" + dayOfMonth; dateCodeUS = monthStr + dayOfMonth + substring(year, 2) + "-" + hour + "h" + minute + "m"; return dateCodeUS; } function getDSXExifTagFromMetaData(metaData, tagName) { /* metaData is string generated by metaData = getMetadata("Info"); v230120: 1st version version b */ i0 = indexOf(metaData, "<" + tagName + ">"); if (i0 != -1) { i1 = indexOf(metaData, "", i0); tagLine = substring(metaData, i0, i1); tagValue = substring(tagLine, indexOf(tagLine, ">") + 1, tagLine.length); } else tagValue = "" + tagName + " not found in metaData"; return tagValue; } function getExifData(exifSource) { /* uses exifReader plugin: https://imagej.net/plugins/exif-reader.html The exif reader plugin will not load a new image directly if one is open, it will only use the open image - this is why this version opens a new image separately v230511: 1st versions v230512: More careful about closing. */ if (nImages > 0) { wasOpen = true; orImageId = getImageID(); } else wasOpen = false; open(exifSource); exifImageID = getImageID(); exifTitle = "EXIF Metadata for " + getTitle(); run("Exif Data..."); wait(10); selectWindow(exifTitle); wait(10); metaInfo = getInfo("window.contents"); wait(10); if (wasOpen) { if (orImageId != exifImageID) { selectImage(exifImageID); close(); } } else close(thisTitle); close(exifTitle); return metaInfo; } function getExifDataForSelectedImage() { /* Requires: exifReader plugin: https://imagej.net/plugins/exif-reader.html and waitForOpenWindow function v230809: 1st version */ functionL = "getExifDataForSelectedImage_v230809"; metaInfo = "Not found"; exifImageID = getImageID(); exifTitle = "EXIF Metadata for " + getTitle(); imagePath = getInfo("image.directory") + getInfo("image.filename"); if (imagePath != "") { if (File.exists(imagePath)) { run("Exif Data..."); waitForOpenWindow(exifTitle, 10, 0, maxOf(10, Image.width * Image.height / 1000)); if (isOpen(exifTitle)) { selectWindow(exifTitle); metaInfo = getInfo("window.contents"); close(exifTitle); } } } return metaInfo; } function getFontChoiceList() { /* v180723 first version v180828 Changed order of favorites. v190108 Longer list of favorites. v230209 Minor optimization. v230919 You can add a list of fonts that do not produce good results with the macro. 230921 more exclusions. v240306: Restored SansSerif. */ systemFonts = getFontList(); IJFonts = newArray("SansSerif", "Serif", "Monospaced"); fontNameChoices = Array.concat(IJFonts, systemFonts); blackFonts = Array.filter(fontNameChoices, "([A-Za-z] + .*[bB]l.*k)"); eBFonts = Array.filter(fontNameChoices, "([A-Za-z] + .*[Ee]xtra.*[Bb]old)"); uBFonts = Array.filter(fontNameChoices, "([A-Za-z] + .*[Uu]ltra.*[Bb]old)"); fontNameChoices = Array.concat(blackFonts, eBFonts, uBFonts, fontNameChoices); /* 'Black' and Extra and Extra Bold fonts work best */ faveFontList = newArray("Your favorite fonts here", "Arial Black", "Myriad Pro Black", "Myriad Pro Black Cond", "Noto Sans Blk", "Noto Sans Disp Cond Blk", "Open Sans ExtraBold", "Roboto Black", "Alegreya Black", "Alegreya Sans Black", "Tahoma Bold", "Calibri Bold", "Helvetica", "SansSerif", "Calibri", "Roboto", "Tahoma", "Times New Roman Bold", "Times Bold", "Goldman Sans Black", "Goldman Sans", "SansSerif", "Serif"); /* Some fonts or font families don't work well with ASC macros, typically they do not support all useful symbols, they can be excluded here using the .* regular expression */ offFontList = newArray("Alegreya SC Black", "Archivo.*", "Arial Rounded.*", "Bodon.*", "Cooper.*", "Eras.*", "Fira.*", "Gill Sans.*", "Lato.*", "Libre.*", "Lucida.*", "Merriweather.*", "Montserrat.*", "Nunito.*", "Olympia.*", "Poppins.*", "Rockwell.*", "Tw Cen.*", "Wingdings.*", "ZWAdobe.*"); /* These don't work so well. Use a ".*" to remove families */ faveFontListCheck = newArray(faveFontList.length); for (i = 0, counter = 0; i < faveFontList.length; i++) { for (j = 0; j < fontNameChoices.length; j++) { if (faveFontList[i] == fontNameChoices[j]) { faveFontListCheck[counter] = faveFontList[i]; j = fontNameChoices.length; counter++; } } } faveFontListCheck = Array.trim(faveFontListCheck, counter); for (i = 0; i < fontNameChoices.length; i++) { for (j = 0; j < offFontList.length; j++) { if (fontNameChoices[i] == offFontList[j]) fontNameChoices = Array.deleteIndex(fontNameChoices, i); if (endsWith(offFontList[j], ".*")) { if (startsWith(fontNameChoices[i], substring(offFontList[j], 0, indexOf(offFontList[j], ".*")))) { fontNameChoices = Array.deleteIndex(fontNameChoices, i); i = maxOf(0, i - 1); } // fontNameChoices = Array.filter(fontNameChoices, "(^" + offFontList[j] + ")"); /* RegEx not working and very slow */ } } } fontNameChoices = Array.concat(faveFontListCheck, fontNameChoices); for (i = 0; i < fontNameChoices.length; i++) { for (j = i + 1; j < fontNameChoices.length; j++) if (fontNameChoices[i] == fontNameChoices[j]) fontNameChoices = Array.deleteIndex(fontNameChoices, j); } return fontNameChoices; } function getPrefsFromParallelArrays(refArray, prefArray, ref, default) { /* refArray has a list of parameter names and prefArray has a list of values for those parameters in the same order v190514 1st version v190605 corrected v200207 added array length check in if statement */ iPref = indexOfArray(refArray, ref, -1); if (iPref >= 0 && prefArray.length > iPref) pref = prefArray[iPref]; else pref = default; return pref; } function getResultsTableList(ignoreHistograms) { /* simply returns array of open results tables v200723: 1st version v201207: Removed warning message v230804: Adds boolean ignoreHistograms option v230815: Restore missing bracket */ nonImageWindows = getList("window.titles"); if (nonImageWindows.length > 0) { resultsWindows = newArray(); for (i = 0; i < nonImageWindows.length; i++) { selectWindow(nonImageWindows[i]); if (getInfo("window.type") == "ResultsTable") { if (!ignoreHistograms) resultsWindows = Array.concat(resultsWindows, nonImageWindows[i]); else if (indexOf(nonImageWindows[i], "Histogram") < 0) resultsWindows = Array.concat(resultsWindows, nonImageWindows[i]); } } return resultsWindows; } else return ""; } function getScaleCalibrationFilePath(calFileName) { /* v260305: 1st version */ pathCalibFile = ""; fS2 = fS + fS; netPathCalibFile = "https:" + fS2 + "files.magnet.fsu.edu" + fS + "ASC" + fS + "Metallography" + fS + "Photoshop%20Extended%20Measurement%20Scales" + fS + calFileName; zPathCalibDir = "Z:" + fS + "ASC" + fS + "Metallography" + fS + "Photoshop Extended Measurement Scales" + fS; zPathCalibFile = zPathCalibDir + calFileName; pluginsPath = getDirectory("plugins"); if (checkForPluginNameContains("Fiji_Package_Maker")) { fiji = true; localPathCalibDir = pluginsPath + "Scripts" + fS + "Analyze" + fS + "Settings" + fS; /* Fiji location */ } else { fiji = false; localPathCalibDir = pluginsPath + "ASC_Analyze" + fS + "Settings" + fS; /* ImageJ location */ } if (!File.exists(localPathCalibDir)) File.makeDirectory(localPathCalibDir); if (!File.exists(localPathCalibDir)) exit("Not able to create \n" + localPathCalibDir); localPathCalibFile = localPathCalibDir + calFileName; if (File.exists(localPathCalibFile)) { pathCalibFile = localPathCalibFile; localFileDate = File.dateLastModified(localPathCalibFile); } else if (File.exists(netPathCalibFile)) { File.copy(zPathCalibFile, localPathCalibFile); pathCalibFile = localPathCalibFile; localFileDate = File.dateLastModified(netPathCalibFile); } else if (File.exists(zPathCalibFile)) { File.copy(zPathCalibFile, localPathCalibFile); pathCalibFile = localPathCalibFile; localFileDate = File.dateLastModified(zPathCalibFile); } else exit(calFileName + " not found in any expected location"); if (File.exists(netPathCalibFile)) { pathCalibFile = netPathCalibFile; netFileDate = File.dateLastModified(netPathCalibFile); } if (localFileDate != 0 && netFileDate != 0) { /* Only looks for local net file if the other archives do not exist as this would otherwise cause a delay if no network is found */ if (File.exists(zPathCalibFile)) pathCalibFile = zPathCalibFile; } /* Now check for date inconsistency of both local and net files found */ if (localFileDate != 0 && netFileDate != 0 && pathCalibFile != "") { if (localFileDate != netFileDate) { Dialog.create("Choose Calibration"); Dialog.addMessage("Calibration file dates are not the same.\nWeb calibration:\n" + netPathCalibFile + "\nLast modified:\n" + netFileDate + "\nlocal calibration:\n" + localPathCalibFile + "\nLast modified:\n" + localFileDate); calibrationFiles = newArray("Web", "Local", "Choose", "Cancel"); Dialog.addRadioButtonGroup("Calibration files:", calibrationFiles, 1, 1, calibrationFiles[0]); Dialog.show(); calibrationChoice = Dialog.getRadioButton(); if (calibrationChoice == "Web") pathCalibFile = netPathCalibFile; else if (calibrationChoice == "Local") pathCalibFile = localPathCalibFile; else if (calibrationChoice == "Choose") pathCalibFile = File.openDialog("Choose calibration file in csv format: label, pixels, distance, units"); } } return pathCalibFile; } function getScaleFactor(inputUnit) { /* v220126 added micrometer symbol v220809 further tweaked handling of microns v230918 Removed restoreExit, scaleFactor returned even if -1. */ functionL = "getScaleFactor_v230918"; micronS = getInfo("micrometer.abbreviation"); micronSs = newArray("µm", "um", fromCharCode(181) + "m", "microns"); scaleFactor = -1; for (i = 0; i < micronSs.length; i++) if (inputUnit == micronSs[i]) inputUnit = micronS; kUnits = newArray("km", "m", "mm", micronS, "nm", "pm"); sFTemp = 1E3; for (i = 0; i < kUnits.length; i++) { if (inputUnit == kUnits[i]) { scaleFactor = sFTemp; i = kUnits.length; } else sFTemp /= 1E3; } if (scaleFactor < 0) { oddUnits = newArray("cm", "A", fromCharCode(197), "inches", "human hair", "pixels"); oddUnits = newArray(1E-2, 1E-10, 1E-10, 2.54E-2, 1E-4, 0); for (i = 0; i < oddUnits.length; i++) { if (inputUnit == oddUnits[i]) { scaleFactor = oddUnits[i]; i = oddUnits.length; } } } if (scaleFactor < 0) IJ.log(inputUnit + " not a recognized unit \(function: " + functionL + "\)"); return scaleFactor; } function getSelectionFromMask(sel_M) { /* v220920 only inverts if full image selection */ batchMode = is("Batch Mode"); /* Store batch status mode before toggling */ if (!batchMode) setBatchMode(true); /* Toggle batch mode on if previously off */ tempID = getImageID(); selectWindow(sel_M); run("Create Selection"); /* Selection inverted perhaps because the mask has an inverted LUT? */ getSelectionBounds(gSelX, gSelY, gWidth, gHeight); if (gSelX == 0 && gSelY == 0 && gWidth == Image.width && gHeight == Image.height) run("Make Inverse"); run("Select None"); selectImage(tempID); run("Restore Selection"); if (!batchMode) setBatchMode(false); /* Return to original batch mode setting */ } function getTitleWOKnownExtension() { /* v230904: 1st version PJL Applied Superconductivity Center v230908: Simplified to replace only */ newTitle = getTitle(); lcExtns = newArray(".avi", ".csv", ".dsx", ".gif", ".jpeg", ".jpg", ".jp2", "_lzw.", ".ols", ".png", ".tiff", ".tif", ".txt", ".vsi", ".xlsx", ".xls"); /* Note: Always list 4 character extensions before 3 character extensions */ nExtns = lcExtns.length; for (i = 0; i < nExtns && lastIndexOf(newTitle, ".") > 0; i++) { newTitle = replace(newTitle, lcExtns[i], ""); newTitle = replace(newTitle, toUpperCase(lcExtns[i]), ""); } return newTitle; } function getVueScanExifTagFromMetaData(metaData, tagName) { /* metaData is string generated by metaData = getMetadata("Info"); v230120: 1st version */ i0 = indexOf(metaData, "] " + tagName + ":"); if (i0 != -1) { i1 = indexOf(metaData, "\n", i0); tagLine = substring(metaData, i0, i1); tagValue = substring(tagLine, indexOf(tagLine, ":") + 1, tagLine.length); } else tagValue = "" + tagName + " not found in metaData"; return tagValue; } function guessBGMedianIntensity() { /* v220822: 1st color array version (based on https://wsr.imagej.net//macros/tools/ColorPickerTool.txt) v230728: Uses selected area if there is a non-line selection. */ if (selectionType < 0 || selectionType > 4) { sW = Image.width - 1; sH = Image.height - 1; sX = 0; sY = 0; } else { getSelectionBounds(sX, sY, sW, sH); sW += sX; sH += sY; } interrogate = round(maxOf(1, (sW + sH) / 200)); if (bitDepth == 24) { red = 0; green = 0; blue = 0; } else int = 0; xC = newArray(sX, sW, sX, sW); yC = newArray(sY, sY, sH, sH); xAdd = newArray(1, -1, 1, -1); yAdd = newArray(1, 1, -1, -1); if (bitDepth == 24) { reds = newArray(); greens = newArray(); blues = newArray(); } else ints = newArray; for (i = 0; i < xC.length; i++) { for (j = 0; j < interrogate; j++) { if (bitDepth == 24) { v = getPixel(xC[i] + j * xAdd[i], yC[i] + j * yAdd[i]); reds = Array.concat(reds, (v >> 16) & 0xff); /* extract red byte (bits 23-17) */ greens = Array.concat(greens, (v >> 8) & 0xff); /* extract green byte (bits 15-8) */ blues = Array.concat(blues, v & 0xff); /* extract blue byte (bits 7-0) */ } else ints = Array.concat(ints, getValue(xC[i] + j * xAdd[i], yC[i] + j * yAdd[i])); } } midV = round((xC.length - 1) / 2); if (bitDepth == 24) { reds = Array.sort(reds); greens = Array.sort(greens); blues = Array.sort(blues); medianVals = newArray(reds[midV], greens[midV], blues[midV]); } else { ints = Array.sort(ints); medianVals = newArray(ints[midV], ints[midV], ints[midV]); } return medianVals; } function indexOfArray(array, value, default) { /* v190423 Adds "default" parameter (use -1 for backwards compatibility). Returns only first found value v230902 Limits default value to array size */ index = minOf(lengthOf(array) - 1, default); for (i = 0; i < lengthOf(array); i++) { if (array[i] == value) { index = i; i = parseFloat("Infinity"); } } return index; } function indexOfArrayThatContains(array, value, default) { /* Like indexOfArray but partial matches possible v190423 Only first match returned, v220801 adds default. v230902 Limits default value to array size */ indexFound = minOf(lengthOf(array) - 1, default); for (i = 0; i < lengthOf(array); i++) { if (indexOf(array[i], value) >= 0) { indexFound = i; i = parseFloat("Infinity"); } } return indexFound; } function indexOfArrayThatStartsWith(array, value, default) { /* Like indexOfArray but partial matches possible v220804 1st version v230902 Limits default value to array size */ indexFound = minOf(lengthOf(array) - 1, default); for (i = 0; i < lengthOf(array); i++) { if (indexOf(array[i], value) == 0) { indexFound = i; i = parseFloat("Infinity"); } } return indexFound; } function lnArray(arrayName) { /* 1st version: v180318 */ outputArray = Array.copy(arrayName); for (i = 0; i < lengthOf(arrayName); i++) outputArray[i] = log(arrayName[i]); return outputArray; } function memFlush(waitTime) { run("Reset...", "reset=[Undo Buffer]"); wait(waitTime); run("Reset...", "reset=[Locked Image]"); wait(waitTime); call("java.lang.System.gc"); /* force a garbage collection */ wait(waitTime); } function padIN(n, paddedStringLength) { while (lengthOf(n) < paddedStringLength && lengthOf("" + n) < paddedStringLength) n = "0" + n; return n; } function pathLengthCheck(path, maxLength) { /* v230504: 1st version */ pathLength = lengthOf(path); if (pathLength > maxLength) { pathLengthMessage = "Path length is " + pathLength - maxLength + " characters longer than chosen maximum \(" + maxLength + "\), try shortening the path:"; fS = File.separator; dirPathEnd = lastIndexOf(path, fS) + 1; Dialog.create("Shorten path length"); Dialog.addMessage(pathLengthMessage); if (dirPathEnd > 0) { dirPath = substring(path, 0, dirPathEnd); fileName = substring(path, dirPathEnd); Dialog.addDirectory("Directory path:", dirPath); } else fileName = path; Dialog.addString("File name:", fileName, fileName.length); Dialog.show(); if (dirPathEnd > 0) path = Dialog.getString() + Dialog.getString(); else path = Dialog.getString(); } return path; } function pathOverwriteCheck(path) { /* v220615: 1st version reworked 061622 v230811: Fixed to avoid unnecessary loop v240118: Fixed path not being updated */ if (File.exists(path)) saveFlag = false; else saveFlag = true; while (saveFlag == false) { newPath = getString("Overwite existing file \(leave\) or rename:", path); if (newPath == path || !File.exists(newPath)) saveFlag = true; path = newPath; } return path; } function randomHexColor() { /* v231207: Replaced function: pad*/ r = toHex(255 * random); g = toHex(255 * random); b = toHex(255 * random); hexName = "#" + "" + String.pad(r, 2) + "" + String.pad(g, 2) + "" + String.pad(b, 2); return hexName; } function rangeFinder(dataExtreme, max) { /* For finding good end values for ramps and plot ranges. v230824: 1st version Peter J. Lee Applied Superconductivity Center FSU */ rangeExtremeStr = d2s(dataExtreme, -2); if (max) rangeExtremeA = Math.ceil(10 * parseFloat(substring(rangeExtremeStr, 0, indexOf(rangeExtremeStr, "E")))) / 10; else rangeExtremeA = Math.floor(10 * parseFloat(substring(rangeExtremeStr, 0, indexOf(rangeExtremeStr, "E")))) / 10; rangeExtremeStrB = substring(rangeExtremeStr, indexOf(rangeExtremeStr, "E") + 1); rangeExtreme = parseFloat(rangeExtremeA + "E" + rangeExtremeStrB); return rangeExtreme; } function removeBlackEdgeObjects() { /* Remove black edge objects without using Analyze Particles Peter J. Lee National High Magnetic Field Laboratory 1st version v190604 v200102 Removed unnecessary print command. v230106 Does not require any plugins or other functions, uses Functions for working with colors available in ImageJ 1.53h and later */ requires("1.53h"); originalFGCol = Color.foreground; Color.setForeground("white"); cWidth = getWidth() + 2; cHeight = getHeight() + 2; run("Canvas Size...", "width=" + cWidth + " height=" + cHeight + " position=Center zero"); floodFill(0, 0); makeRectangle(1, 1, cWidth - 2, cHeight - 2); run("Crop"); showStatus("Remove_Edge_Objects function complete"); Color.setForeground(originalFGCol); } function removeDuplicatesInArray(array, sortFlag) { /* v230822-3: 1st version. Peter J. Lee FSU */ if (lengthOf(array) <= 1) return array; else { if (!sortFlag) arrayOrder = Array.rankPositions(array); sortedArray = Array.sort(array); for (i = 0; i < lengthOf(sortedArray) - 1; i++) { if (sortedArray[i] == sortedArray[i + 1]) { sortedArray = Array.deleteIndex(sortedArray, i); if (!sortFlag) arrayOrder = Array.deleteIndex(arrayOrder, i); i -= 1; } } if (!sortFlag) Array.sort(arrayOrder, sortedArray); return sortedArray; } } function removeOverlaysByName(overlayNameSubstring) { /* Some overlays seem hard to remove . . . this tries really hard! Using "" as substring will remove all overlays v210817 1st version as function v210826 self-contained */ getDimensions(null, null, channels, slices, frames); if (Overlay.size > 0) { initialOverlaySize = Overlay.size; for (i = 0; i < slices; i++) { setSlice(i + 1); ovl = 0; for (j = 0; j < initialOverlaySize; j++) { if (j < Overlay.size + 1) { Overlay.activateSelection(ovl); overlaySelectionName = getInfo("selection.name"); if (indexOf(overlaySelectionName, overlayNameSubstring) >= 0) { Overlay.removeSelection(ovl); run("Select None"); if (ovl < Overlay.size) { Overlay.activateSelection(ovl); overlaySelectionName = getInfo("selection.name"); if (indexOf(overlaySelectionName, overlayNameSubstring) >= 0) Overlay.removeSelection(ovl); /* don't know why I need this 2nd deletion attempt after index reset - it just works for my images */ run("Select None"); } } else ovl++; /* only advance ovl count if slice is not removed, removing slice resets index values */ } } } if (slices == 1 && channels > 1) { /* not actually tested on Channels yet !! */ for (i = 0; i < channels; i++) { setChannel(i + 1); ovl = 0; for (j = 0; j < initialOverlaySize; j++) { if (j < Overlay.size) { Overlay.activateSelection(ovl); overlaySelectionName = getInfo("selection.name"); if (indexOf(overlaySelectionName, overlayNameSubstring) >= 0) { Overlay.removeSelection(ovl); if (ovl < Overlay.size) { Overlay.activateSelection(ovl); overlaySelectionName = getInfo("selection.name"); if (indexOf(overlaySelectionName, overlayNameSubstring) >= 0) Overlay.removeSelection(ovl); /* don't know why I need this 2nd deletion attempt after index reset - it just works for my images */ } } else ovl++; /* only advance ovl count if slice is not removed, removing slice resets index values */ } } } } } } function removeTrailingZerosAndPeriod(string) { /* Removes any trailing zeros after a period v210430 totally new version: Note: Requires remTZeroP function Nested string functions require "" prefix */ lIP = lastIndexOf(string, "."); if (lIP >= 0) { lIP = lengthOf(string) - lIP; string = "" + remTZeroP(string, lIP); } return string; } function remTZeroP(string, iterations) { for (i = 0; i < iterations; i++) { if (endsWith(string, "0")) string = substring(string, 0, lengthOf(string) - 1); else if (endsWith(string, ".")) string = substring(string, 0, lengthOf(string) - 1); /* Must be "else if" because we only want one removal per iteration */ } return string; } function restoreExit(message) { /* Make a clean exit from a macro, restoring previous settings */ /* v200305 first version using memFlush function v220316 if message is blank this should still work now REQUIRES saveSettings AND memFlush */ restoreSettings(); /* Restore previous settings before exiting */ setBatchMode("exit & display"); /* Probably not necessary if exiting gracefully but otherwise harmless */ memFlush(200); if (message != "") exit(message); else exit; } function runDemo() { /* Generates standard imageJ demo blob analysis */ /* v180104 */ run("Blobs (25K)"); run("Auto Threshold", "method=Default"); run("Convert to Mask"); run("Set Scale...", "distance=10 known=1 unit=um"); /* Add an arbitrary scale to demonstrate unit usage. */ run("Analyze Particles...", "display exclude clear add"); resetThreshold(); if (is("Inverting LUT")) run("Invert LUT"); } function safeColornameFill(colorName) { /* Requires function getColorArrayFromColorName v230406: 1st version Peter J. Lee. v230920 Switched to setColor. */ orFGC = getValue("color.foreground"); colorArray = getColorArrayFromColorName(colorName); setColor(colorArray[0], colorArray[1], colorArray[2]); fill(); setForegroundColor(orFGC); } function safeColornameFillClear(colorName) { /* Requires function getColorArrayFromColorName v230418 1st version pjl */ if (selectionType >= 0) { orBGC = getValue("color.background"); colorArray = getColorArrayFromColorName(colorName); setBackgroundColor(colorArray[0], colorArray[1], colorArray[2]); run("Clear", "slice"); setBackgroundColor(orBGC); } } function safeSaveAndClose(filetype, path, fileSaveName, closeImageIfSaved) { /* v230411: 1st version reworked v230812: Uses full dialog which should save time for non-saves, includes options to change the directory and filetype. v230814: Close by imageID not filename. Added option to override closeImageIfSaved. v230915: Saves if there is no change in path rather than getting stuck in loop. v230920: Allows empty path string. v240315: Fixed RadioButton issue. */ functionL = "safeSaveAndClose_v240315"; imageID = getImageID(); fS = File.separator; filetypes = newArray("tiff", "png", "jpeg"); extensions = newArray("tif", "png", "jpg"); for (i = 0; i < 3; i++) { if (filetype == filetypes[i]) extension = extensions[i]; else extension = extensions[0]; } if (!endsWith(fileSaveName, extension)) { if (lastIndexOf(fileSaveName, ".") > fileSaveName.length - 5) fileSaveName = substring(fileSaveName, 0, lastIndexOf(fileSaveName, ".") + 1) + extension; else fileSaveName += "." + extension; } if (path != "") { if (endsWith(path, fS)) path = substring(path, 0, path.length - 1); fullPath = path + fS + fileSaveName; } else fullPath = ""; newSave = false; if (!File.exists(fullPath) && fullPath != "") { saveAs(filetype, fullPath); if (File.exists(fullPath)) newSave = true; } if (!newSave) { Dialog.create("Options: " + functionL); if (path != "") { Dialog.addMessage("File: " + fileSaveName + " already exists in\n" + path); Dialog.addMessage("If no changes are made below, the existing file will be overwritten"); } Dialog.addString("Change the filename?", fileSaveName, fileSaveName.length + 5); if (path == "") path = File.directory; Dialog.addDirectory("Change the directory?", path); // Dialog.addChoice("Change the filetype?", filetypes, filetypes[0]); Dialog.addRadioButtonGroup("Change the filetype?", filetypes, 1, filetypes.length, filetypes[0]); Dialog.addCheckbox("Don't save file", false); Dialog.addCheckbox("Close image \(imageID: " + imageID + ") after successful save", closeImageIfSaved); Dialog.show; newFileSaveName = Dialog.getString(); newPath = Dialog.getString(); // newFiletype = Dialog.getChoice(); newFiletype = Dialog.getRadioButton(); dontSaveFile = Dialog.getCheckbox(); closeImageIfSaved = Dialog.getCheckbox(); if (!dontSaveFile) { if (!File.isDirectory(newPath)) File.makeDirectory(newPath); if (!endsWith(newPath, fS)) newPath += fS; for (i = 0; i < 3; i++) { if (newFiletype == filetypes[i]) { newExtension = extensions[i]; if (extension != newExtension) newfileSaveName = replace(newFileSaveName, extension, newExtension); } } newFullPath = newPath + newFileSaveName; if (!File.exists(newFullPath) || newFullPath == fullPath) saveAs(newFiletype, newFullPath); else safeSaveAndClose(newFiletype, newPath, newFileSaveName, closeImageIfSaved); if (File.exists(newFullPath)) newSave = true; } } if (newSave && closeImageIfSaved && nImages > 0) { if (getImageID() == imageID) close(); else IJ.log(functionL + ": Image ID change so fused image not closed"); } } function selectResultsWindow(ignoreHistograms) { /* selects the Results window v200722: 1st version v200723: Uses separate getResultsTableList function v211027: if only one Results window found it selects it. Requires restoreExit function v230804: Adds boolean ignoreHistograms option */ functionL = "selectResultsWindow function v211027"; resultsWindows = getResultsTableList(ignoreHistograms); if (resultsWindows.length > 1) { resultsWindows = Array.sort(resultsWindows); /* R for Results comes before S for Summary */ Dialog.create("Select table for analysis: " + functionL); Dialog.addChoice("Choose Results Table: ", resultsWindows, resultsWindows[0]); Dialog.show(); selectWindow(Dialog.getChoice()); } else if (resultsWindows.length == 1) selectWindow(resultsWindows[0]); else restoreExit("Sorry, no results windows found"); } function sensibleScales(pixelW, inUnit, targetLength) { /* v230808: 1st version REQUIRES indexOfArray function v230926: Removed exit, just logs without change v240209: Does not require indexOfArray function. Now return original units if kUnit match not found. */ kUnits = newArray("m", "mm", getInfo("micrometer.abbreviation"), "nm", "pm"); if (inUnit == "inches") { inUnit = "mm"; pixelW *= 25.4; IJ.log("Inches converted to mm units"); } if (startsWith(inUnit, "micro") || endsWith(inUnit, "ons") || inUnit == "um" || inUnit == "µm") inUnit = kUnits[2]; for (i = 0, iInUnit = -1; i < kUnits.length; i++) if (inUnit == kUnits[i]) iInUnit = i; if (iInUnit < 0) IJ.log("Scale unit \(" + inUnit + "\) not in unitChoices for sensible scale function, so units not optimized"); else { while (pixelW * targetLength > 500) { pixelW /= 1000; iInUnit -= 1; inUnit = kUnits[iInUnit]; } while (pixelW * targetLength < 0.1) { pixelW *= 1000; iInUnit += 1; inUnit = kUnits[iInUnit]; } } outArray = Array.concat(pixelW, inUnit); return outArray; } function sensibleUnits(pixelW, inUnit) { /* v220805: 1st version v230808: Converts inches to mm automatically. v230809: Removed exit, just logs without change. v240209: Does not require indexOfArray function. v260306: Added Zeiss vsi units. */ kUnits = newArray("m", "mm", getInfo("micrometer.abbreviation"), "nm", "pm"); if (inUnit == "inches") { inUnit = "mm"; pixelW *= 25.4; IJ.log("Inches converted to mm units"); } if (startsWith(inUnit, "micro") || endsWith(inUnit, "ons") || inUnit == "um" || inUnit == "µm" || inUnit.indexOf("-6m") >= 0) inUnit = kUnits[2]; else if (inUnit.indexOf("-3m") >= 0) inUnit = kUnits[1]; else if (inUnit.indexOf("-9m") >= 0) inUnit = kUnits[3]; else if (inUnit.indexOf("-12m") >= 0) inUnit = kUnits[4]; for (i = 0, iInUnit = -1; i < kUnits.length; i++) if (inUnit == kUnits[i]) iInUnit = i; if (iInUnit < 0) IJ.log("Scale unit \(" + inUnit + "\) not in unitChoices for sensible scale function, so units not optimized"); else { while (round(pixelW) > 50) { pixelW /= 1000; iInUnit -= 1; inUnit = kUnits[iInUnit]; } while (pixelW < 0.02) { pixelW *= 1000; iInUnit += 1; inUnit = kUnits[iInUnit]; } } outArray = Array.concat(pixelW, inUnit); return outArray; } function setAnalysisDefaults() { /* Set options for black objects on white background as this works better for publications */ run("Options...", "iterations=1 white count=1"); /* Set the background to white */ run("Colors...", "foreground=black background=white selection=yellow"); /* Set the preferred colors for these macros */ setOption("BlackBackground", false); run("Appearance...", " "); if (is("Inverting LUT")) run("Invert LUT"); /* do not use Inverting LUT */ /* The above should be the defaults but this makes sure (black particles on a white background) https://imagej.net/doku.php?id=faq:technical:how_do_i_set_up_imagej_to_deal_with_white_particles_on_a_black_background_by_default} */ run("Set Measurements...", "area mean standard modal min centroid center perimeter bounding fit shape feret's integrated median skewness kurtosis area_fraction stack nan redirect=None decimal=9"); } function getScaleFromBioformats(filePath){ /* v260309 + v260313(annotation) 1st version uploaded */ run("Bio-Formats Macro Extensions"); Ext.setId(filePath); bioFormatsScales = newArray(0, 0, 0); Ext.getPixelsPhysicalSizeX(bioFormatsScales[0]); /* Obtains the width of a pixel in microns, or NaN if the width is not stored in the original file. */ Ext.getPixelsPhysicalSizeY(bioFormatsScales[1]); /* Obtains the height of a pixel in microns, or NaN if the width is not stored in the original file. */ Ext.getPixelsPhysicalSizeZ(bioFormatsScales[2]); /* Obtains the depth of a pixel in microns, or NaN if the width is not stored in the original file. */ Ext.close(); /* Closes the active dataset */ return bioFormatsScales; } function setScaleFromCZSemHeader() { /* This very simple function sets the scale for SEM images taken with the Carl Zeiss SmartSEM program. It requires the tiff_tags plugin written by Joachim Wesner. It can be downloaded from https://imagej.net/ij/plugins/tiff-tags.html There is an example image available at https://imagej.net/ij/images/SmartSEMSample.tif This is the number of the VERY long tag that stores all the SEM information See original Nabble post by Pablo Manuel Jais: http://imagej.1557.x6.nabble.com/Importing-SEM-images-with-scale-td3689900.html imageJ version: https://imagej.net/ij/macros/SetScaleFromTiffTag.txt v161103 with minor tweaks by Peter J. Lee National High Magnetic Field Laboratory v161108 adds Boolean unit option, v171024 fixes Boolean option. v180820 fixed incorrect message in dialog box. v220812 REQUIRES sensibleUnits function v230918 Version for fancyScaleBar REQUIREs sensibleScales instead of sensibleUnits */ /* Gets the path+name of the active image */ path = getDirectory("image"); if (path == "") exit("path not available"); name = getInfo("image.filename"); if (name == "") exit("name not available"); if (!matches(getInfo("image.filename"), ".*[tT][iI][fF].*")) exit("Not a TIFF file \(original Zeiss TIFF file required\)"); if (!checkForPlugin("tiff_tags.jar")) exit("TIFF Tags plugin missing"); path = path + name; /* Gets the tag, and parses it to get the pixel size information */ tag = call("TIFF_Tags.getTag", path, 34118); i0 = indexOf(tag, "Image Pixel Size = "); if (i0 != -1) { i1 = indexOf(tag, "=", i0); i2 = indexOf(tag, "AP", i1); if (i1 == -1 || i2 == -1 || i2 <= i1 + 4) exit("Parsing error! Maybe the file structure changed?"); scaleCSV = substring(tag, i1 + 2, i2 - 2); /* Splits the pixel size in number+unit and sets the scale of the active image */ CZScale = split(scaleCSV); distPerPixel = parseFloat(CZScale[0]); CZScale = sensibleScales(distPerPixel, CZScale[1], 100); distPerPixel = parseFloat(CZScale[0]); CZUnit = CZScale[1]; setVoxelSize(distPerPixel, distPerPixel, 1, CZUnit); } else if (getBoolean("No CZSem tag found; do you want to continue?")) run("Set Scale..."); } function stripKnownExtensionFromString(string) { /* Note: Do not use on path as it may change the directory names v210924: Tries to make sure string stays as string. v211014: Adds some additional cleanup. v211025: fixes multiple 'known's issue. v211101: Added ".Ext_" removal. v211104: Restricts cleanup to end of string to reduce risk of corrupting path. v211112: Tries to fix trapped extension before channel listing. Adds xlsx extension. v220615: Tries to fix the fix for the trapped extensions ... v230504: Protects directory path if included in string. Only removes doubled spaces and lines. v230505: Unwanted dupes replaced by unusefulCombos. v230607: Quick fix for infinite loop on one of while statements. v230614: Added AVI. v230905: Better fix for infinite loop. v230914: Added BMP and "_transp" and rearranged */ fS = File.separator; string = "" + string; protectedPathEnd = lastIndexOf(string, fS) + 1; if (protectedPathEnd > 0) { protectedPath = substring(string, 0, protectedPathEnd); string = substring(string, protectedPathEnd); } unusefulCombos = newArray("-", "_", " "); for (i = 0; i < lengthOf(unusefulCombos); i++) { for (j = 0; j < lengthOf(unusefulCombos); j++) { combo = unusefulCombos[i] + unusefulCombos[j]; while (indexOf(string, combo) >= 0) string = replace(string, combo, unusefulCombos[i]); } } if (lastIndexOf(string, ".") > 0 || lastIndexOf(string, "_lzw") > 0) { knownExts = newArray(".avi", ".csv", ".bmp", ".dsx", ".gif", ".jpg", ".jpeg", ".jp2", ".png", ".tif", ".txt", ".xlsx"); knownExts = Array.concat(knownExts, knownExts, "_transp", "_lzw"); kEL = knownExts.length; for (i = 0; i < kEL / 2; i++) knownExts[i] = toUpperCase(knownExts[i]); chanLabels = newArray(" \(red\)", " \(green\)", " \(blue\)", "\(red\)", "\(green\)", "\(blue\)"); for (i = 0, k = 0; i < kEL; i++) { for (j = 0; j < chanLabels.length; j++) { /* Looking for channel-label-trapped extensions */ iChanLabels = lastIndexOf(string, chanLabels[j]) - 1; if (iChanLabels > 0) { preChan = substring(string, 0, iChanLabels); postChan = substring(string, iChanLabels); while (indexOf(preChan, knownExts[i]) > 0) { preChan = replace(preChan, knownExts[i], ""); string = preChan + postChan; } } } while (endsWith(string, knownExts[i])) string = "" + substring(string, 0, lastIndexOf(string, knownExts[i])); } } unwantedSuffixes = newArray(" ", "_", "-"); for (i = 0; i < unwantedSuffixes.length; i++) { while (endsWith(string, unwantedSuffixes[i])) string = substring(string, 0, string.length - lengthOf(unwantedSuffixes[i])); /* cleanup previous suffix */ } if (protectedPathEnd > 0) { if (!endsWith(protectedPath, fS)) protectedPath += fS; string = protectedPath + string; } return string; } function stripUnitFromString(string) { if (endsWith(string, "\)")) { /* Label with units from string if enclosed by parentheses */ unitIndexStart = lastIndexOf(string, "\("); unitIndexEnd = lastIndexOf(string, "\)"); stringUnit = substring(string, unitIndexStart + 1, unitIndexEnd); unitCheck = matches(stringUnit, ".*[0-9].*"); if (unitCheck == 0) { /* If the "unit" contains a number it probably isn't a unit */ stringLabel = substring(string, 0, unitIndexStart); } else stringLabel = string; } else stringLabel = string; return stringLabel; } function tableColumnRenameOrReplace(oldName, newName) { /* 1st version 9/5/2019 11:29 AM PJL v190906 Add table update */ headingsString = "\t" + Table.headings + "\t"; /* 1st tab not currently needed */ oldNameTab = "\t" + oldName + "\t"; newNameTab = "\t" + newName + "\t"; if (indexOf(headingsString, newNameTab) >= 0) { Table.deleteColumn(newName); Table.update; } if (indexOf(headingsString, oldNameTab) >= 0) { Table.renameColumn(oldName, newName); Table.update; } } function tableDeleteColumn(columnName) { /* 1st version 9/5/2019 11:29 AM PJL Allows no-error running if when column may or may not exist. v190906 Add table update */ headingsString = "\t" + Table.headings + "\t"; /* 1st tab not currently needed. */ if (indexOf(headingsString, columnName) > = 0) { Table.deleteColumn(columnName); Table.update; } } function tableSetColumnValue(columnName, value) { /* Original version v190905 to overcome Table macro limitation - PJL v190906 Add table update v200730 If value cannot be converted to number it is entered as a string */ if (Table.size > 0) { tempArray = newArray(Table.size); number = parseFloat(value); if (isNaN(number)) for (i = 0; i < Table.size; i++) Table.set(columnName, i, value); else { Array.fill(tempArray, number); Table.setColumn(columnName, tempArray); } Table.update; } else restoreExit("No Table for array fill"); } function tightBoundingBox(toleranceInPc, limitsPc) { /* Function to create a bounding box that takes into account the maximum extent of the edge intensity Fitting to left-right and top-bottom are true/false. v211209d 1st version Peter J. Lee */ if (toleranceInPc >= 50) exit("Tight bounding box tolerance is not sensible: " + toleranceInPc + "%"); if (limitsPc >= 50) exit("Tight bounding box limits are not sensible: " + limitsPc + "%"); limits = limitsPc / 100; startBM = true; if (is("Batch Mode") == false) { startBM = false; setBatchMode(true); } run("Duplicate...", "title=tBBTemp ignore"); if (bitDepth() == 24) run("8-bit"); getMinAndMax(minI, maxI); toleranceI = toleranceInPc * (maxI - minI) / 100; minI += toleranceI; maxI -= toleranceI; getDimensions(imageWidth, imageHeight, null, null, null); leftX = 0; rightX = imageWidth - 1; xEnd = rightX; topY = 0; bottomY = imageHeight - 1; yEnd = bottomY; progC = 0; progRange = 2 * imageWidth + 2 * imageHeight - 4; bounds = limits * imageHeight; for (x = 0; x < imageWidth; x++) { showProgress(x, progRange); wI = getPixel(x, 0); if (wI <= minI || wI >= maxI) { doWand(x, 0); if (selectionType() >= 0) { getSelectionBounds(startX, startY, tBBWidth, tBBHeight); if (tBBHeight < bounds) { topY = maxOf(topY, tBBHeight); x = startX + tBBWidth; } run("Select None"); } } } progC = imageWidth - 1; for (x = 0; x < imageWidth; x++) { showProgress(x + progC, progRange); wI = getPixel(x, yEnd); if (wI <= minI || wI >= maxI) { doWand(x, yEnd); if (selectionType() >= 0) { getSelectionBounds(startX, startY, tBBWidth, tBBHeight); if (tBBHeight < bounds) { bottomY = minOf(bottomY, imageHeight - tBBHeight); x = startX + tBBWidth; } run("Select None"); } } } progC += imageWidth - 1; bounds = limits * imageWidth; for (y = 0; y < imageHeight; y++) { showProgress(y + progC, progRange); wI = getPixel(0, y); if (wI <= minI || wI >= maxI) { doWand(0, y); if (selectionType() >= 0) { getSelectionBounds(startX, startY, tBBWidth, tBBHeight); if (tBBWidth >= bounds && y > (1 - limits) * imageHeight) y = imageHeight; /* To ignore annotation label at bottom */ else if (tBBWidth < bounds) { leftX = maxOf(leftX, tBBWidth); y = maxOf(y, startY + tBBHeight); } run("Select None"); } } } progC += imageHeight - 1; for (y = 0; y < imageHeight; y++) { showProgress(y + progC, progRange); wI = getPixel(xEnd, y); if (wI <= minI || wI >= maxI) { doWand(xEnd, y); if (selectionType() >= 0) { getSelectionBounds(startX, startY, tBBWidth, tBBHeight); if (tBBWidth >= bounds && y > (1 - limits) * imageHeight) y = imageHeight; /* To ignore annotation label at bottom */ else if (tBBWidth < bounds) { rightX = minOf(rightX, startX); y = maxOf(y, startY + tBBHeight); } } } } close(); makeRectangle(leftX, topY, rightX - leftX, bottomY - topY); if (startBM == false) setBatchMode("exit & display"); } function toChar(string) { /* v180612 first version v1v180627 Expanded v200428 removed "symbol" prefix */ string = replace(string, "Angstrom", fromCharCode(0x212B)); /* ANGSTROM SIGN */ string = replace(string, "alpha", fromCharCode(0x03B1)); string = replace(string, "Alpha", fromCharCode(0x0391)); string = replace(string, "beta", fromCharCode(0x03B2)); /* Lower case beta */ string = replace(string, "Beta", fromCharCode(0x0392)); /* ß CAPITAL */ string = replace(string, "gamma", fromCharCode(0x03B3)); /* MATHEMATICAL SMALL GAMMA */ string = replace(string, "Gamma", fromCharCode(0xD835)); /* MATHEMATICAL BOLD CAPITAL GAMMA */ string = replace(string, "delta", fromCharCode(0x1E9F)); /* SMALL LETTER DELTA */ string = replace(string, "Delta", fromCharCode(0x0394)); /* CAPITAL LETTER DELTA */ string = replace(string, "epsilon", fromCharCode(0x03B5)); /* GREEK SMALL LETTER EPSILON */ string = replace(string, "Epsilon", fromCharCode(0x0395)); /* GREEK CAPITAL LETTER EPSILON */ string = replace(string, "zeta", fromCharCode(0x03B6)); /* GREEK SMALL LETTER ZETA */ string = replace(string, "Zeta", fromCharCode(0x0396)); /* GREEK CAPITAL LETTER ZETA */ string = replace(string, "theta", fromCharCode(0x03B8)); /* GREEK SMALL LETTER THETA */ string = replace(string, "Theta", fromCharCode(0x0398)); /* GREEK CAPITAL LETTER THETA */ string = replace(string, "iota", fromCharCode(0x03B9)); /* GREEK SMALL LETTER IOTA */ string = replace(string, "Iota", fromCharCode(0x0196)); /* GREEK CAPITAL LETTER IOTA */ string = replace(string, "kappa", fromCharCode(0x03BA)); /* GREEK SMALL LETTER KAPPA */ string = replace(string, "Kappa", fromCharCode(0x0196)); /* GREEK CAPITAL LETTER KAPPA */ string = replace(string, "lambda", fromCharCode(0x03BB)); /* GREEK SMALL LETTER LAMDA */ string = replace(string, "Lambda", fromCharCode(0x039B)); /* GREEK CAPITAL LETTER LAMDA */ string = replace(string, "mu", fromCharCode(0x03BC)); /* µ GREEK SMALL LETTER MU */ string = replace(string, "Mu", fromCharCode(0x039C)); /* GREEK CAPITAL LETTER MU */ string = replace(string, "nu", fromCharCode(0x03BD)); /* GREEK SMALL LETTER NU */ string = replace(string, "Nu", fromCharCode(0x039D)); /* GREEK CAPITAL LETTER NU */ string = replace(string, "xi", fromCharCode(0x03BE)); /* GREEK SMALL LETTER XI */ string = replace(string, "Xi", fromCharCode(0x039E)); /* GREEK CAPITAL LETTER XI */ string = replace(string, "pi", fromCharCode(0x03C0)); /* GREEK SMALL LETTER Pl */ string = replace(string, "Pi", fromCharCode(0x03A0)); /* GREEK CAPITAL LETTER Pl */ string = replace(string, "rho", fromCharCode(0x03C1)); /* GREEK SMALL LETTER RHO */ string = replace(string, "Rho", fromCharCode(0x03A1)); /* GREEK CAPITAL LETTER RHO */ string = replace(string, "sigma", fromCharCode(0x03C3)); /* GREEK SMALL LETTER SIGMA */ string = replace(string, "Sigma", fromCharCode(0x03A3)); /* GREEK CAPITAL LETTER SIGMA */ string = replace(string, "phi", fromCharCode(0x03C6)); /* GREEK SMALL LETTER PHI */ string = replace(string, "Phi", fromCharCode(0x03A6)); /* GREEK CAPITAL LETTER PHI */ string = replace(string, "omega", fromCharCode(0x03C9)); /* GREEK SMALL LETTER OMEGA */ string = replace(string, "Omega", fromCharCode(0x03A9)); /* GREEK CAPITAL LETTER OMEGA */ string = replace(string, "eta", fromCharCode(0x03B7)); /* GREEK SMALL LETTER ETA */ string = replace(string, "Eta", fromCharCode(0x0397)); /* GREEK CAPITAL LETTER ETA */ string = replace(string, "sub2", fromCharCode(0x2082)); /* subscript 2 */ string = replace(string, "sub3", fromCharCode(0x2083)); /* subscript 3 */ string = replace(string, "sub4", fromCharCode(0x2084)); /* subscript 4 */ string = replace(string, "sub5", fromCharCode(0x2085)); /* subscript 5 */ string = replace(string, "sup2", fromCharCode(0x00B2)); /* superscript 2 */ string = replace(string, "sup3", fromCharCode(0x00B3)); /* superscript 3 */ string = replace(string, ">=", fromCharCode(0x2265)); /* GREATER-THAN OR EQUAL TO */ string = replace(string, "<=", fromCharCode(0x2264)); /* LESS-THAN OR EQUAL TO */ string = replace(string, "xx", fromCharCode(0x00D7)); /* MULTIPLICATION SIGN */ string = replace(string, "copyright=", fromCharCode(0x00A9)); /* © */ string = replace(string, "ro", fromCharCode(0x00AE)); /* registered sign */ string = replace(string, "tm", fromCharCode(0x2122)); /* ™ */ string = replace(string, "parallelto", fromCharCode(0x2225)); /* PARALLEL TO note CANNOT use "|" key */ // string= replace(string, "perpendicularto", fromCharCode(0x27C2)); /* PERPENDICULAR note CANNOT use "|" key */ string = replace(string, "degree", fromCharCode(0x00B0)); /* Degree */ string = replace(string, "degreeC", fromCharCode(0x00B0) + fromCharCode(0x2009) + "C"); /* Degree C */ string = replace(string, "arrow-up", fromCharCode(0x21E7)); /* 'UPWARDS WHITE ARROW */ string = replace(string, "arrow-down", fromCharCode(0x21E9)); /* 'DOWNWARDS WHITE ARROW */ string = replace(string, "arrow-left", fromCharCode(0x21E6)); /* 'LEFTWARDS WHITE ARROW */ string = replace(string, "arrow-right", fromCharCode(0x21E8)); /* 'RIGHTWARDS WHITE ARROW */ return string; } function toWhiteBGBinary(windowTitle) { /* For black objects on a white background */ /* Replaces binaryCheck function v220707 v? Warns but does not exit. */ selectWindow(windowTitle); if (!is("binary")) run("8-bit"); /* Quick-n-dirty threshold if not previously thresholded */ getThreshold(t1, t2); if (t1 == -1) { run("8-bit"); run("Auto Threshold", "method=Default"); setOption("BlackBackground", false); run("Make Binary"); } if (is("Inverting LUT")) run("Invert LUT"); /* Make sure black objects on white background for consistency */ yMax = Image.height - 1; xMax = Image.width - 1; cornerPixels = newArray(getPixel(0, 0), getPixel(1, 1), getPixel(0, yMax), getPixel(xMax, 0), getPixel(xMax, yMax), getPixel(xMax - 1, yMax - 1)); Array.getStatistics(cornerPixels, cornerMin, cornerMax, cornerMean, cornerStdDev); if (cornerMax != cornerMin) IJ.log("Warning: There may be a problem with the image border, there are different pixel intensities at the corners"); /* Sometimes the outline procedure will leave a pixel border around the outside - this next step checks for this. i.e. the corner 4 pixels should now be all black, if not, we have a "border issue". */ if (cornerMean < 1) run("Invert"); } function unCleanLabel(string) { /* v161104 This function replaces special characters with standard characters for file system compatible filenames. + 041117b to remove spaces as well. + v220126 added getInfo("micrometer.abbreviation"). + v220128 add loops that allow removal of multiple duplication. + v220131 fixed so that suffix cleanup works even if extensions are included. + v220616 Minor index range fix that does not seem to have an impact if macro is working as planned. v220715 added 8-bit to unwanted dupes. v220812 minor changes to micron and Ångström handling + v231005 Replaced superscript abbreviations that did not work. + v240124 Replace _+_ with +. */ /* Remove bad characters */ string = string.replace(fromCharCode(178), "sup2"); /* superscript 2 */ string = string.replace(fromCharCode(179), "sup3"); /* superscript 3 UTF-16 (decimal) */ string = string.replace(fromCharCode(0xFE63) + fromCharCode(185), "sup-1"); /* Small hyphen substituted for superscript minus as 0x207B does not display in table */ string = string.replace(fromCharCode(0xFE63) + fromCharCode(178), "sup-2"); /* Small hyphen substituted for superscript minus as 0x207B does not display in table */ string = string.replace(fromCharCode(181) + "m", "um"); /* micron units */ string = string.replace(getInfo("micrometer.abbreviation"), "um"); /* micron units */ string = string.replace(fromCharCode(197), "Angstrom"); /* Ångström unit symbol */ string = string.replace(fromCharCode(0x212B), "Angstrom"); /* the other Ångström unit symbol */ string = string.replace(fromCharCode(0x2009) + fromCharCode(0x00B0), "deg"); /* replace thin spaces degrees combination */ string = string.replace(fromCharCode(0x2009), "_"); /* Replace thin spaces */ string = string.replace("%", "pc"); /* % causes issues with html listing */ string = string.replace(" ", "_"); /* Replace spaces - these can be a problem with image combination */ /* Remove duplicate strings */ unwantedDupes = newArray("8bit", "8-bit", "lzw"); for (i = 0; i < lengthOf(unwantedDupes); i++) { iLast = lastIndexOf(string, unwantedDupes[i]); iFirst = indexOf(string, unwantedDupes[i]); if (iFirst != iLast) { string = string.substring(0, iFirst) + string.substring(iFirst + lengthOf(unwantedDupes[i])); i = -1; /* check again */ } } unwantedDbls = newArray("_-", "-_", "__", "--", "\\+\\+"); for (i = 0; i < lengthOf(unwantedDbls); i++) { iFirst = indexOf(string, unwantedDbls[i]); if (iFirst >= 0) { string = string.substring(0, iFirst) + string.substring(string, iFirst + lengthOf(unwantedDbls[i]) / 2); i = -1; /* check again */ } } string = string.replace("_\\+", "\\+"); /* Clean up autofilenames */ string = string.replace("\\+_", "\\+"); /* Clean up autofilenames */ /* cleanup suffixes */ unwantedSuffixes = newArray(" ", "_", "-", "\\+"); /* things you don't wasn't to end a filename with */ extStart = lastIndexOf(string, "."); sL = lengthOf(string); if (sL - extStart <= 4 && extStart > 0) extIncl = true; else extIncl = false; if (extIncl) { preString = substring(string, 0, extStart); extString = substring(string, extStart); } else { preString = string; extString = ""; } for (i = 0; i < lengthOf(unwantedSuffixes); i++) { sL = lengthOf(preString); if (endsWith(preString, unwantedSuffixes[i])) { preString = substring(preString, 0, sL - lengthOf(unwantedSuffixes[i])); /* cleanup previous suffix */ i = -1; /* check one more time */ } } if (!endsWith(preString, "_lzw") && !endsWith(preString, "_lzw.")) preString = replace(preString, "_lzw", ""); /* Only want to keep this if it is at the end */ string = preString + extString; /* End of suffix cleanup */ return string; } function unInvertLUT() { if (is("Inverting LUT")) run("Invert LUT"); } function unitLabelFromString(string, imageUnit) { /* v180404 added Feret_MinDAngle_Offset v210823 REQUIRES ASC function indexOfArray(array, string, default) for expanded "unitless" array. v220808 Replaces ° with fromCharCode(0x00B0). v230109 Expand px to pixels. Simplify angleUnits. v231005 Look and underscores replaced by spaces too. */ if (endsWith(string, "\)")) { /* Label with units from string if enclosed by parentheses */ unitIndexStart = lastIndexOf(string, "\("); unitIndexEnd = lastIndexOf(string, "\)"); stringUnit = substring(string, unitIndexStart + 1, unitIndexEnd); unitCheck = matches(stringUnit, ".*[0-9].*"); if (unitCheck == 0) { /* If the "unit" contains a number it probably isn't a unit unless it is the 0-90 degress setting */ unitLabel = stringUnit; } else if (indexOf(stringUnit, "0-90") < 0 || indexOf(stringUnit, "0to90") < 0) unitLabel = fromCharCode(0x00B0); else { unitLabel = ""; } } else { noUnits = newArray("Circ.", "Slice", "AR", "Round", "Solidity", "Image_Name", "PixelAR", "ROI_name", "ObjectN", "AR_Box", "AR_Feret", "Rnd_Feret", "Compact_Feret", "Elongation", "Thinnes_Ratio", "Squarity_AP", "Squarity_AF", "Squarity_Ff", "Convexity", "Rndnss_cAR", "Fbr_Snk_Crl", "Fbr_Rss2_Crl", "AR_Fbr_Snk", "Extent", "HSF", "HSFR", "Hexagonality"); noUnitSs = newArray; for (i = 0; i < noUnits.length; i++) noUnitSs[i] = replace(noUnits[i], "_", " "); angleUnits = newArray("Angle", "FeretAngle", "Cir_to_El_Tilt", "0-90", fromCharCode(0x00B0), "0to90", "degrees"); angleUnitSs = newArray; for (i = 0; i < angleUnits.length; i++) angleUnitSs[i] = replace(angleUnits[i], "_", " "); chooseUnits = newArray("Mean", "StdDev", "Mode", "Min", "Max", "IntDen", "Median", "RawIntDen", "Slice"); if (string == "Area") unitLabel = imageUnit + fromCharCode(178); else if (indexOfArray(noUnits, string, -1) >= 0) unitLabel = "None"; else if (indexOfArray(noUnitSs, string, -1) >= 0) unitLabel = "None"; else if (indexOfArray(chooseUnits, string, -1) >= 0) unitLabel = ""; else if (indexOfArray(angleUnits, string, -1) >= 0) unitLabel = fromCharCode(0x00B0); else if (indexOfArray(angleUnitSs, string, -1) >= 0) unitLabel = fromCharCode(0x00B0); else if (string == "%Area") unitLabel = "%"; else unitLabel = imageUnit; if (indexOf(unitLabel, "px") >= 0) unitLabel = "pixels"; } return unitLabel; } function waitForOpenWindow(windowName, testWait, minWait, maxWait) { /* v230511: 1st version v240222: Flash green on completion. v240227: Flash pink for each test. v240311: Adds option to keep waiting if window has not opened */ wait(minWait); maxIterations = maxWait / testWait; for (i = 0; i < maxIterations; i++) { showProgress(i, maxIterations); showStatus("Waiting for " + windowName, "flash pink"); if (!isOpen(windowName)) wait(testWait); else i = maxIterations; } if (!isOpen(windowName)) { keepWaiting = getBoolean("Target window is not open; do you want to continue waiting?"); if (keepWaiting && !isOpen(windowName)) waitForOpenWindow(windowName, testWait, minWait, maxWait); else(!isOpen(windowName)) exit("Gave up waiting for " + windowName); } showStatus("The wait for " + windowName + " is over", "flash green"); } function writeLabel(labelColor) { setColorFromColorName(labelColor); drawString(finalLabel, finalLabelX, finalLabelY); } function writeLabel7(font, size, color, text, x, y, aA) { /* Requires the functions setColorFromColorName, getColorArrayFromColorName(colorName) etc. v190619: All variables as options. v230918: With ImageJ 1.54e and newer, text antialiasing is enabled by default and you have to use the keyword 'no-smoothing' */ if (aA) setFont(font, size); else setFont(font, size, "no-smoothing"); setColorFromColorName(color); drawString(text, x, y); } function writeObjectLabel() { /* Replace decPlaces = autoCalculateDecPlaces(labelValue); with decPlaces = autoCalculateDecPlaces4(dP, min, max, labelValue); if necessary */ roiManager("Select", i); labelValue = getResult(parameter, i); if (dpChoice == "Auto") decPlaces = autoCalculateDecPlaces(dP, min, max, labelValue); labelString = d2s(labelValue, decPlaces); /* Reduce Decimal places for labeling - move these two lines to below the labels you prefer */ labelString = removeTrailingZerosAndPeriod(labelString); /* remove trailing characters */ Roi.getBounds(roiX, roiY, roiWidth, roiHeight); lFontSize = fontSize; /* initial estimate */ setFont(font, lFontSize, fontStyle); lFontSize = fontSizeCorrection * fontSize * roiWidth / (getStringWidth(labelString)); /* Adjust label font size so that the label fits within object width */ if (lFontSize > fontSizeCorrection * roiHeight) lFontSize = fontSizeCorrection * roiHeight; /* readjust label font size so label fits within object height */ if (lFontSize > maxLFontSize) lFontSize = maxLFontSize; if (lFontSize < minLFontSize) lFontSize = minLFontSize; setFont(font, lFontSize, fontStyle); if (ctrChoice == "ROI Center") textOffset = roiX + ((roiWidth) - getStringWidth(labelString)) / 2; else textOffset = getResult("mc_X\(px\)", i) - round(getStringWidth(labelString) / 2); setColorFromColorName("white"); if (ctrChoice == "ROI Center") drawString(labelString, textOffset, roiY + round((roiHeight + lFontSize) / 2); else drawString(labelString, textOffset, getResult("mc_Y\(px\)", i) + round(lFontSize / 2)); } function writeObjectLabelNoRamp() { /* 3/16/2017 this version adds labeling by "ID" number */ roiManager("Select", i); if (parameter == "ID") labelValue = i + 1; else labelValue = getResult(parameter, i); if (dpChoice == "Auto") decPlaces = autoCalculateDecPlacesFromValueOnly(labelValue); labelString = d2s(labelValue, decPlaces); /* Reduce decimal places for labeling (move these two lines to below the labels you prefer) */ Roi.getBounds(roiX, roiY, roiWidth, roiHeight); lFontSize = fontSize; /* Initial estimate */ setFont(font, lFontSize, fontStyle); lFontSize = fontSizeCorrection * fontSize * roiWidth / (getStringWidth(labelString)); /* Adjust label font size so that the label fits within object width */ if (lFontSize > fontSizeCorrection * roiHeight) lFontSize = fontSizeCorrection * roiHeight; /* Readjust the label font size so that the label fits within the object height */ if (lFontSize > maxLFontSize) lFontSize = maxLFontSize; if (lFontSize < minLFontSize) lFontSize = minLFontSize; setFont(font, lFontSize, fontStyle); if (ctrChoice == "ROI Center") textOffset = roiX + ((roiWidth) - getStringWidth(labelString)) / 2; else textOffset = getResult("mc_X\(px\)", i) - getStringWidth(labelString) / 2; setColorFromColorName("white"); if (ctrChoice == "ROI Center") drawString(labelString, textOffset, roiY + roiHeight / 2 + lFontSize / 2); else drawString(labelString, textOffset, getResult("mc_Y\(px\)", i) + lFontSize / 2); } function zapGremlins(inputString, lfRep, tabRep, nulRep, allElse) { /* v220803 Just https://wsr.imagej.net//macros/ZapGremlins.txt Basic Latin decimal character numbers are listed here: https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin v220812: chars 181 and 63 is mu and 176 is the degree symbol which seem too valuable to risk loosing: this version allows more latin extended symbols, adds allElse option for light pruning v221207: Adds NUL character replacement */ requires("1.39f"); LF = 10; TAB = 9; NUL = 0; /* Carriage return = 13 */ String.resetBuffer; n = lengthOf(inputString); for (i = 0; i < n; i++) { c = charCodeAt(inputString, i); if (c == LF) String.append(lfRep); else if (c == TAB) String.append(tabRep); else if (c == NUL) String.append(nulRep); else if (c == 63) String.append(getInfo("micrometer.abbreviation")); else if (c == 197) String.append(fromCharCode(0x212B)); /* Ångström */ else if (allElse) String.append(fromCharCode(c)); else if ((c >= 32 && c <= 127) || (c >= 176 && c <= 186)) String.append(fromCharCode(c)); } return String.buffer; String.resetBuffer; } /* ( 8(|) ( 8(|) End of All ASC Functions Section ( 8(|) ( 8(|) */