|
| 1 | +#@Double(label="Crop width/height (XY, calibrated units):", value=100, min=1) boxXY |
| 2 | +#@Double(label="Crop depth (Z, calibrated units):", value=100, min=0) boxZ |
| 3 | +#@boolean(label="Use secondary (filtered) layer, if loaded") useSecondary |
| 4 | +#@SNTService snt |
| 5 | +#@UIService ui |
| 6 | + |
| 7 | +/** |
| 8 | + * file: Crop_Around_Marker.groovy |
| 9 | + * author: Tiago Ferreira |
| 10 | + * info: Carves out a small region around the last marked location in SNT's Stream-mode BigDataViewer/BigVolumeViewer |
| 11 | + * and materializes it as a regular, in-RAM ImagePlus. Streamed big-data sources (N5/OME-Zarr/IMS/BDV-XML) are |
| 12 | + * lazy by design, so most classic ImageJ/SNT operations that require full pixel access can't run on them |
| 13 | + * directly. This script bridges that gap for the case where only a small, local region is actually needed. |
| 14 | + * |
| 15 | + * Usage: Press 'M' in the BVV/BDV canvas to mark the location of interest (see the viewer's Markers pane), then run |
| 16 | + * this script: it crops around the most recently placed arker, however long ago it was placed. |
| 17 | + * |
| 18 | + * @see Locations_Montage.groovy, for batch-cropping many locations from a file on disk |
| 19 | + */ |
| 20 | + |
| 21 | +snt.requireVersion("5.0.14") // SNT version required to run this script |
| 22 | + |
| 23 | +if (!snt.isActive() || snt.getUI() == null) { |
| 24 | + ui.showDialog("SNT does not appear to be running.", "Error") |
| 25 | + return |
| 26 | +} |
| 27 | +plugin = snt.getInstance() |
| 28 | +viewer = snt.getUI().getActiveBigViewer() |
| 29 | +if (viewer == null) { |
| 30 | + ui.showDialog("This script requires SNT's Stream mode (BVV/BDV) to be active.", "No Big Viewer Active") |
| 31 | + return |
| 32 | +} |
| 33 | +if (useSecondary && !plugin.isSecondaryDataAvailable()) { |
| 34 | + ui.showDialog("No secondary (filtered) layer is currently loaded. Using the main image instead.", |
| 35 | + "Secondary Layer Unavailable") |
| 36 | +} |
| 37 | +data = (useSecondary && plugin.isSecondaryDataAvailable()) ? plugin.getSecondaryData() : plugin.getLoadedData() |
| 38 | +if (data == null) { |
| 39 | + ui.showDialog("No image data appears to be loaded.", "Error") |
| 40 | + return |
| 41 | +} |
| 42 | + |
| 43 | +// Retrieve the last marker placed in the viewer (world/calibrated coordinates), then convert to pixels |
| 44 | +markerPos = viewer.getMarkerManager().getLastPosition() |
| 45 | +if (markerPos == null) { |
| 46 | + ui.showDialog("No marker has been placed yet. Press 'M' in the viewer to mark a location, " |
| 47 | + + "then re-run this script.", "No Marker Found") |
| 48 | + return |
| 49 | +} |
| 50 | +cx = Math.round(markerPos.getX() / plugin.getPixelWidth()) |
| 51 | +cy = Math.round(markerPos.getY() / plugin.getPixelHeight()) |
| 52 | +cz = Math.round(markerPos.getZ() / plugin.getPixelDepth()) |
| 53 | + |
| 54 | +// Compute crop bounds (pixels), clamped to the data's extent |
| 55 | +ndims = data.numDimensions() |
| 56 | +hw = Math.round((boxXY / 2) / plugin.getPixelWidth()) |
| 57 | +hd = Math.round((boxZ / 2) / plugin.getPixelDepth()) |
| 58 | +min = new long[ndims] |
| 59 | +max = new long[ndims] |
| 60 | +min[0] = Math.max(0, cx - hw) |
| 61 | +max[0] = Math.min(data.dimension(0) - 1, cx + hw) |
| 62 | +min[1] = Math.max(0, cy - hw) |
| 63 | +max[1] = Math.min(data.dimension(1) - 1, cy + hw) |
| 64 | +if (ndims > 2) { |
| 65 | + min[2] = Math.max(0, cz - hd) |
| 66 | + max[2] = Math.min(data.dimension(2) - 1, cz + hd) |
| 67 | +} |
| 68 | +if (min[0] > max[0] || min[1] > max[1] || (ndims > 2 && min[2] > max[2])) { |
| 69 | + ui.showDialog("The last marker's position falls outside the current image data. Place a new " |
| 70 | + + "marker (M key) over a valid location and try again.", "Invalid Position") |
| 71 | + return |
| 72 | +} |
| 73 | + |
| 74 | +// Crop and materialize into a regular, in-RAM ImagePlus (inspired by n5-viewer's CropController: |
| 75 | +// https://github.com/saalfeldlab/n5-viewer/blob/master/src/main/java/org/janelia/saalfeldlab/n5/bdv/CropController.java) |
| 76 | +cropped = Views.zeroMin(Views.interval(data, min, max)) |
| 77 | +title = "Crop_x${cx}_y${cy}_z${cz}" |
| 78 | +imp = ImgUtils.raiToImp(cropped, title) |
| 79 | +imp.setCalibration(plugin.getCalibration()) |
| 80 | +imp.resetDisplayRange() |
| 81 | +imp.show() |
| 82 | + |
| 83 | +dimsStr = (0..<ndims).collect { max[it] - min[it] + 1 }.join("x") |
| 84 | +posStr = (0..<ndims).collect { [cx, cy, cz][it] }.join(", ") |
| 85 | +println("Extracted ${dimsStr} crop centered at pixel (${posStr})") |
| 86 | +println("This crop is now regular, in-RAM data: feed it to Weka classifier training, Sholl " |
| 87 | + + "analysis, or any command that requires full random access -- none of which run " |
| 88 | + + "directly on streamed big data.") |
| 89 | + |
| 90 | + |
| 91 | +// Imports below |
| 92 | +import net.imglib2.view.Views |
| 93 | +import sc.fiji.snt.util.ImgUtils |
0 commit comments