|
| 1 | +#@String(value = "<HTML>This script assembles a montage of small image neighborhoods centered on each<br>tip of a reconstruction. Works on both regular (e.g., TIFF) and pyramidal images<br>(e.g., OME-Zarr, N5).", visibility = "MESSAGE") msg |
| 2 | +#@File(required = false, label = "Reconstruction file (leave empty for demo)") treeFile |
| 3 | +#@File(required = false, label = "Image file (leave empty for demo)") imgFile |
| 4 | +# @int(label = "Window XY size (in pixels)", min = 4) windowXY |
| 5 | +# @int(label = "Window Z thickness (in pixels, 1=2D)", min = 1) windowZ |
| 6 | +#@SNTService sntService |
| 7 | +#@UIService uiService |
| 8 | + |
| 9 | + |
| 10 | +/** |
| 11 | + * Builds a montage of all the end-points (tips) of a reconstruction (Tree). |
| 12 | + * |
| 13 | + * Uses sc.fiji.snt.analysis.NodeCollector, which: |
| 14 | + * - operates on an ImgPlus, working directly with lazy, pyramid sources (OME-Zarr, N5) w/o loading the whole volume |
| 15 | + * - pads out-of-bounds crops with zeros, so the resulting montage forms a uniform grid |
| 16 | + * - defaults the channel/frame to the tip's owning Path (overridable) |
| 17 | + * |
| 18 | + * TF 20260609 |
| 19 | + */ |
| 20 | + |
| 21 | +import sc.fiji.snt.Tree |
| 22 | +import sc.fiji.snt.analysis.NodeCollector |
| 23 | +import sc.fiji.snt.util.ImgUtils |
| 24 | + |
| 25 | + |
| 26 | +// Abort if running an old version |
| 27 | +sntService.requireVersion("5.0.11") |
| 28 | + |
| 29 | +def tree, imgPlus |
| 30 | + |
| 31 | +// Load data |
| 32 | +if (treeFile && imgFile) { |
| 33 | + // Use SCIFIO to load ImgPlus (both regular and pyramid file formats supported) |
| 34 | + imgPlus = ImgUtils.open(imgFile.getAbsolutePath()) |
| 35 | + tree = Tree.fromFile(treeFile.getAbsolutePath()) |
| 36 | +} else { |
| 37 | + // No data specified: Run on demo dataset |
| 38 | + imgPlus = sntService.demoImgPlus("OP_1") |
| 39 | + tree = sntService.demoTree("OP_1") |
| 40 | + windowXY = 40 |
| 41 | + windowZ = 1 |
| 42 | +} |
| 43 | +if (!tree || !imgPlus) { |
| 44 | + uiService.showDialog("Invalid file paths or demo data not available.") |
| 45 | + return |
| 46 | +} |
| 47 | + |
| 48 | +// Tree must know its source image so that tip coordinates can be unscaled against the right calibration |
| 49 | +tree.assignImage(imgPlus) |
| 50 | + |
| 51 | +// If the window depth is a slab of multiple planes, we will project it (maximum intensity projection) |
| 52 | +def doMIP = (windowZ > 1) ? NodeCollector.Projection.MIP_Z : NodeCollector.Projection.NONE |
| 53 | + |
| 54 | +// Now we can assemble the NodeCollector... |
| 55 | +def collector = NodeCollector.fromTips(imgPlus, tree) |
| 56 | +collector.setWindow(windowXY, windowZ) |
| 57 | +collector.setProjection(doMIP) |
| 58 | + |
| 59 | +//... and retrieve the montage: |
| 60 | +def montage = collector.getMontage( |
| 61 | + -1, // no. of columns (-1=auto) |
| 62 | + -1, // no. of columns (-1=auto) |
| 63 | + 1, // scaling factor for each tile in the montage |
| 64 | + 2, // width of the border around each tile |
| 65 | + false) // label each tile? |
| 66 | +montage.show() |
| 67 | + |
| 68 | +// Other things you can do with the same builder: |
| 69 | +// |
| 70 | +// // Branch-point montage |
| 71 | +// NodeCollector.fromBranchPoints(imgPlus, tree).setWindow(20, 1).getMontage().show() |
| 72 | +// |
| 73 | +// // Every 10th node along all paths |
| 74 | +// NodeCollector.fromNodes(imgPlus, tree, 10).setWindow(20, 1).getMontage().show() |
| 75 | +// |
| 76 | +// // Roots of a population of Trees |
| 77 | +// NodeCollector.fromRoots(imgPlus, listOfTrees).setWindow(20, 1).getMontage().show() |
| 78 | +// |
| 79 | +// // Arbitrary points (bookmarks, seeds, ...) |
| 80 | +// NodeCollector collector = new NodeCollector(imgPlus, myPointsCollection) |
| 81 | +// collector.setWindow(20, 5).setProjection(NodeCollector.Projection.MIP_Z) |
| 82 | +// collector.getStack().show() // just the stack, no montage |
| 83 | +// |
| 84 | +// // Lazy crops as RAIs (without actual materialization of the volume), e.g. for further imglib2 work: |
| 85 | +// def crops = NodeCollector.fromTips(imgPlus, tree).setWindow(20, 1).getCrops() |
0 commit comments