macro "Image-stitch manual helper" { /* Allows for manual control if image-stitching by point pairs. v231114: 1st working version, Peter J. Lee Applied Superconductivity Center, Florida State University based on Images_to_Stack_Plus_v231113.ijm" v231115: Completed saved lines code. Fixed error on common name loop. v231116: More consistent variable names and addition of fully overlapping frames. v231127: Removed "!" from intermediate showStatus command to allow getLine, makeLine and run("SelectNone") selectWindow to work reliably. F1: Updated safeSaveAndClose */ macroL = "Image-Stitch_Manual_Helper_v231127a-f1.ijm"; ascPrefsKey = "asc.ImageStitchManualHelper.Prefs."; close("temp_*"); /* Cleanup if crashed last time */ saveSettings(); if (nImages<2) exit("Need more than one open image to stitch"); imageList = removeDuplicatesInArray(getList("image.titles"), true); imageN = lengthOf(imageList); minTL = imageList[0].length; for (i=1; i0 && imageN>4){ Dialog.addToSameRow(); addRow = -1; } Dialog.addChoice("tile " + i+1 + " \(" + shortCommon + "\)", imageShortList, imageShortList[i]); } } else { Dialog.addChoice("Choose top tile \(1\)", imageList, imageList[0]); Dialog.addCheckbox("Set tile label as unique name as listed above", true); } Dialog.setInsets(0, 20, 0); tileOptions = newArray("Reverse this order", "Manually crop montage", "Compute overlap", "Diagnostic mode"); tileOptionChecks = newArray(); tileOptionChecks = Array.concat(tileOptionChecks, call("ij.Prefs.get", ascPrefsKey + "reverseOrder", false)); tileOptionChecks = Array.concat(tileOptionChecks, call("ij.Prefs.get", ascPrefsKey + "manCrop", false)); tileOptionChecks = Array.concat(tileOptionChecks, call("ij.Prefs.get", ascPrefsKey + "computeOverlap", false)); tileOptionChecks = Array.concat(tileOptionChecks, call("ij.Prefs.get", ascPrefsKey + "diagnostics", false)); Dialog.addCheckboxGroup(1, 4, tileOptions, tileOptionChecks); manualAlignInfo = "Only vertical or horizontal strips are currently available in this test version." + "\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); Dialog.addNumber("Horizontal overlap", call("ij.Prefs.get", ascPrefsKey + "xOverlap", NaN), 1, 3, "%, leave blank for vertical strip"); Dialog.addNumber("Vertical overlap", call("ij.Prefs.get", ascPrefsKey + "yOverlap", NaN), 1, 3, "%, leave blank for horizontal strip"); /* Note that margins work for this manual alignment but IJ auto-alignment thinks that margins (any value including NaN) are dominant alignment features. */ Dialog.show(); tileN = Dialog.getNumber(); mosaicName = Dialog.getString(); tilesOrdered = newArray(); tilesShortOrdered = newArray(); if (imageN<30){ for (i=0; i7"); Dialog.addNumber("Regression value:",0.3,1,4,"Assumed non-overlapping if below this. 'Good' values are typically >0.7"); Dialog.addNumber("max/avg:",3.5,1,4,"Default threshold error for discarding is 2.5"); Dialog.addNumber("Absolute value:",3.5,1,4,"Links removed if the absolute displacement is greater \(default 3.5\)"); fusionAreas = Array.resample(Array.getSequence(2), 11); Dialog.addRadioButtonGroup("Blended Area Fraction, TBH this doesn't seem to have much impact \(default is 0.2\):", fusionAreas, 1, 5, "0.2"); if (is("grayscale")) isMonochrome = true; checkboxOptions = newArray("Copy 1st tile metadata to stitched image?","Decompose composite output"); checkboxDefaults = newArray(true, true); if(getSliceNumber()>1){ if (Stack.isHyperstack){ hyper = true; checkboxOptions = Array.concat("Add tiles as ROIs?", checkboxOptions); checkboxDefaults = Array.concat(true, checkboxDefaults); } } else hyper = false; colN = 2; rowN = Math.ceil(checkboxOptions.length/colN); Dialog.setInsets(5, 15, 0); Dialog.addCheckboxGroup(rowN, colN, checkboxOptions, checkboxDefaults); Dialog.addFile("Load previously saved line set", ""); Dialog.show(); blendingOption = Dialog.getChoice(); call("ij.Prefs.set", ascPrefsKey+"blending", blendingOption); fusionVal = Dialog.getNumber(); regressionVal = Dialog.getNumber(); maxAvgVal = Dialog.getNumber(); absVal = Dialog.getNumber(); fusionFraction = Dialog.getRadioButton(); bshScript = "mpicbg.stitching.fusion.BlendingPixelFusion.fractionBlended = " + fusionFraction; IJ.log("Temporary fractionBlend applied:") eval("bsh", bshScript); if (hyper) addROIs = Dialog.getCheckbox(); else addROIs = false; transferMetadata = Dialog.getCheckbox(); compoConvert = Dialog.getCheckbox(); savedLinesFile = Dialog.getString(); /* Currently only single-width strips are supported */ /* End of main dialog */ gotLines = false; if (savedLinesFile!=""){ if (File.exists(savedLinesFile)){ savedLineSet = File.openAsString(savedLinesFile); savedLines = split(savedLineSet,"\n"); if (lengthOf(savedLines)==2*(tileN-1)) gotLines = true; else IJ.log("Saved lines not used because " + lengthOf(savedLines) + "were found but " + 2*(tileN-1) + "are needed"); } } call("ij.Prefs.set", ascPrefsKey + "lastImageStartWidth", Image.width); call("ij.Prefs.set", ascPrefsKey + "lastImageStartHeight", Image.height); tileNames = newArray; for (i=0; i0 && commonL2) makeLine(x1, y1, x2, y2, 10); else makeLine(0.1 * imWidth, 0.1 * imHt, 0.9 * imWidth, 0.9 * imHt, 10); } } Roi.setStrokeColor("#50ff0000"); Roi.setStrokeWidth(3); updateDisplay(); showStatus("Target tile for alignment", "flash image red 1000ms"); while (getTitle!=tilesOrdered[i]) 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 cyan 1000ms"); run("Select None"); if (gotLines){ restoredCoords = split(savedLines[sLines], ","); run("Select None"); while (selectionType!=5) makeLine(parseInt(restoredCoords[0]), parseInt(restoredCoords[1]), parseInt(restoredCoords[2]), parseInt(restoredCoords[3]), 5); sLines++; } else if (xTile){ x12 = imWidth * xOverlap / 2; run("Select None"); while (selectionType!=5) makeLine(x12, 0.1 * imHt, x12, 0.9 * imHt, 13); } else if (yTile){ y12 = imHt * yOverlap / 2; run("Select None"); while (selectionType!=5) makeLine(0.1 * imWidth, y12, 0.9 * imWidth, y12, 13); /* makeLine(x1, y1, x2, y2, lineWidth) */ } else { run("Select None"); while (selectionType!=5) { if (i>2) makeLine(x1, y1, x2, y2, 13); else makeLine(0.1 * imWidth, 0.1 * imHt, 0.9 * imWidth, 0.9 * imHt, 13); } } Roi.setStrokeColor("#5000ffff"); Roi.setStrokeWidth(3); updateDisplay(); 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; waitForUser(tileType + ": set #" + i + ": Line Align Instructions", lineInstructions); while(getTitle!=tilesOrdered[i-1]) selectWindow(tilesOrdered[i-1]); getLine(x1, y1, x2, y2, lineWidth); lineCoords += "" + x1 + "," + y1 + "," + x2 + "," + y2 + "\n"; cXT = (x1 + x2)/2; cYT = (y1 + y2)/2; run("Select None"); run("Put Behind [tab]"); while(getTitle!=tilesOrdered[i]) selectWindow(tilesOrdered[i]); getLine(x1, y1, x2, y2, lineWidth); lineCoords += "" + x1 + "," + y1 + "," + x2 + "," + y2 + "\n"; cXS = (x1 + x2)/2; cYS = (y1 + y2)/2; run("Select None"); offsetX += cXT - cXS; offsetY += cYT - cYS; thisTileConfig = tilesOrdered[i] + "; ; \(" + d2s(offsetX, 3) + ", " + d2s(offsetY, 3) + "\)"; tileConfig += thisTileConfig; dir = getInfo("image.directory"); tileConfigFP += "" + dir + thisTileConfig; if (i=0){ run("Crop"); run("Select None"); } } run("Scale to Fit"); setBatchMode("exit and display"); Color.setBackground(preBackground); Color.setForeground(preForeground); restoreSettings(); IJ.log("Completed " + macroL + ":\n" + mosaicName); showStatus(macroL + " completed", "flash green"); } /* ( 8(|) ( 8(|) Functions @@@@@:-) @@@@@:-) */ 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; ifileSaveName.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 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=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; i0){ 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; i0){ if(!endsWith(protectedPath,fS)) protectedPath += fS; string = protectedPath + string; } return string; } function waitForOpenWindow(windowName, testWait, minWait, maxWait) { /* v230511: 1st version v231114: Added fail status and return */ wait(minWait); maxIterations = maxWait/testWait; for (i=0; i