/* This macro unravels an outline (aimed at curved surfaces) so that x coordinates are relative to adjacent pixels and y coordinates are relative to a single point (or line - not activated yet). It is assumed that a pixel-width outline has already been created. Inspired by an idea proposed by Jeremy Adler from an imageJ mailing list post on Monday, August 15, 2022 4:17 AM v220825 1st version. v221003 Adds option to create pseudo-height map for analysis in 3D analysis software like Gwyddion. Also report for evaluation length based in shape v221004 Replaces "None" with "Total_pixel-pixel_length" in evaluation length options v221005 Default median smoothing radius determined from initial bounding box. Map now has square pixels. Smoothed image can be generated independent of ref length. More reference location options. v221014 Restored ability to analyze line segments. v221107 Skeletonize is optional. v221108 Added initial menu to simplify instructions. Replaced sample length terminology with evaluation length to avoid confusion with sampling length. Added additional output columns. v221110 Outputs information about non-sequenced pixels (those that were missed from the continuous line because they were not the closest adjacent pixel in the search order. v221111 Evaluation lengths and highlighting of non-sequenced pixels are now shown as overlays on the pixel-sequence map if there are any non-sequenced pixels (even if the map was not requested). v221128 Incorrect start pixel fixed. Can now set intensity range for cut off and output; v221202 Replaced binary median with binary mean+threshold to speed up operation as suggested by post to ImageJ mailing list: https://imagej.net/list.html by Herbie Gluender, 29. Nov 2022 v230210 Fixed excessive space buffer creation and angle offset now relative to coordinate chosen in "Reference coordinate" dialog. Map can now be output using rotational sequence. v230211 Adds angle increment columns and directional filtering (useful if you have re-entrant angles). v230213 Adds column of sequential distance normalized to the evaluation length and adds option to output direction filtered results to csv file. v230214 Adds directional continuity flag to primary Results table, overlay display of filtered sequence, simple color options for overlays. Add zero degree start option and fixes disappearing Results window. v230228 Adds directional directional output from v230214 to horizontal and vertical lines - and fixes issues created with horizontal and vertical lines produced by v230214. Added color choices for overlays. Ra and Rq corrected relative to meanline. v230301 Changed name of pArea/pPerimeter ratio and more cosmetic changes to Dialog 1. b) Output menu cosmetic changes. v230303 Changed default spline fit for horizontal and vertical lines to 10% of pixels from 2% of perimeter. v230306 Option to crop overlay output image back to original dimensions of input image and sets this as default. f1-2: Updates stripKnownExtensionFromString function. f3: Updates indexOf functions. F4: getColorArrayFromColorName_v230908. F8: Replaced function: pad. F9: Updated getColorFromColorName function (012324). F10: updated function unCleanLabel. */ macroL = "Unravel_interface_v230306-f10.ijm"; saveSettings(); /* required for restoreExit */ setBatchMode(true); oTitle = getTitle; oID = getImageID(); getPixelSize(unit, pixelWidth, pixelHeight); lcf=(pixelWidth+pixelHeight)/2; /* ---> add here the side size of 1 pixel in the new calibrated units (e.g. lcf=5, if 1 pixels is 5mm) <--- */ if(lcf!=1) gotScale = true; else gotScale = false; nTitle = stripKnownExtensionFromString(oTitle); nTitle += "_unrav"; dir = getInfo("image.directory"); run("Duplicate...", "title=&nTitle ignore"); if (!is("binary") || is("Inverting LUT") || getPixel(0,0)==0){ if(getBoolean("This macro expects a binary image with white background, would you like to try a apply a quick fix?")) toWhiteBGBinary(nTitle); else ("Goodbye"); } tempID = getImageID(); getDimensions(oImageW, oImageH, oChannels, oSlices, oFrames); run("Select None"); run("Create Selection"); getSelectionBounds(minX, minY, widthS, heightS); /* The following section helps classify the object */ borders = newArray(minX, minY, oImageH-heightS-minY,oImageW-widthS-minX); Array.getStatistics(borders,minBorder,maxBorder,null); pArea = getValue("Area raw"); oSolidity = getValue("Solidity"); oAR = getValue("AR"); oAngle = getValue("Angle"); pPerimeter = getValue("Perim. raw"); pxPpxARatio = pPerimeter/pArea; run("Select None"); run("Fill Holes"); getRawStatistics(nPixels, meanPx, minPx, maxPx); pFArea = (maxPx/meanPx-1)*nPixels; run("Undo"); if (pxPpxARatio>0.95){ if (pFArea/pArea>=1.1) objectType = "Continuous_outline"; else if (oSolidity<1){ if (oAngle<45 || oAngle>135) objectType = "Horizontal_line"; else objectType = "Vertical_line"; } else objectType = "Something_else"; } else if (pFArea/pArea<1.1 || oSolidity>0.5) objectType = "Solid_object"; if (objectType=="Solid_object") safeMargin = round(pPerimeter/40); else { run("Create Selection"); setOption("BlackBackground", false); run("Skeletonize"); pPerimeter = getValue("Perim. raw"); pArea = getValue("Area raw"); run("Undo"); safeMargin = round(pArea/40); run("Select None"); } if (safeMargin>oImageW && safeMargin>oImageH) showMessage("Problem with safeMargin size \(" + safeMargin + "\), minimum border space = " + minBorder); /* Make sure canvas is large enough to accommodate any smoothing or fits */ if (minXmaxInt) exit ("Start pixel \(" + x + ", " + y + "\) intensity = " + startPixelInt); xSearchPxlsA = newArray(); ySearchPxlsA = newArray(); dSearchPxls =newArray(); for(i=(0-kernelR),k=0; i0){ radInc = radianAngles[i] - radianAngles[i-1]; if(radInc>PI) radInc -= 2*PI; else if (radInc<-PI) radInc += 2*PI; radianIncrements[i] = radInc; degreeIncrements[i] = radInc * 180/PI; } degreeAngles[i] = radianAngles[i] * 180/PI; } Array.getStatistics(degreeIncrements, degreeIncrements_min, degreeIncrements_max, degreeIncrements_mean, degreeIncrements_stdDev); if(degreeIncrements_mean<0){ clockwiseIncr = true; IJ.log("Apparent direction clockwise; mean advance " + degreeIncrements_mean + " degrees"); } else { clockwiseIncr = false; IJ.log("Apparent direction anticlockwise; mean advance " + degreeIncrements_mean + " degrees"); } } relDistances = newArray(); if (startsWith(objectType,"Vert")) for (i=0;iorthDist){ if(i>0){ orthDist = orthOffset; oneDirOrthOffsets[k] = orthDist; oneDirOrthIncrements[k] = orthDist - oneDirOrthOffsets[k-1]; } Table.set("Directional_continuity",i,true); oneDirOutPTPDists[k] = outPTPDists[i]; oneDirOutPTPDistTotals[k] = outPTPDistTotals[i]; oneDirOutRelDists[k] = outRelDists[i]; oneDirOutRelDistFMeans[k] = outRelDistFMeans[i]; oneDirOutRelDistFMeanSqs[k] = outRelDistFMeanSqs[i]; oneDirOutPTPDistTotalEvals[k] = outPTPDistTotalEvals[i]; oneDirXSeqCoords[k] = xSeqCoords[i]; oneDirYSeqCoords[k] = ySeqCoords[i]; oneDirOriginalIDs[k] = i; k++; } else Table.set("Directional_continuity",i,false); } } else { if(clockwise) angle = 360; else angle = -1; oneDirOutDegreeOffsets = newArray(0,0); oneDirOutDegreeIncrements = newArray(0,0); for(i=0,k=0; iangle)){ if(i>0){ angle = degreeOffsets[i]; oneDirOutDegreeOffsets[k] = angle; oneDirOutDegreeIncrements[k] = abs(oneDirOutDegreeOffsets[k]-oneDirOutDegreeOffsets[k-1]); } Table.set("Directional_continuity",i,true); oneDirOutPTPDists[k] = outPTPDists[i]; oneDirOutPTPDistTotals[k] = outPTPDistTotals[i]; oneDirOutRelDists[k] = outRelDists[i]; oneDirOutRelDistFMeans[k] = outRelDistFMeans[i]; oneDirOutRelDistFMeanSqs[k] = outRelDistFMeanSqs[i]; oneDirOutPTPDistTotalEvals[k] = outPTPDistTotalEvals[i]; oneDirXSeqCoords[k] = xSeqCoords[i]; oneDirYSeqCoords[k] = ySeqCoords[i]; oneDirOriginalIDs[k] = i; k++; } else Table.set("Directional_continuity",i,false); } } fPixN = k; IJ.log (fPixN + " direction-filtered pixels out of original " + seqPixN); if (filteredNorm){ Array.getStatistics(oneDirOutRelDists,minOutRel,null,meanOutRel,null); for (i=0;i>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 hideResultsAs(deactivatedResults) { if (isOpen("Results")) { /* This swapping of tables does not increase run time significantly */ selectWindow("Results"); IJ.renameResults(deactivatedResults); } } 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; i0){ 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 columnOperation(columnNames,newColumnNames,operator,operand){ lFunction = "columnOperation_v230306"; coords = newArray("Seq_coord_x","Seq_coord_y"); if (columnNames.length!=newColumnNames.length) exit(lFunction + ": Unequal column name array lengths"); for(i=0;i=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