macro "ASC Set Scale" { /* 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 at end 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. F1: Updated sensibleUnits function. F2 : updated waitForOpenWindow. 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. */ macroL = "ASC-Set_Scale_from_Calibrations_or_headers_etc_v240827.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(); fullPath = path + fileName; orImageID = getImageID(); prefsNameKey = "asc_SetScale."; micronS = getInfo("micrometer.abbreviation"); xChar = fromCharCode(0x00D7); userPath = getInfo("user.dir"); diagnostics = false; tifFile = false; tiffFile = false; dsxFile = false; feiTIF = false; zeissTIF = false; fS = File.separator; metaData = getMetadata("Info"); if (checkForPlugin("Exif_reader")) exifReading = true; else exifReading = false; if (path != "") { if (fileName != "") { if (matches(getInfo("image.filename"), ".*[tT][iI][fF][fF].*")) tiffFile = true; else if (matches(getInfo("image.filename"), ".*[tT][iI][fF].*")) tifFile = true; else if (matches(getInfo("image.filename"), ".*[dD][sX][xX].*")) dsxFile = true; } } if (indexOf(metaData, "VueScan") >= 0 && exifReading) { 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 = dpiY / dpiX; pixelW = 25.4 / dpiX; /* in mm */ newUnits = sensibleUnits(pixelW, "mm"); run("Set Scale...", "distance=1 known=" + newUnits[0] + " pixel=" + pixelAR + " unit=" + newUnits[1]); IJ.log(cleanLabel("Pixel size set from VueScan Exif header: dpi = " + dpiX + ", pixel width = " + newUnits[0] + " " + newUnits[1] + ", pixel aspect ratio = " + pixelAR)); setMetadata("Info", metaVS); exit(); } } } } if (dsxFile) { 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 = umPerPixelX / umPerPixelY; run("Set Scale...", "distance=1 known=" + umPerPixelX + " pixel=" + pixelAR + " unit=um"); IJ.log(cleanLabel("Pixel size set from DSX image header: " + umPerPixelX + " " + micronS + ", Aspect ratio: " + pixelAR)); showStatus("ASC set scale completed", "flash image green"); exit(); /* DSX scale will only be as saved from Olympus app, so no modifications will be necessary */ } else { onZ = startsWith(toLowerCase(path), "z"); sizeWarning = 6; /* warns if file over 6 MB */ if (mBytes > sizeWarning || onZ) { messageTxt = "Be patient! Entire image file is 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); } getDimensions(imageWidth, imageHeight, channels, slices, frames); /* only width and slices currently used */ getVoxelSize(voxelWidth, voxelHeight, voxelDepth, voxelUnit); totalPixels = imageWidth * imageHeight; calFileName = "Measurement_Scales_ASC.csv"; 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 (indexOf(pluginsPath, "Fiji") >= 0) { 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; degCh = fromCharCode(0x00B0); zeissShortName = ""; CZScale = newArray(); CZUnit = micronS; semName = ""; 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"); /* FSU Microscope Information */ BX41Minfo = "BX41: Olympus BX41M-LED Light Microscope: 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\)"; DSXinfo = "DSX1000: Olympus DSX1000 Digital Light Microscope \(original file required\)"; EVO10info = "Zeiss EVO 10: LaB6 Scanning Electron Microscope \(original file required\)"; EsBinfo = "Zeiss 1540EsB: Field Emission Scanning Electron Microscope \(Polaroid Mag\)"; Heliosinfo = "FEI Helios G4 UC: Dual Beam FESEM/FIB"; LEXTinfo = "LEXT: Olympus OLS 3100 Scanning Confocal Laser Microscope"; PhenomXLG2info = "Phenom XLG2: Phenom XL G2 Benchtop SEM"; XBinfo = "Zeiss 1540XB: Field Emission Scanning Electron Microscope \(Polaroid Mag\)"; FrameWidthinfo = "Frame width: If you know the scaled width of your image"; dpiinfo = "DPI: Use for flatbed scanners \(typical presets\)"; /* preset values from excel file */ ascMicroscopes = newArray("EsB", "XB", "BX41M", "LEXT"); /* Just the microscopes that have preset calibrated scales available */ microscopes = newArray(""); localFileDate = 0; netFileDate = 0; zFileDate = 0; if (selectionType == 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 if (selectionType >= 0) { 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\)"; } 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; // zFileDate = File.dateLastModified(zPathCalibFile); } else { setScale = getBoolean("No calibration preset found", "Input scale parameters?", "Exit?"); if (setScale) run("Set Scale..."); else exit("Goodbye"); } } /* Now check for date inconsistency of both local and net files found */ if (localFileDate != 0 && netFileDate != 0) { 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"); else exit("Goodbye"); } } 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); /* Now check table for embedded values */ 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; } } useHeader = false; /* 1st check for previously embedded data */ infoLines = split(getMetadata("Info"), "\n"); iSEMName = indexOfArrayThatStartsWith(infoLines, "Sem = ", -1); smartSEMInfos = newArray(""); if (iSEMName >= 0) SEMName = substring(infoLines[iSEMName], 6); else SEMName = "Not found"; if (SEMName == "Not found") smartSEMInfos = newArray("SmartSEM header not found"); else smartSEMInfos = newArray(""); feiSEMName = "Not found"; if (tifFile || tiffFile) { if (SEMName == "Not found" && !dsxFile) fullFileString = File.openAsString(fullPath); if (tifFile) { if (SEMName == "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") { if (SEMName == "Not found") for (i = 0; i < smartSEMInfos.length; i++) smartSEMInfos = Array.deleteIndex(smartSEMInfos, i); /* For SmartSEM images ONLY: Remove unnecessary info title lines */ iSEMName = indexOfArrayThatStartsWith(smartSEMInfos, "Sem = ", -1); zeissSEMNameLine = smartSEMInfos[iSEMName]; zeissSEMName = substring(zeissSEMNameLine, 6); zeissSEMNames = newArray("Ultra 40 XB", "1540XB", "EVO 10"); zeissShortNames = newArray("EsB", "XB", "EVO10"); iMicroscope = indexOfArrayThatContains(zeissSEMNames, zeissSEMName, -1); if (iMicroscope >= 0) { zeissTIF = true; zeissShortName = zeissShortNames[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 == false) { 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") { /* Next remove unnecessary info title lines */ 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; microscopes = Array.concat(microscopes, feiSEMName); feiInfo = "FEI : " + feiSEMName + " SEM"; } else feiTIF = false; } } } if (zeissTIF) microscopes = Array.concat(microscopes, zeissShortName); else if (feiTIF) microscopes = Array.concat(microscopes, feiSEMName); 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; microscopes = Array.concat(microscopes, feiSEMName); 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; } } microscopes = Array.concat(microscopes, "dpi", "Frame_width", "Manual_input", "Clear_scale"); for (i = 0; i < ascMicroscopes.length; i++) { if (indexOfArray(microscopes, ascMicroscopes[i], -1) < 0) microscopes = Array.concat(microscopes, ascMicroscopes[i]); } for (i = 0; i < microscopes.length; i++) if (microscopes[i] == "") microscopes = Array.deleteIndex(microscopes, i); /* 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; Dialog.create("Choose microscope or calibration method \(" + macroL + "\)"); Dialog.addMessage("Image:" + getTitle() + "\nCalibration file \(" + calFileName + "\) from directory:\n" + dirCalibFile + "\nLast modified: " + fileDate, infoFontSize, infoColor); infoText = "ASC microscopes and calibration methods:_____\n\t\n"; if (zeissTIF) { if (zeissShortName == "EsB") infoText += EsBinfo; else if (zeissShortName == "XB") infoText += XBinfo; else if (zeissShortName == "EVO10") infoText += EVO10info; if (CZScale[0] >= 0) { infoText += "\n SmartSEM Zeiss Scale: Pixel size = " + CZScale[0] + " " + CZUnit + ", frame width = " + CZScale[1] + " pixels\n "; if (zeissShortName == "EVO10") infoText += "\n This scale is applied if \"EVO10\" is selected\n "; } else infoText += "\nSmartSEM scale not found"; } else if (feiTIF) infoText += feiInfo; else if (dsxFile) infoText += DSXinfo; else infoText += EsBinfo + "\n" + XBinfo + "\n" + EVO10info + "\n" + LEXTinfo + "\n" + DSXinfo; infoText += "\n" + FrameWidthinfo + "\n" + dpiinfo; Dialog.addMessage(infoText, infoFontSize, infoColor); if (selectionType == 5) { Dialog.addMessage(LineLengthinfo, infoFontSize, infoColor); microscopes = Array.concat(microscopes, "Line_length"); } else if (selectionType >= 0) { Dialog.addMessage(SelectionWidthinfo + "\n" + SelectionHeightinfo, infoFontSize, infoColor); microscopes = Array.concat(microscopes, "Selection_width", "Selection_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); else if (tableScale) iScope = indexOfArray(microscopes, "Scale_from_table", 0); else if (zeissTIF) iScope = indexOfArray(microscopes, zeissShortName, 0); else if (feiTIF) iScope = indexOfArray(microscopes, feiSEMName, 0); else if (endsWith(fileName, ".dsx")) iScope = indexOfArray(microscopes, "DSX1000", 0); else if (endsWith(fileName, ".vsi")) iScope = indexOfArray(microscopes, "BX41M", 0); else if (indexOf(fileName, "dpi") >= 0) iScope = indexOfArray(microscopes, "dpi", 0); else iScope = indexOfArray(microscopes, call("ij.Prefs.get", prefsNameKey + "lastScope", microscopes[1]), 1); Dialog.addRadioButtonGroup("Choose microscope or method:", microscopes, round(microscopes.length / 3.5), 4, microscopes[iScope]); Dialog.addCheckbox("Run in diagnostics mode", diagnostics); Dialog.show(); microscope = Dialog.getRadioButton(); diagnostics = Dialog.getCheckbox(); call("ij.Prefs.set", prefsNameKey + "lastScope", microscope); /* End of microscope selection dialog */ if (microscope == "Scale_from_table") run("Set Scale...", "distance=1 known=" + tablePW + " pixel=" + tablePAR + " unit=" + tableUnit); else { if (microscope == "EsB" || microscope == "XB" || microscope == "BX41M") frameFactor = true; else frameFactor = false; if (microscope != "Manual_input" && microscope != "Frame_width" && microscope != "Line_length" && microscope != "Clear_scale" && microscope != "Selection_width" && microscope != "Selection_height") { counter = 0; for (i = 0; i < entries; i++) { columns = split(calibrations[i], "\,"); /* csv file */ if (indexOf(columns[0], microscope) >= 0) { labels[counter] = columns[0]; pixels[counter] = parseFloat(columns[1]); distances[counter] = parseFloat(columns[2]); units[counter] = columns[3]; orFrameWidth[counter] = columns[4]; counter += 1; } } labels = Array.trim(labels, counter); pixels = Array.trim(pixels, counter); distances = Array.trim(distances, counter); units = Array.trim(units, counter); 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 = 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 (microscope == "LEXT") microscopeInfo = LEXTinfo; else if (microscope == "EsB" || microscope == "XB" || microscope == "EVO10") { if (microscope == "EsB") microscopeInfo = EsBinfo; else if (microscope == "XB") microscopeInfo = XBinfo; else if (microscope == "EVO10") microscopeInfo = EVO10info; 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 (microscope == "dpi") microscopeInfo = dpiinfo; else if (microscope == "DSX1000") microscopeInfo = DSXinfo; else if (microscope == feiSEMName) microscopeInfo = feiInfo; else microscopeInfo = "Microscope information unavailable"; Dialog.create("Choose " + microscope + " Calibration \(" + macroL + "\)"); Dialog.addMessage(microscopeInfo + "\nImage width = " + imageWidth + " pixels", infoFontSize, infoColor); if (microscope == "DSX1000") { /* 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 == "EVO10") { /* 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 */ } else { if (CZScale.length > 0) { if (microscope == "EsB" || microscope == "XB" && CZScale.length > 1) { if (CZScale[0] >= 0) { if (microscope == "EsB" && (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"); } } Dialog.show(); if (microscope == "DSX1000") { /* Get Olympus DSX1000 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("Pixel 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 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("Pixel size set for FEI microscope: " + distPerPixel + " " + unitChoice)); } /* End FEI FESEM options */ else if (microscope == "EVO10") { /* Get Zeiss EVO 10 LaB6 SEM options */ /* pixelWidth and unit extracted above before dialog */ unitChoice = Dialog.getRadioButton(); nonAnnoSel = Dialog.getCheckbox(); annoCrop = Dialog.getCheckbox(); /* End EVO10 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("Pixel size set for " + microscope + ": " + distPerPixel + " " + unitChoice)); /* End Zeiss EVO10 options */ } else { /* Older Zeiss SmartSEM options */ if (CZScale.length > 0 && microscope == "EsB" && (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); } 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]; if (microscope == "dpi") manDPI = Dialog.getNumber(); else manDPI = 0; if (manDPI > 0) { sUs = sensibleUnits(25.4 / manDPI, "mm"); run("Set Scale...", "distance=1 known=" + sUs[0] + " pixel=1 unit=" + sUs[1]); IJ.log(cleanLabel("Pixel size set from DPI entry: " + sUs[0] + " " + sUs[1])); } else { run("Set Scale...", "distance=" + pixelDistance + " known=" + distances[entry] + " pixel=1 unit=" + units[entry]); distPerPixel = distances[entry] / pixelDistance; IJ.log(cleanLabel("Pixel size set from chosen calibration value: " + distPerPixel + " " + units[entry] + " \(" + 1 / distPerPixel + " pixels/" + cleanLabel(units[entry]) + "\)")); } 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 calibration selection dialog */ } else { if (microscope == "Frame_width") { Dialog.create("Choose Full Frame_width \(" + macroL + "\)"); getDimensions(frameWidthPixels, null, null, null, null); getPixelSize(unit, 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("Pixel size set from frame width: " + frameWidthPixels + " " + unit)); } else if (microscope == "Clear_scale") run("Set Scale...", "distance=0 known=0 pixel=1 unit=pixel"); else { if (microscope == "Selection_width") distanceP = selWidth; else if (microscope == "Selection_height") distanceP = selHeight; else if (microscope == "Line_Length") distanceP = lengthPx; else distanceP = 0; Dialog.create("Set scale based on selection \(" + macroL + "\)"); Dialog.addNumber("Distance", distanceP, 6, 10, "pixels"); Dialog.addNumber("Calibrated distance", 0, 6, 10, "in scaled units"); Dialog.addNumber("Pixel aspect ratio", 1.0, 6, 10, ""); moreUnitChoices = Array.concat(unitChoices, oddballUnitChoices, "pixels"); Dialog.addChoice("Choose Unit", moreUnitChoices, moreUnitChoices[indexOfArray(moreUnitChoices, voxelUnit, "pixels")]); // Dialog.addString("Unit of length", voxelUnit, 10); if (slices > 1) Dialog.addNumber("Z axis voxel depth", 0, 6, 10, "in scaled units"); Dialog.show; distanceP = Dialog.getNumber; distanceC = Dialog.getNumber; distanceA = Dialog.getNumber; /* width/height */ unit = Dialog.getChoice; if (slices > 1) zVoxel = Dialog.getNumber; else zVoxel = 0; pixelWidth = distanceC / distanceP; pixelHeight = pixelWidth / distanceA; 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=" + distanceA + " unit=" + unit); } } } 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)); } } } selectImage(orImageID); showStatus("ASC set scale completed", "flash image green"); /* End of ASC-Set_Scale_from_Calibrations macro */ } /* History: v180820: First version Peter J. Lee NHMFL. v180917: Calculates scale for different original frame sizes. v181002: Added import of CZ header scale for EsB and XB images. v181003: Added 512 pixel Frame_width for SEM images. v190213: Magnification guessed from image title contents. v190219: Added depth to Manual_input for stacks. v190222: Added 'k' suffix guess for thousands abbreviation in image label. v190306 Fixed l148 typo. v190322: Prioritized local path calibration file and deemphasized z-file to speed use in non-networked computer. v190403: Saves last used user microscope for repeated use. v190418: Full Frame_width Option added. v190423: Added calibrate to selected line option. v190430: Added clear scale option. v190528: ExtractMag function completely revised to return mag if followed by "x" etc. as well as preceded. v190912: Combination Boolean error fix for 1.52q v191011: Added 6x6 binning for BX41 v200609: Added import of values embedded in active table v201130: Corrected to row 0 import v210521: Added scale based on selection bound box option v210630: Adds Olympus DSX format v210730: Adds Phenom XL G2 format v210903: Changed some naming within the DSX function to be less confusing v211012: Copies net calibration file to local location if it not found there v211213: Creates local directory if it does not exist v220310-1: Improved handling of stacks functions updated: 5/16/2022 1:15 PM v220617: Added EVO10 and auto-selection of Zeiss SEM from TIFF header. REQUIRED new function: getSEMFromSmartSEMHeader v220722: Replaced windows slash with universal file separator in directory paths. v220801: Reworked to import SmartSEM header only once. v220802: Uses string buffer for header extraction v220804-5: Reworked to make more universal. Requires zapGremlins and new functions: indexOfArrayThatStartsWith and sensibleUnits. v220811: Works better with images containing previously imported info. v220811_f1 updates ZapGremlins function. v220815 corrected 'pixel' to 'pixels'. v220817: Corrects bad TIFF file handling. v220824: Improves handling of images w/o headers. v220920: Frame width option now works again. v221004 Replace '&' variable shortcuts which seem to cause issues on some systems v221017: Fixed channels typo introduced in v221004 v230110-11: DSX info fix. and DSX montage scale option. v230120: DSX calibration read directly for ImageJ imported info. v230206: Added logging of the set scale for some microscopes (useful information for mosaics). v230224: Added new local path for ASC settings. v230510: DSX aspect ratio supported. v230516 Change name of getEXIF function v230809: Added VueScan dpi import. v230810: Minor change to text. */ /* ( 8(|) ( 8(|) Functions @@@@@:-) @@@@@:-) */ 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 */ 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 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 */ 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, "(? 1) string = substring(string, 0, indexOf(string, "mý") - 1) + getInfo("micrometer.abbreviation") + fromCharCode(178); return string; } 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, "?"); 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 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 */ iEndTag = lastIndexOf(tag, endTag); if (iEndTag >= 0) { if (iEndTag + endBuffer > tag.length) endBuffer = tag.length - iEndTag; tag = substring(tag, 0, iEndTag + endBuffer); iStartTag = indexOf(tag, beginTag); if (iStartTag >= 0) { tag = substring(tag, 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 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 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(); exifPrefix = "EXIF Metadata for "; exifTitle = exifPrefix + 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 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 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 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. */ 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 (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 waitForOpenWindow(windowName, testWait, minWait, maxWait) { /* v230511: 1st version. v240222: Flash green on completion. */ wait(minWait); maxIterations = maxWait / testWait; for (i = 0; i < maxIterations; i++) { showProgress(i, maxIterations); showStatus("Waiting for " + windowName); if (!isOpen(windowName)) wait(testWait); else i = maxIterations; } showStatus("The wait for " + windowName + " is over", "flash green"); } 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; }