Skip to content

Commit db162e4

Browse files
authored
Merge pull request #153 from scijava/scijava-ops-docs/scyjava
Add SciJava Ops with Python (via scyjava) use case
2 parents e819391 + 93a11eb commit db162e4

File tree

5 files changed

+160
-3
lines changed

5 files changed

+160
-3
lines changed

docs/ops/doc/ScriptingInFiji.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@ The below script can be pasted into the Script Editor. **Ensure that the Script
4141
out = ops.op("filter.gauss").input(imgInput, new Double(3.0)).apply()
4242
```
4343

44-
Scripting in Fiji is a convenient gateway to accessing SciJava Ops. To see more, check out some examples, such as [image deconvolution](examples/deconvolution.rst) or [FLIM analysis](examples/example_flim_analysis.rst)!
44+
Scripting in Fiji is a convenient gateway to accessing SciJava Ops. To see more, check out some examples, such as [image deconvolution](examples/deconvolution.rst) or [FLIM analysis](examples/flim_analysis.rst)!
File renamed without changes.
File renamed without changes.

docs/ops/doc/examples/scyjava.rst

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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

docs/ops/doc/index.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ The combination of these libraries allows declarative image analysis workflows,
3030
:caption: ⚙️ Examples
3131

3232
examples/deconvolution
33-
examples/example_gaussian_subtraction
33+
examples/flim_analysis
34+
examples/gaussian_subtraction
3435
examples/opencv_denoise
35-
examples/example_flim_analysis
36+
examples/scyjava
3637

3738
.. toctree::
3839
:maxdepth: 2

0 commit comments

Comments
 (0)