diff --git a/scijava-ops-tutorial/scripts/gateways.py b/scijava-ops-tutorial/scripts/gateways.py deleted file mode 100644 index 24e77365e..000000000 --- a/scijava-ops-tutorial/scripts/gateways.py +++ /dev/null @@ -1,123 +0,0 @@ -''' -Create namespace convenience methods for all discovered ops. -Assumes we're running in an environment with scyjava configured to have a classpath with some SciJava Ops implementations (i.e. org.scijava:scijava-ops-engine plus implementations) - -Classes: - OpNamespace - OpGateway - -Variables: - env - OpEnvironment used to populate the OpGateway and OpNamespaces - ops - OpGateway entry point -''' - -from scyjava import jimport -from types import MethodType -OpEnvironment = jimport('org.scijava.ops.api.OpEnvironment') -env = OpEnvironment.build() - -op_names={str(name) for info in env.infos() for name in info.names()} - -class OpNamespace: - ''' - Represents intermediate ops categories. For example, "math.add" and "math.sub" are in both in the "math" namespace. - ''' - def __init__(self, env, ns): - self.env = env - self.ns = ns - - def help(self, op_name=None): - ''' - Convenience wrapper for OpEnvironment.help(), for information about available ops. Prints all returned information, line-by-line. - ''' - print(*self.env.help(op_name), sep = "\n") - -class OpGateway(OpNamespace): - ''' - Global base specialization of OpNamespace. Contains all other namespaces, in addition to all ops in the "global" namespace. - ''' - def __init__(self, env): - super().__init__(env, 'global') - -def nary(env, fqop, arity): - ''' - Helper method to convert a numerical arity to the corresponding OpEnvironment method - - Parameters: - env (OpEnvironment): the environment being used - fqop (str): the fully-qualified op name - arity (int): the desired arity of the op - - Returns: - The appropriate OpBuilder.Arity instance for invoking the given op with the indicated number of parameters - ''' - arities=[env.op, env.op, env.op, env.op, env.op, env.op, env.op, env.op, env.op, env.op, env.op, env.arity11, env.arity12, env.arity13, env.arity14, env.arity15, env.arity16] - return arities[arity](fqop) - -def add_op(c, op_name): - ''' - Helper method patch in a given op name as its corresponding function call within the given class - - Parameters: - c (class): the OpNamespace/OpGateway to add a new function with the given op_name that calls the op - op_name (str): the actual name of the op we're adding - ''' - if hasattr(c, op_name): - return - - def f(self, *args, **kwargs): - ''' - Instance method to attach to our OpNamespace/OpGateway that does the actual op call - ''' - fqop = op_name if self.ns == 'global' else self.ns + "." + op_name - run = kwargs.get('run', True) - b = nary(self.env, fqop, len(args)).input(*args) - # inplace - # ops.filter.gauss(image, 5, inplace=0) - if (inplace:=kwargs.get('inplace', None)) is not None: - return b.mutate(inplace) if run else b.inplace(inplace) - - # computer - # ops.filter.gauss(image, 5, out=result) - if (out:=kwargs.get('out', None)) is not None: - b=b.output(out) - return b.compute() if run else b.computer() - - # function - # gauss_op = ops.filter.gauss(image, 5) - # result = ops.filter.gauss(image, 5) - return b.apply() if run else b.function() - - if c == OpGateway: - # op_name is a global. - setattr(c, op_name, f) - else: - m=MethodType(f, c) - setattr(c, op_name, m) - -def add_namespace(c, ns, op_name): - ''' - Helper method to add an op call with nested OpNamespace instances if needed - - Parameters: - c (class): the OpNamespace/OpGateway to add the given op and namespace - ns(str): the namespace to nest the given op within - op_name (str): the actual name of the op we're adding - ''' - if not hasattr(c, ns): - setattr(c, ns, OpNamespace(env, ns)) - add_op(getattr(c, ns), op_name) - -# Entry point to do the namespace population. -# This modifies the OpGateway class to add a call for each op, with intermediate accessors by namespace -for op in op_names: - dot = op.find('.') - if dot >= 0: - ns=op[:dot] - op_name=op[dot+1:] - add_namespace(OpGateway, ns, op_name) - else: - add_op(OpGateway, op) - -# Make an instance of our modified OpGateway class as a global for external use -ops = OpGateway(env) diff --git a/scijava-ops-tutorial/scripts/ops-setup.py b/scijava-ops-tutorial/scripts/ops-setup.py deleted file mode 100644 index a2b5e745a..000000000 --- a/scijava-ops-tutorial/scripts/ops-setup.py +++ /dev/null @@ -1,20 +0,0 @@ -''' -Python entry point script for using SciJava Ops in python. After running this script -you will have an "ops" variable that can be used to call ops, and explore available ops. - -For example: - ops.math.add(27, 15) - ops.filter.addPoissonNoise(img) - -Variables: - ops - fully intialized OpGateway with all SciJava and ImageJ Ops available - env - OpEnvironment for more traditional Java-style API -''' - -from scyjava import config, jimport -config.endpoints.append('org.scijava:scijava-ops-tutorial:1.0.0') - -import gateways as g - -ops = g.ops -env = g.env diff --git a/scijava-ops-tutorial/scripts/ops_gateway.py b/scijava-ops-tutorial/scripts/ops_gateway.py new file mode 100644 index 000000000..f8fa940cc --- /dev/null +++ b/scijava-ops-tutorial/scripts/ops_gateway.py @@ -0,0 +1,229 @@ +""" +Classes +------- + + - OpsNamespace + - OpsGateway + +Functions +--------- + + - init + +Variables +--------- + + - `ops` + +Example +------- + + - Interactive Python session: + python -i ops-gateway.py + - Module import: + >>> import ops-gateway + >>> ops = ops-gateway.init() +""" + +from types import MethodType +from typing import List, Sequence +import scyjava as sj + +endpoints = [ + "net.imglib2:imglib2:6.4.0", + "net.imglib2:imglib2-imglyb", + "org.scijava:scijava-ops-engine", + "org.scijava:scijava-ops-flim", + "org.scijava:scijava-ops-image" + ] + +class OpNamespace: + """Op namespace class. + + Represents intermediate Ops categories and Ops. For example, + "math.add" and "features.haralick.asm". + """ + + def __init__(self, env: "scijava.OpEnvironment", ns: str): + self.op = env.op + self._env = env + self._ns = ns + + +class OpsGateway(OpNamespace): + """SciJava Ops Gateway class. + + Contains all other namespaces, in addition to all Ops in + the "global" namespace. + """ + + def __init__(self, env): + super().__init__(env, "global") + + def help(self, op_name: str = None): + """SciJava Ops help. + + :param op_name: + + Namespace and Op name (e.g. "filter.gauss") + """ + if op_name: + print(self._env.help(op_name), sep="\n") + else: + print(self._env.help(), sep="\n") + + def helpVerbose(self, op_name: str = None): + """SciJava Ops verbose help. + + :param op_name: + + Namespace and Op name (e.g. "filter.gauss") + """ + if op_name: + print(self._env.helpVerbose(op_name), sep="\n") + else: + print(self._env_helpVerbose(), sep="\n") + + +def init(endpoints: List[str]) -> OpsGateway: + """Get the SciJava Ops Gateway. + + Initialize the JVM and return an instance of the + SciJava Ops Gateway class. + + :return: + + The SciJava Ops Gateway. + """ + # configure and start the jvm + if not sj.jvm_started(): + sj.config.endpoints = endpoints + sj.start_jvm() + + # build Ops environment + env = sj.jimport("org.scijava.ops.api.OpEnvironment").build() + + # find op names, base namespaces and intermediate namespaces + op_names = _find_op_names(env) + op_base_ns = [] + for op in op_names: + op_sig = op.split(".") + # skip "base" Ops + if len(op_sig) == 1: + continue + else: + op_base_ns.append(op_sig[0]) + op_base_ns = set(op_base_ns) + + # populate base namespaces + for ns in op_base_ns: + _add_namespace(OpsGateway, env, ns) + + # populate nested namespaces and ops + for op in op_names: + op_sig = op.split(".") + sig_size = len(op_sig) + if sig_size > 1: + # find/add nested namespaces + gateway_ref = OpsGateway # used to reference nested namespaces + for s in op_sig[:-1]: + if hasattr(gateway_ref, s): + gateway_ref = getattr(gateway_ref, s) + else: + _add_namespace(gateway_ref, env, s) + gateway_ref = getattr(gateway_ref, s) + # add the Op to the nested namespace + _add_op(gateway_ref, env, op_sig[-1]) + else: + _add_op(OpsGateway, env, op_sig[0]) + + return OpsGateway(env) + +def _add_namespace(gc: OpsGateway, env: "scijava.OpEnvironment", ns: str): + """Add an Op and it's namespace to the OpsGateway. + + Helper method to add an Op call with the appropriate nested + OpNamespace instances if needed. + + :param gc: + + OpsGateway class + + :param env: + + SciJava Ops environment instance + + :param ns: + + Namespace + + :param on: + + Op name + """ + if not hasattr(gc, ns): + setattr(gc, ns, OpNamespace(env, ns)) + + +def _add_op(gc: OpsGateway, env: "scijava.OpEnvironment", on: str): + """Add an Op to the OpsGateway. + + Helper method to add an Op with its corresponding function call + to the given class. + + :param gc: + + OpsGateway class + + :param env: + + SciJava Ops environment instance + + :param on: + + Op name + """ + if hasattr(gc, on): + return + + def f(self, *args, **kwargs): + """Op call instance methods. + + Instance method to attach to the OpNamespace/OpsGateway that does + the actual Op call. + """ + fqop = on if self._ns == "global" else self._ns + "." + on + run = kwargs.get("run", True) + req = env.op(fqop).input(*args) + + # inplace Op requests + if (inplace := kwargs.get("inplace", None)) is not None: + return req.mutate(inplace) if run else req.inplace(inplace) + + # computer Op requests + if (out := kwargs.get("out", None)) is not None: + req = req.output(out) + return req.compute() if run else req.computer() + + # function Op requests + return req.apply() if run else req.function() + + if gc == OpsGateway: + # Op name is a global + setattr(gc, on, f) + else: + m = MethodType(f, gc) + setattr(gc, on, m) + + +def _find_op_names(env: "scijava.OpEnvironment") -> set: + """Find all Op names in a SciJava Ops environment. + + :return: + + Set of all Op names/signatures + """ + return {str(name) for info in env.infos() for name in info.names()} + +if __name__ == "__main__": + ops = init(endpoints)