%classpath config resolver scijava.public https://maven.scijava.org/content/groups/public %classpath add mvn net.imagej imagej 2.0.0-rc-71 ij = new net.imagej.ImageJ() "ImageJ ${ij.getVersion()} is ready to go." clown = ij.io().open("https://imagej.net/images/clown.png") import net.imglib2.interpolation.randomaccess.NLinearInterpolatorFactory scaleFactors = [0.5, 0.5, 1] // Reduce X and Y to 50%; leave C dimension alone. interpolationStrategy = new NLinearInterpolatorFactory() image = ij.op().run("scaleView", clown, scaleFactors, interpolationStrategy) import net.imglib2.FinalInterval w = image.dimension(0); h = image.dimension(1) slice = FinalInterval.createMinSize(0, 0, 0, w, h, 1) grayImage = ij.op().run("crop", image, slice, true) image32 = ij.op().convert().float32(grayImage) import net.imglib2.RandomAccessibleInterval tile = { images -> int[] gridLayout = images[0] in List ? [images[0].size, images.size] : // 2D images list [images.size] // 1D images list RandomAccessibleInterval[] rais = images.flatten() ij.notebook().mosaic(gridLayout, rais) } import net.imagej.ops.OpUtils opsByNS = [:] ij.op().ops().each{op -> ns = OpUtils.getNamespace(op) name = OpUtils.stripNamespace(op) if (!opsByNS.containsKey(ns)) opsByNS.put(ns, name) else opsByNS.put(ns, opsByNS.get(ns) + ', ' + name) } opsByNS.put('', opsByNS.remove(null)) ij.notebook().display(opsByNS) ij.op().help('gauss') // Smudge it up horizontally. double[] horizSigmas = [8, 0, 0] horizGauss = ij.op().filter().gauss(image, horizSigmas) // And now vertically. double[] vertSigmas = [0, 8, 0] vertGauss = ij.op().filter().gauss(image, vertSigmas) // We can also blur the channels. double[] channelSigmas = [0, 0, 1] channelGauss = ij.op().filter().gauss(image, channelSigmas) ij.notebook().display([["image":image, "horizontal blur":horizGauss, "vertical blur":vertGauss, "channel blur":channelGauss]]) ij.op().help("create.img") def info(name, image) { imageType = image.firstElement().getClass().getSimpleName() name + " = " + image.dimension(0) + " x " + image.dimension(1) + " x " + image.dimension(2) + " : " + imageType + "\n" } // Create an empty image of the same size as an existing image. empty = ij.op().run("create.img", image) // Create an empty image based on another image, but of different type. import net.imglib2.type.logic.BitType bitType = ij.op().run("create.img", image, new BitType()) // Create an image from scratch. import net.imglib2.type.numeric.real.FloatType smallFloat = ij.op().run("create.img", [25, 17, 2], new FloatType()) info("Original ", image) + info("Empty one", empty) + info("Bit image", bitType) + info("Small float", smallFloat) ij.op().help("copy.rai") copy = ij.op().run("copy.rai", image) ij.op().help("equation") import net.imglib2.type.numeric.integer.UnsignedByteType sinusoid = ij.op().run("create.img", [150, 100], new UnsignedByteType()) formula = "63 * (Math.cos(0.3*p[0]) + Math.sin(0.3*p[1])) + 127" ij.op().image().equation(sinusoid, formula) // Here we have a gradient ramp in an array. w = 160; h = 96 byte[] pixels = new byte[w * h] for (y in 0..h-1) { for (x in 0..w-1) { pixels[y * w + x] = x + y } } // Wrap the array into an image. import net.imglib2.img.array.ArrayImgs ramp = ArrayImgs.unsignedBytes(pixels, w, h) sinusoid32 = ij.op().run("create.img", [150, 100]) formula = "63 * (Math.cos(0.3*p[0]) + Math.sin(0.3*p[1])) + 127" ij.op().image().equation(sinusoid32, formula) ij.notebook().display([ "geometricMean": ij.op().stats().geometricMean(sinusoid32).toString(), "harmonicMean": ij.op().stats().harmonicMean(sinusoid32).toString(), "kurtosis": ij.op().stats().kurtosis(sinusoid32).toString(), "max": ij.op().stats().max(sinusoid32).toString(), "mean": ij.op().stats().mean(sinusoid32).toString(), "median": ij.op().stats().median(sinusoid32).toString(), "min": ij.op().stats().min(sinusoid32).toString(), "moment1AboutMean": ij.op().stats().moment1AboutMean(sinusoid32).toString(), "moment2AboutMean": ij.op().stats().moment2AboutMean(sinusoid32).toString(), "moment3AboutMean": ij.op().stats().moment3AboutMean(sinusoid32).toString(), "moment4AboutMean": ij.op().stats().moment4AboutMean(sinusoid32).toString(), "size": ij.op().stats().size(sinusoid32).toString(), "skewness": ij.op().stats().skewness(sinusoid32).toString(), "stdDev": ij.op().stats().stdDev(sinusoid32).toString(), "sum": ij.op().stats().sum(sinusoid32).toString(), "sumOfInverses": ij.op().stats().sumOfInverses(sinusoid32).toString(), "sumOfLogs": ij.op().stats().sumOfLogs(sinusoid32).toString(), "sumOfSquares": ij.op().stats().sumOfSquares(sinusoid32).toString(), "variance": ij.op().stats().variance(sinusoid32).toString() ]) // Prepare a couple of equally sized images. import net.imglib2.type.numeric.real.FloatType image1 = ij.op().run("create.img", [160, 96], new FloatType()) image2 = ij.op().run("copy.rai", image1) // Gradient toward bottom right. ij.op().image().equation(image1, "p[0] + p[1]") minMax1 = ij.op().stats().minMax(image1) println("image1 range = (" + minMax1.getA() + ", " + minMax1.getB() + ")") // Sinusoid. ij.op().image().equation(image2, "64 * (Math.sin(0.1 * p[0]) + Math.cos(0.1 * p[1])) + 128") minMax2 = ij.op().stats().minMax(image2) println("image2 range = (" + minMax2.getA() + ", " + minMax2.getB() + ")") ij.notebook().display([["image1":image1, "image2":image2]]) addImage = image1 // Try also with image2! tile([ addImage, ij.op().run("math.add", ij.op().run("copy.rai", addImage), 60), ij.op().run("math.add", ij.op().run("copy.rai", addImage), 120), ij.op().run("math.add", ij.op().run("copy.rai", addImage), 180) ]) sum = ij.op().run("math.add", image1, image2) diff = ij.op().run("math.subtract", image1, image2) tile([sum, diff]) ij.op().run("math.multiply", image1, image2) ij.op().run("math.divide", image1, image2) ij.op().help("eval") import net.imagej.ops.eval.OpEvaluator ij.notebook().display(new OpEvaluator().getOpMap().entrySet().stream().map{ entry -> ["Operator": entry.getKey().toString(), "Op name": entry.getValue()] }.collect()) dogFormula = "gauss(image, [10, 10, 1]) - gauss(image, [5, 5, 1])" dog = ij.op().eval(dogFormula, ["image": image32]) dogFormula = "gauss(image, [10, 10, 1]) - gauss(image, [5, 5, 1])" dog = ij.op().eval(dogFormula, ["image": image32]) dogWrong = ij.op().eval(dogFormula, ["image": grayImage]) dogConverted = ij.op().convert().uint8(dog) ij.notebook().display([["dogWrong":dogWrong, "dogConverted":dogConverted]]) ij.op().ops().findAll{op -> op.startsWith("convert.big") || op.startsWith("convert.bit") || op.startsWith("convert.cfloat") || op.startsWith("convert.float") || op.startsWith("convert.int") || op.startsWith("convert.uint") }.collect{op -> op[8..-1]} // Make a handy method for printing the image min/max. printMinMax = { prefix, image -> minMax = ij.op().stats().minMax(image) println(prefix + ": min = " + minMax.getA() + ", max = " + minMax.getB()) } // Create an image with an illustrative range of intensities. import net.imglib2.type.numeric.integer.UnsignedShortType imageToConvert = ij.op().run("create.img", [96, 64], new UnsignedShortType()) formula = "128 * (Math.cos(0.3*p[0]) + Math.sin(0.3*p[1])) + 384" ij.op().image().equation(imageToConvert, formula) printMinMax("Original", imageToConvert) imageToConvert import net.imagej.ops.Ops import net.imagej.ops.special.computer.Computers import net.imglib2.type.numeric.integer.UnsignedByteType convertMethods = [ Ops.Convert.Copy.class, Ops.Convert.Clip.class, // HACK: convert.scale op is disabled here due to a bug in Ops. // Ops.Convert.Scale.class, Ops.Convert.NormalizeScale.class ] convertedImages = [] for (convertMethod in convertMethods) { // Create a uint8 destination image for the conversion. convertedImage = ij.op().run("create.img", imageToConvert, new UnsignedByteType()) // Look up the needed convert op for converting from source to destination. inType = imageToConvert.firstElement() // Type from which we are converting. outType = convertedImage.firstElement() // Type to which we are converting. convertOp = Computers.unary(ij.op(), convertMethod, outType, inType) // NB: Prepare the convert op to behave properly. // The need for these calls is hacky, and will be fixed in a future version of Ops. convertOp.checkInput(inType, outType) convertOp.checkInput(imageToConvert) // Run this convert op on every sample of our source image. ij.op().run("map", convertedImage, imageToConvert, convertOp) methodName = convertMethod.getField("NAME").get(null) printMinMax(methodName, convertedImage) convertedImages.add(convertedImage) } tile(convertedImages) ij.op().ops().findAll{op -> op.startsWith("filter.") }.collect{op -> op[7..-1]} ij.op().help("filter.addPoissonNoise") imageToFilter = image32 radius = 3 // We will use a 3x3 diamond as our neighborhood here. import net.imglib2.algorithm.neighborhood.DiamondShape shape = new DiamondShape(radius) // Add Poisson noise. addPoissonNoise = ij.op().run("create.img", imageToFilter) ij.op().filter().addPoissonNoise(addPoissonNoise, imageToFilter) // Gaussian blur. gauss = ij.op().filter().gauss(imageToFilter, radius) // Median filter. median = ij.op().run("create.img", imageToFilter) ij.op().filter().median(median, imageToFilter, shape) // Min filter. min = ij.op().run("create.img", imageToFilter) ij.op().filter().min(min, imageToFilter, shape) // Max filter. max = ij.op().run("create.img", imageToFilter) ij.op().filter().max(max, imageToFilter, shape) // Sobel filter. sobel = ij.op().filter().sobel(imageToFilter) // Display the results side by side. ij.notebook().display([ [["image":imageToFilter, "poissonNoise":addPoissonNoise, "gauss":gauss, "Sobel":sobel]], [["median":median,"min":min,"max":max]] ]) imageToProcess = image32 sigma1 = 5 sigma2 = 10 // Difference of Gaussians. dog = ij.op().filter().dog(imageToProcess, sigma1, sigma2) // gauss(sigma2) - gauss(sigma1) // We can also use eval to perform the DoG. vars = [ "image": imageToProcess, "sigma1": [sigma1, sigma1], "sigma2": [sigma2, sigma2] ] evalDoG = ij.op().eval("gauss(image, sigma2) - gauss(image, sigma1)", vars) ij.notebook().display([["dog":dog, "evalDoG":evalDoG]]) input = ij.io().open("https://upload.wikimedia.org/wikipedia/commons/6/66/Mra-mip.jpg") ij.op().help("filter.frangiVesselness") import net.imglib2.img.array.ArrayImgs // spacing refers to the physical distance between pixels. The default setting is {1, 1, 1...} spacings = [1, 1, 1] //parse the strings dims = new long[input.numDimensions()] input.dimensions(dims) // scale refers the the pixel distance at which the filter operates. Larger scales measure larger structures. filtered = [:] for (scale in [2, 5, 8, 13, 21]) filtered.put("Scale " + scale, ij.op().run("filter.frangiVesselness", ArrayImgs.doubles(dims), input, spacings, scale)) ij.notebook().display([filtered]) import net.imglib2.type.numeric.real.FloatType; // create the sample image base = ij.op().run("create.img", [150, 100], new FloatType()) formula = "p[0]^2 * p[1]" ij.op().image().equation(base, formula) // create kernel kernel_small = ij.op().run("create.img", [3,3], new FloatType()) kernel_big = ij.op().run("create.img", [20,20], new FloatType()) ij.op().image().equation(kernel_small, "p[0]") ij.op().image().equation(kernel_big, "p[0]") // convolved convolved_small = ij.op().filter().convolve(base, kernel_small) convolved_big = ij.op().filter().convolve(base, kernel_big) ij.notebook().display([[ "base":base, "small kernel":kernel_small, "big kernel":kernel_big, "small convolved":convolved_small, "big convolved":convolved_big ]]) op = ij.op().op("filter.convolve", base, kernel_small) op2 = ij.op().op("filter.convolve", base, kernel_big) [op.getClass().getName(), op2.getClass().getName()] import net.imglib2.util.Util import net.imglib2.outofbounds.OutOfBoundsConstantValueFactory import net.imagej.ops.deconvolve.RichardsonLucyF base_deconvolved = ij.op().run(RichardsonLucyF.class, convolved_small, kernel_small, null, new OutOfBoundsConstantValueFactory<>(Util.getTypeFromInterval(kernel_small).createVariable()), 10) base_deconvolved_big = ij.op().run(RichardsonLucyF.class, convolved_big, kernel_big, null, new OutOfBoundsConstantValueFactory<>(Util.getTypeFromInterval(kernel_small).createVariable()), 10) ij.notebook().display([[ "small kernel":base_deconvolved, "big kernel":base_deconvolved_big ]]) import net.imagej.ops.deconvolve.RichardsonLucyF base_deconvoled_big_iterate = ij.op().run(RichardsonLucyF.class,convolved_big,kernel_big,50) import net.imagej.ops.deconvolve.RichardsonLucyF base_deconvolved_big_noncirc = ij.op().run(RichardsonLucyF.class, convolved_big, kernel_big, null, null,null, null,null,50,true,false ) import net.imagej.ops.deconvolve.RichardsonLucyF base_deconvolved_big_acc_noncirc = ij.op().run(RichardsonLucyF.class, convolved_big, kernel_big, null, null,null, null, null,50,true,true) import net.imglib2.converter.Converters import net.imglib2.type.numeric.real.FloatType // create image fft_in = ij.op().run("create.img", [150, 100], new FloatType()) formula = "p[0]^2 + p[1]" ij.op().image().equation(fft_in, formula) // apply fft fft_out = ij.op().run("filter.fft", fft_in) // display the complex-valued result // Quantitatively, it is not very useful to do this, but we like pictures. :-) import net.imglib2.RandomAccessibleInterval fft_out_real = Converters.convert((RandomAccessibleInterval) fft_out, {i, o -> o.setReal(i.getRealFloat())}, new FloatType()) fft_out_imaginary = Converters.convert((RandomAccessibleInterval) fft_out, {i, o -> o.setReal(i.getImaginaryFloat())}, new FloatType()) ij.notebook().display([[ "real": ij.notebook().display(fft_out_real, -1, 1), "imaginary": ij.notebook().display(fft_out_imaginary, -1, 1) ]]) import net.imglib2.type.numeric.real.FloatType inverted = ij.op().run("create.img", [150,100], new FloatType()) ij.op().filter().ifft(inverted, fft_out) inverted ij.op().ops().findAll{op -> op.startsWith("transform.") }.collect{op -> op[10..-1]} ij.op().help("rotateView") // set parameter for rotate x = 0; y = 1; c = 2 // define functions to see the bounds of an image bounds = {interval -> return "(" + interval.min(0) + ", " + interval.min(1) + ") - " + "(" + interval.max(0) + ", " + interval.max(1) + ")" } // Rotate the image (image, fromAxis, toAxis) rotated = ij.op().run("rotateView", image, x, y) // 90 degrees clockwise //rotated = ij.op().run("rotateView", image, y, x)// 90 degrees counter-clockwise //rotated = ij.op().run("rotateView", image, x, c) // rotate through channels! WAT // The interval bounds have automatically changed! println("Old bounds: " + bounds(image)) println("New bounds: " + bounds(rotated)) rotated ij.op().help("crop") import net.imglib2.FinalInterval region = FinalInterval.createMinSize(75, 27, 0, 40, 28, 3) eye = ij.op().run("crop", image, region) eyeView = ij.op().run("intervalView", image, region) println("Eye bounds: " + bounds(eye)) println("EyeView bounds: " + bounds(eyeView)) ij.notebook().display([["eye":eye, "view":eyeView]]) import net.imglib2.interpolation.randomaccess.NearestNeighborInterpolatorFactory import net.imglib2.interpolation.randomaccess.NLinearInterpolatorFactory import net.imglib2.interpolation.randomaccess.LanczosInterpolatorFactory scaleFactors = [4, 4, 1] // Enlarge X and Y by 4x; leave channel count the same. nearestNeighborEye = ij.op().run("scaleView", eye, scaleFactors, new NearestNeighborInterpolatorFactory()) nLinearEye = ij.op().run("scaleView", eye, scaleFactors, new NLinearInterpolatorFactory()) lanczosEye = ij.op().run("scaleView", eye, scaleFactors, new LanczosInterpolatorFactory()) ij.notebook().display([["nearest neighbor":nearestNeighborEye, "N-linear":nLinearEye, "Lanczos":lanczosEye]]) ij.op().ops().findAll{op -> op.startsWith("transform.extend") }.collect{op -> op[10..-1]} def pad(image, extended, t, r, b, l) { min = new long[image.numDimensions()] max = new long[image.numDimensions()] image.min(min) image.max(max) min[0] -= l; min[1] -= t; max[0] += r; max[1] += b return ij.op().run("intervalView", extended, min, max) } // Define the top, right, bottom and left padding amounts. t = r = b = l = 20 // Pad the image with different out-of-bounds strategies. extendedBorder = ij.op().run("transform.extendBorderView", eye) paddedBorder = pad(eye, extendedBorder, t, r, b, l) extendedMirrorDouble = ij.op().run("transform.extendMirrorDoubleView", eye) paddedMirrorDouble = pad(eye, extendedMirrorDouble, t, r, b, l) extendedMirrorSingle = ij.op().run("transform.extendMirrorSingleView", eye) paddedMirrorSingle = pad(eye, extendedMirrorSingle, t, r, b, l) extendedPeriodic = ij.op().run("transform.extendPeriodicView", eye) paddedPeriodic = pad(eye, extendedPeriodic, t, r, b, l) minValue = eye.firstElement().getMinValue().doubleValue() maxValue = eye.firstElement().getMaxValue().doubleValue() extendedRandom = ij.op().run("transform.extendRandomView", eye, minValue, maxValue) paddedRandom = pad(eye, extendedRandom, t, r, b, l) value = eye.firstElement().copy(); value.set(100) extendedValue = ij.op().run("transform.extendValueView", eye, value) paddedValue = pad(eye, extendedValue, t, r, b, l) extendedZero = ij.op().run("transform.extendZeroView", eye) paddedZero = pad(eye, extendedZero, t, r, b, l) ij.notebook().display([[ "border":paddedBorder, "mirror double":paddedMirrorDouble, "mirror single":paddedMirrorSingle, "periodic":paddedPeriodic, "random":paddedRandom, "value":paddedValue, "zero":paddedZero ]]) // Hessian filter hessianComposite = ij.op().filter().hessian(image32) hessian = hessianComposite.interval println(hessian.dimension(2)) stackIndex = hessian.numDimensions() - 1 stackLength = hessian.dimension(stackIndex) hessianSlices = [:] for (i = 0; i < stackLength; i++) hessianSlices.put("Derivative #" + i, ij.op().transform().hyperSliceView(hessian, stackIndex, i)) ij.notebook().display([hessianSlices]) threshImage = ij.op().run("crop", image, slice, true) ij.op().ops().findAll{op -> op.startsWith("threshold.") }.collect{op -> op[10..-1]} tHuang = ij.op().threshold().huang(threshImage) tIJ1 = ij.op().threshold().ij1(threshImage) // ImageJ 1.x calls this "Default" tIntermodes = ij.op().threshold().intermodes(threshImage) tIsoData = ij.op().threshold().isoData(threshImage) tLi = ij.op().threshold().li(threshImage) tMaxEntropy = ij.op().threshold().maxEntropy(threshImage) tMaxLikelihood = ij.op().threshold().maxLikelihood(threshImage) tMean = ij.op().threshold().mean(threshImage) tMinError = ij.op().threshold().minError(threshImage) tMinimum = ij.op().threshold().minimum(threshImage) tMoments = ij.op().threshold().moments(threshImage) tOtsu = ij.op().threshold().otsu(threshImage) tPercentile = ij.op().threshold().percentile(threshImage) tRenyiEntropy = ij.op().threshold().renyiEntropy(threshImage) tRosin = ij.op().threshold().rosin(threshImage) tShanbhag = ij.op().threshold().shanbhag(threshImage) tTriangle = ij.op().threshold().triangle(threshImage) tYen = ij.op().threshold().yen(threshImage) ij.notebook().display([ [["huang":tHuang, "ij1":tIJ1, "intermodes":tIntermodes, "isodata":tIsoData, "li":tLi, "max entropy":tMaxEntropy]], [["max likelihood":tMaxLikelihood, "mean":tMean, "min error":tMinError, "minimum":tMinimum, "moments":tMoments, "otsu":tOtsu]], [["percentile":tPercentile, "renyi entropy":tRenyiEntropy, "rosin":tRosin, "shanbhag":tShanbhag, "triangle":tTriangle, "yen":tYen]] ]) ij.op().help("threshold.localBernsenThreshold") import net.imglib2.algorithm.neighborhood.HyperSphereShape // Secondary parameter values. These are wild guesses. contrastThreshold = 10 halfMaxValue = 10 // Let's try with a variety of neighborhood radii and compare side-by-side. import net.imglib2.type.logic.BitType radii = [1, 3, 5, 8, 12, 15] bernsenImages = [:] for (radius in radii) { binaryImage = ij.op().run("create.img", threshImage, new BitType()) ij.op().threshold().localBernsenThreshold(binaryImage, threshImage, new HyperSphereShape(radius), contrastThreshold, halfMaxValue) bernsenImages.put("radius " + radius, binaryImage) } ij.notebook().display([bernsenImages]) import net.imglib2.algorithm.neighborhood.HyperSphereShape // get binary image threshImage = grayImage // Secondary parameter values. These are wild guesses. contrastThreshold = 10 halfMaxValue = 10 import net.imglib2.type.logic.BitType radius = 15 binaryImage = ij.op().run("create.img", threshImage, new BitType()) ij.op().threshold().localBernsenThreshold(binaryImage, threshImage, new HyperSphereShape(radius), contrastThreshold, halfMaxValue) ij.notebook().display([["gray": grayImage, "binary": binaryImage]]) // HINT: Try different binary images here! morphImage = binaryImage // We will use the smallest radius: a single pixel. import net.imglib2.algorithm.neighborhood.HyperSphereShape shape = new HyperSphereShape(1) erode = ij.op().morphology().erode(morphImage, shape) dilate = ij.op().morphology().dilate(morphImage, shape) open = ij.op().morphology().open(morphImage, [shape]) close = ij.op().morphology().close(morphImage, [shape]) // Let's also check whether open and close visually match what we expect. manualOpen = ij.op().morphology().dilate(erode, shape) manualClose = ij.op().morphology().erode(dilate, shape) ij.notebook().display([["erode":erode, "dilate":dilate, "open":open, "close":close, "manual open":manualOpen, "manual close":manualClose]]) morphImage = grayImage // We will use a larger radius of 3 for our neighborhood this time. import net.imglib2.algorithm.neighborhood.HyperSphereShape shape = new HyperSphereShape(3) erode = ij.op().morphology().erode(morphImage, shape) dilate = ij.op().morphology().dilate(morphImage, shape) open = ij.op().morphology().open(morphImage, [shape]) close = ij.op().morphology().close(morphImage, [shape]) blackTopHat = ij.op().morphology().blackTopHat(morphImage, [shape]) whiteTopHat = ij.op().morphology().topHat(morphImage, [shape]) ij.notebook().display([["erode":erode, "dilate":dilate, "open":open, "close":close, "black top-hat":blackTopHat, "white top-hat":whiteTopHat]]) import net.imagej.ops.morphology.thin.ThinGuoHall import net.imagej.ops.morphology.thin.ThinHilditch import net.imagej.ops.morphology.thin.ThinMorphological import net.imagej.ops.morphology.thin.ThinZhangSuen // HINT: Try different binary images here! morphImage = binaryImage GuoHall = ij.op().run(ThinGuoHall.class, morphImage) Hilditch = ij.op().run(ThinHilditch.class, morphImage) Morphological = ij.op().run(ThinMorphological.class, morphImage) zhangSuen = ij.op().run(ThinZhangSuen.class, morphImage) ij.notebook().display([["GuoHall":GuoHall, "Hilditch":Hilditch, "Morphological":Morphological, "zhangSuen":zhangSuen]]) colCount = 5 // Change it if you wish. // Define some helper functions. notebookPath = { op -> tokens = op.split("\\.") tokens[-1] += ".ipynb" opNotebook = java.nio.file.Paths.get("Ops", tokens) if (opNotebook.toFile().exists()) return opNotebook ns = net.imagej.ops.OpUtils.getNamespace(op) if (ns == null) return null nsNotebook = java.nio.file.Paths.get("Ops", ns, ns + ".ipynb") if (nsNotebook.toFile().exists()) return nsNotebook } ns = { net.imagej.ops.OpUtils.getNamespace(it) ?: "<global>" } // Organize op namespaces by column. opsByNS = [:]; ij.op().ops().each{ opsByNS.get(ns(it), []).add(it) } cols = []; (1..colCount).each { cols.add([]) } for (ns in opsByNS.keySet().sort()) { col = cols.min { it.flatten().size() } // Add to shortest column. item = ["

${ns}

"] item.addAll(opsByNS.get(ns)) col.add(item) } // Generate HTML table. s = "

Index of available ops

" s += "" s += "" for (col in cols) { s += "" } s += "
" for (ns in col) { // Nested tables! Great plan? Or the greatest plan? s += "" for (op in ns) { label = net.imagej.ops.OpUtils.stripNamespace(op) url = notebookPath(op) html = url == null ? label : "${label}" s += "" } s += "
${html}

" } s += "
" (net.imagej.notebook.mime.HTMLObject) { s }