Skip to content

Commit af7eb5b

Browse files
committed
Add improved SciJava Ops Python gateway
This commit adds an improved Python gateway contained in the ops-gateway.py file. This gateway resolves a usability bug where nested namespaces like "features.haralick.asm" are unreachable as the string "haralick.asm" is appended as an attribute to the gateway instead of the "haralick" namespace being added as an intermediate namespace. Attempting to access "haralick" fails as that attribute does not exist. Additionally this commit adds the scijava-ops-flim library to the gateway. Intended use cases/scenarios - A user can simply run `python -i ops-gateway.py` to obtain an actiave `ops` environment. - A user can copy the ops-gateway.py module into their own project and create an ops gateway for their own internal project use.
1 parent d8276ac commit af7eb5b

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
"""
2+
TODO: Module description.
3+
"""
4+
5+
from types import MethodType
6+
from typing import List, Sequence
7+
import scyjava as sj
8+
9+
endpoints = [
10+
"net.imglib2:imglib2:6.4.0",
11+
"net.imglib2:imglib2-imglyb",
12+
"org.scijava:scijava-ops-engine",
13+
"org.scijava:scijava-ops-flim",
14+
"org.scijava:scijava-ops-image"
15+
]
16+
17+
class OpNamespace:
18+
"""Op namespace class.
19+
20+
Represents intermediate Ops categories and Ops. For example,
21+
"math.add" and "features.haralick.asm".
22+
"""
23+
24+
def __init__(self, env: "scijava.OpEnvironment", ns: str):
25+
self.op = env.op
26+
self._env = env
27+
self._ns = ns
28+
29+
30+
class OpsGateway(OpNamespace):
31+
"""SciJava Ops Gateway class.
32+
33+
Contains all other namespaces, in addition to all Ops in
34+
the "global" namespace.
35+
"""
36+
37+
def __init__(self, env):
38+
super().__init__(env, "global")
39+
40+
def help(self, op_name: str = None):
41+
"""SciJava Ops help.
42+
43+
:param op_name:
44+
45+
Namespace and Op name (e.g. "filter.gauss")
46+
"""
47+
if op_name:
48+
print(self._env.help(op_name), sep="\n")
49+
else:
50+
print(self._env.help(), sep="\n")
51+
52+
def helpVerbose(self, op_name: str = None):
53+
"""SciJava Ops verbose help.
54+
55+
:param op_name:
56+
57+
Namespace and Op name (e.g. "filter.gauss")
58+
"""
59+
if op_name:
60+
print(self._env.helpVerbose(op_name), sep="\n")
61+
else:
62+
print(self._env_helpVerbose(), sep="\n")
63+
64+
65+
def init(endpoints: List[str]) -> OpsGateway:
66+
"""Get the SciJava Ops Gateway.
67+
68+
Initialize the JVM and return an instance of the
69+
SciJava Ops Gateway class.
70+
71+
:return:
72+
73+
The SciJava Ops Gateway.
74+
"""
75+
# configure and start the jvm
76+
if not sj.jvm_started():
77+
sj.config.endpoints = endpoints
78+
sj.start_jvm()
79+
80+
# build Ops environment
81+
env = sj.jimport("org.scijava.ops.api.OpEnvironment").build()
82+
83+
# find op names, base namespaces and intermediate namespaces
84+
op_names = _find_op_names(env)
85+
op_base_ns = []
86+
for op in op_names:
87+
op_sig = op.split(".")
88+
# skip "base" Ops
89+
if len(op_sig) == 1:
90+
continue
91+
else:
92+
op_base_ns.append(op_sig[0])
93+
op_base_ns = set(op_base_ns)
94+
95+
# populate base namespaces
96+
for ns in op_base_ns:
97+
_add_namespace(OpsGateway, env, ns)
98+
99+
# populate nested namespaces and ops
100+
for op in op_names:
101+
op_sig = op.split(".")
102+
sig_size = len(op_sig)
103+
if sig_size > 1:
104+
# find/add nested namespaces
105+
gateway_ref = OpsGateway # used to reference nested namespaces
106+
for s in op_sig[:-1]:
107+
if hasattr(gateway_ref, s):
108+
gateway_ref = getattr(gateway_ref, s)
109+
else:
110+
_add_namespace(gateway_ref, env, s)
111+
gateway_ref = getattr(gateway_ref, s)
112+
# add the Op to the nested namespace
113+
_add_op(gateway_ref, env, op_sig[-1])
114+
else:
115+
_add_op(OpsGateway, env, op_sig[0])
116+
117+
return OpsGateway(env)
118+
119+
def _add_namespace(gc: OpsGateway, env: "scijava.OpEnvironment", ns: str):
120+
"""Add an Op and it's namespace to the OpsGateway.
121+
122+
Helper method to add an Op call with the appropriate nested
123+
OpNamespace instances if needed.
124+
125+
:param gc:
126+
127+
OpsGateway class
128+
129+
:param env:
130+
131+
SciJava Ops environment instance
132+
133+
:param ns:
134+
135+
Namespace
136+
137+
:param on:
138+
139+
Op name
140+
"""
141+
if not hasattr(gc, ns):
142+
setattr(gc, ns, OpNamespace(env, ns))
143+
144+
145+
def _add_op(gc: OpsGateway, env: "scijava.OpEnvironment", on: str):
146+
"""Add an Op to the OpsGateway.
147+
148+
Helper method to add an Op with its corresponding function call
149+
to the given class.
150+
151+
:param gc:
152+
153+
OpsGateway class
154+
155+
:param env:
156+
157+
SciJava Ops environment instance
158+
159+
:param on:
160+
161+
Op name
162+
"""
163+
if hasattr(gc, on):
164+
return
165+
166+
def f(self, *args, **kwargs):
167+
"""Op call instance methods.
168+
169+
Instance method to attach to the OpNamespace/OpsGateway that does
170+
the actual Op call.
171+
"""
172+
fqop = on if self._ns == "global" else self._ns + "." + on
173+
run = kwargs.get("run", True)
174+
req = env.op(fqop).input(*args)
175+
176+
# inplace Op requests
177+
if (inplace := kwargs.get("inplace", None)) is not None:
178+
return req.mutate(inplace) if run else req.inplace(inplace)
179+
180+
# computer Op requests
181+
if (out := kwargs.get("out", None)) is not None:
182+
req = req.output(out)
183+
return req.compute() if run else req.computer()
184+
185+
# function Op requests
186+
return req.apply() if run else req.function()
187+
188+
if gc == OpsGateway:
189+
# Op name is a global
190+
setattr(gc, on, f)
191+
else:
192+
m = MethodType(f, gc)
193+
setattr(gc, on, m)
194+
195+
196+
def _find_op_names(env: "scijava.OpEnvironment") -> set:
197+
"""Find all Op names in a SciJava Ops environment.
198+
199+
:return:
200+
201+
Set of all Op names/signatures
202+
"""
203+
return {str(name) for info in env.infos() for name in info.names()}
204+
205+
ops = init(endpoints)

0 commit comments

Comments
 (0)