|
| 1 | +======================= |
| 2 | +SciJava Ops from Python |
| 3 | +======================= |
| 4 | + |
| 5 | +This example demonstrates how to use SciJava Ops with Python. Using SciJava Ops framework with Python depends on ``scyjava`` to provide robust |
| 6 | +Java code access and ``imglyb`` to bridge the ImgLib2 and NumPy data structures. The Python script in this example downloads a `3D HeLa cell`_ |
| 7 | +nucleus dataset (with shape: ``61, 200, 200``), performes image processing with to improve the nucleus signal, segments the nucleus and measures |
| 8 | +the 3D volume of the nucleus by creating a mesh. Finally the input image, processed image and the segmented label images are displayed in |
| 9 | +``matplotlib``. |
| 10 | + |
| 11 | +.. figure:: https://media.imagej.net/scijava-ops/1.0.0/scyjava_example_1.png |
| 12 | + |
| 13 | + |
| 14 | +To run this example, create a conda/mamba environment with the following ``environment.yml`` file: |
| 15 | + |
| 16 | +.. code-block:: yaml |
| 17 | +
|
| 18 | + name: scijava-ops |
| 19 | + channels: |
| 20 | + - conda-forge |
| 21 | + - defaults |
| 22 | + dependencies: |
| 23 | + - scyjava |
| 24 | + - imglyb |
| 25 | + - tifffile |
| 26 | + - matplotlib |
| 27 | + - requests |
| 28 | + - openjdk >= 17 |
| 29 | +
|
| 30 | +
|
| 31 | +Activate the ``scijava-ops`` conda/mamba environment and run the following Python script: |
| 32 | + |
| 33 | +.. code-block:: python |
| 34 | +
|
| 35 | + import io |
| 36 | + import requests |
| 37 | + import imglyb |
| 38 | + import scyjava as sj |
| 39 | + import numpy as np |
| 40 | + import tifffile as tf |
| 41 | + import matplotlib.pyplot as plt |
| 42 | + from typing import List |
| 43 | +
|
| 44 | + def imglib_to_numpy(rai: "net.imglib2.RandomAccessibleInterval", dtype: str) -> np.ndarray: |
| 45 | + """Convert an ImgLib2 image to NumPy. |
| 46 | +
|
| 47 | + :param rai: Input RandomAccessibleInterval (RAI) |
| 48 | + :param dtype: dtype for output NumPy array |
| 49 | + :return: A NumPy array with the specified dtype and data |
| 50 | + """ |
| 51 | + # create empty NumPy array |
| 52 | + shape = list(rai.dimensionsAsLongArray()) |
| 53 | + shape.reverse() # XY -> row, col |
| 54 | + narr = np.zeros(shape, dtype=dtype) |
| 55 | + # create RAI reference with imglyb and copy data |
| 56 | + ImgUtil.copy(rai, imglyb.to_imglib(narr)) |
| 57 | +
|
| 58 | + return narr |
| 59 | +
|
| 60 | +
|
| 61 | + def numpy_to_imglib(narr: np.ndarray) -> "net.imglib2.RandomAccessibleInterval": |
| 62 | + """Convert a NumPy image to ImgLib2. |
| 63 | +
|
| 64 | + :param narr: Input NumPy array |
| 65 | + :return: A ImgLib2 RandomAccessibleInterval (reference) |
| 66 | + """ |
| 67 | + return imglyb.to_imglib(narr) |
| 68 | +
|
| 69 | +
|
| 70 | + def read_image_from_url(url: str) -> np.ndarray: |
| 71 | + """Read a .tif image from a URL. |
| 72 | +
|
| 73 | + :param url: URL of .tif image |
| 74 | + :return: NumPy array of image in URL |
| 75 | + """ |
| 76 | + return tf.imread(io.BytesIO(requests.get(url).content)) |
| 77 | +
|
| 78 | +
|
| 79 | + def segment_nuclei(rai: "net.imglib2.RandomAccessibleInterval") -> List: |
| 80 | + """Segment nuclei using SciJava Ops! |
| 81 | +
|
| 82 | + :param rai: Input RandomAccessibleInterval (RAI) |
| 83 | + :return: A list containing: |
| 84 | + (1) Image processing result |
| 85 | + (2) Threshold boolean mask |
| 86 | + (3) ImgLabeling |
| 87 | + """ |
| 88 | + # create image containers |
| 89 | + mul_result = ops.op("create.img").input(rai, FloatType()).apply() |
| 90 | + thres_mask = ops.op("create.img").input(rai, BitType()).apply() |
| 91 | +
|
| 92 | + # process image and create ImgLabeling |
| 93 | + mean_blur = ops.op("filter.mean").input(rai, HyperSphereShape(5)).apply() |
| 94 | + ops.op("math.mul").input(rai, mean_blur).output(mul_result).compute() |
| 95 | + ops.op("threshold.huang").input(mul_result).output(thres_mask).compute() |
| 96 | + labeling = ops.op("labeling.cca").input(thres_mask, StructuringElement.EIGHT_CONNECTED).apply() |
| 97 | + |
| 98 | + return [mul_result, thres_mask, labeling] |
| 99 | +
|
| 100 | +
|
| 101 | + def measure_volume(rai: "net.imglib2.RandomAccessibleInterval") -> float: |
| 102 | + """Create a mesh and measure its volume. |
| 103 | + |
| 104 | + :param rai: Input RandomAccessibleInterval (RAI) |
| 105 | + :return: Volume of the 3D mesh |
| 106 | + """ |
| 107 | + mesh = ops.op("geom.marchingCubes").input(rai).apply() |
| 108 | + volume = ops.op("geom.size").input(mesh).apply() |
| 109 | + |
| 110 | + return float(volume.getRealDouble()) |
| 111 | +
|
| 112 | +
|
| 113 | + # add SciJava repository |
| 114 | + print("[INFO]: Adding SciJava repo...") |
| 115 | + sj.config.add_repositories({'scijava.public': 'https://maven.scijava.org/content/groups/public'}) |
| 116 | +
|
| 117 | + # add endpoints |
| 118 | + print("[INFO]: Adding endpoints...") |
| 119 | + sj.config.endpoints = ['net.imglib2:imglib2', |
| 120 | + 'net.imglib2:imglib2-imglyb', |
| 121 | + 'io.scif:scifio', |
| 122 | + 'org.scijava:scijava-ops-engine:0-SNAPSHOT', |
| 123 | + 'org.scijava:scijava-ops-image:0-SNAPSHOT'] |
| 124 | +
|
| 125 | + # import Java classes |
| 126 | + print("[INFO]: Adding classes...") |
| 127 | + OpEnvironment = sj.jimport('org.scijava.ops.api.OpEnvironment') |
| 128 | + BitType = sj.jimport('net.imglib2.type.logic.BitType') |
| 129 | + FloatType = sj.jimport('net.imglib2.type.numeric.real.FloatType') |
| 130 | + HyperSphereShape = sj.jimport('net.imglib2.algorithm.neighborhood.HyperSphereShape') |
| 131 | + ImgUtil = sj.jimport('net.imglib2.util.ImgUtil') |
| 132 | + StructuringElement = sj.jimport('net.imglib2.algorithm.labeling.ConnectedComponents.StructuringElement') |
| 133 | +
|
| 134 | + # build OpEnvironment |
| 135 | + ops = OpEnvironment.build() |
| 136 | +
|
| 137 | + # open image |
| 138 | + narr = read_image_from_url("https://media.imagej.net/scijava-ops/1.0.0/hela_nucleus.tif") |
| 139 | + rai = numpy_to_imglib(narr) |
| 140 | + results = segment_nuclei(rai) |
| 141 | + print(f"[INFO]: volume = {measure_volume(results[1])}") |
| 142 | +
|
| 143 | + # display results with matplotlib |
| 144 | + processed = imglib_to_numpy(results[0], "float32") |
| 145 | + labels = imglib_to_numpy(results[2].getIndexImg(), "int32") |
| 146 | + fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(10, 3), sharex=True, sharey=True) |
| 147 | + ax[0].imshow(narr[30, :, :], cmap='gray') |
| 148 | + ax[0].set_title("input") |
| 149 | + ax[1].imshow(processed[30, :, :], cmap='gray') |
| 150 | + ax[1].set_title("processed") |
| 151 | + ax[2].imshow(labels[30, :, :]) |
| 152 | + ax[2].set_title("segmentation") |
| 153 | + plt.tight_layout() |
| 154 | + plt.show() |
| 155 | +
|
| 156 | +.. _`3D HeLa cell`: https://media.imagej.net/scijava-ops/1.0.0/hela_nucleus.tif |
0 commit comments