本教程旨在指导开发人员了解核心imagej-ops项目之外添加操作的选项、流程和动机。由于本教程面向外部开发人员,因此在本教程中我们将展示如何将BAR函数转换为Op。
制作你的第一个操作
在通信的层面上,Op 是一个封装了一个功能性的 SciJava Plugin,可以由中央OpService发现和使用。
创建一个Op至少需要两个部分——一个接口和一个实现
##创建你的界面
package bar;
import net.imagej.ops.Op;
/**
* Op interface for calculating the greatest common divisor (GCD)
*/
public interface GCD extends Op {
// Ops can be called by name, defined as properties of the Op interface
String NAME = "gcd";
// An alias is OPTIONAL but gives users additional ways to call your Op.
// For example - the GCD is also called greatest common factor (GCF).
String ALIASES = "gcf";
}
实施你的操作
package bar;
import net.imagej.ops.AbstractOp;
import org.scijava.ItemIO;
import org.scijava.plugin.Attr;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
// The Plugin annotation allows this Op to be discovered by the OpService.
// We declare the type of op; the name and any aliases will be auto-detected.
@Plugin(type = GCD.class)
public class DefaultGCD implements GCD extends AbstractOp {
// -- Inputs --
// We want our GCD function to have two inputs. These are declared using @Parameter notation
@Parameter
private double a;
@Parameter
private double b;
// -- Outputs --
// This op will return a single value (the computed GCD) so we declare an output parameter
@Parameter(type = ItemIO.OUTPUT)
private double result;
@Override
public void run() {
// The job of the run method is to populate any outputs using the inputs
result = computeGCD(a, b);
}
private double computeGCD(final double p1, final double p2) {
return p2 == 0 ? p1 : computeGCD(p2, p1%p2);
}
}
使用你的操作
有了这两个组件,您就可以开始使用您的 Op - 例如,在 script editor 中:
# @OpService ops
from bar import GCD
# We look up our Op by asking the framework to find an implementation of the
# GCD interface that could handle these inputs
gcd = ops.op(GCD, 7, 10);
# Print the Op instance, to verify our Op is picked up
print(gcd)
# The OpService can print useful information about any Op..
print(ops.info(gcd))
# .. including its usage
print(ops.help("gcd"))
# We can also run an Op by passing the name or alias we defined
# Print the result of running our Op
print(ops.run("gcf", 20, 7))
将您的 Ops 分组到命名空间中
命名我们的Ops不是类型安全的,并且导入每个接口都非常繁琐。如果您要提供Op集合,则将它们的备用有用方法是调用自定义Namespace。
##您创建的界面
package bar;
import org.scijava.plugin.Plugin;
import net.imagej.ops.AbstractNamespace;
import net.imagej.ops.Namespace;
import net.imagej.ops.Op;
import net.imagej.ops.OpMethod;
@Plugin(type = Namespace.class)
public class BAR extends AbstractNamespace {
@Override
public String getName() {
return "name";
}
// -- BAR Namespace Op interfaces --
// We can make all of our interfaces nested classes.
// This allows references to take the form of "Namespace.Op" which
// can make things easier to understand.
public interface GCD extends Op {
// Note that the name and aliases are prepended with Namespace.getName
String NAME = "bar.gcd";
String ALIASES = "bar.gcf";
}
// -- BAR Namespace built-in methods --
// Built-in methods provide type-safe methods for accessing Ops
// in a namespace.
// We always provide an Object... constructor that can be passed directly to the
// OpService.run method
@OpMethod(op = bar.BAR.GCD.class)
public Object gcd(final Object... args) {
return ops().run(bar.BAR.GCD.class, args);
}
// But we can also type-narrow our inputs and returns with our knowledge of the Op
// implementations
@OpMethod(op = bar.BAR.GCD.class)
public double gcd(final double a, final double b) {
return (Double) ops().run(bar.BAR.GCD.class, a, b);
}
}
实施你的操作
尽管我们必须更新类引用,但其实现本质上与单个操作相同。
由于通常不直接访问实现,因此它们是否为请求类或单独提供并不像接口那么重要。
package bar;
import net.imagej.ops.AbstractOp;
import org.scijava.ItemIO;
import org.scijava.plugin.Attr;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
@Plugin(type = BAR.GCD.class, name = BAR.GCD.NAME, attrs = { @Attr(name = "aliases", value = BAR.GCD.ALIASES) })
public class DefaultGCD extends AbstractOp implements BAR.GCD {
// -- Inputs --
@Parameter
private double a;
@Parameter
private double b;
// -- Outputs --
@Parameter(type = ItemIO.OUTPUT)
private double result;
@Override
public void run() {
result = computeGCD(a, b);
}
private double computeGCD(final double p1, final double p2) {
return p2 == 0 ? p1 : computeGCD(p2, p1%p2);
}
}
使用你的操作
我们仍然可以通过 OpService 使用我们的 Op:
# @OpService ops
print(ops.run("bar.gcd", 20, 15))
但我们也可以使用我们的内置方法:
# @bar.BAR bar
print(bar.gcd(20, 15))
这在具有代码完成的环境中特别有用。
命名空间还为用户提供了一种使用基本帮助操作查找有关可用功能的信息的简单方法:
# @OpService ops
# @bar.BAR bar
# Print usage for all ops in the BAR namespace
print(ops.help(bar))
潜在的后续步骤
为您的命名空间创建帮助服务
SciJava Services 是给定 SciJava 上下文中的每个的通用主力。上下文创建的每个服务都有一个实例,因此它们是静态实用程序样式方法的公共容器。
开发外部命名空间时,创建相应服务的直接好处是它提供了一个初始化挂钩,用于在 Ops 框架之外注册新的命名空间 - 特别是使用 SCriptService:
package bar;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import org.scijava.script.ScriptService;
import org.scijava.service.AbstractService;
import org.scijava.service.Service;
import net.imagej.ImageJService;
@Plugin(type = Service.class)
public class BARService extends AbstractService implements ImageJService {
@Parameter
private ScriptService scriptService;
@Override
public void initialize() {
// Register this namespace with the ScriptService so we can drop package prefixes
// in script parameters, allowing:
// @BAR
// instead of
// @bar.BAR
scriptService.addAlias(bar.BAR.class);
}
}
现在,我们可以在脚本中使用命名空间时删除包:
# @OpService ops
# @BAR bar
# Print usage for all ops in the BAR namespace
print(ops.help(bar))
分发如何使用您的 Ops 脚本
ImageJ script editor自动位于src/main/resources/script_templates中的脚本。例如,如果我们创建一个文件:
src/main/resources/script_templates/BAR/GCD.py
with contents:
# @BAR bar
# @float a
# @float b
print("Greatest common divisor of " + str(a) + " and " + str(b) + " is: " + str(bar.gcd(a, b)))
[On GitHub](https://github.com/tferr/Scripts/blob/-/BAR/src/main/resources/script_templates/BAR/GCD.py)
然后,用户将能够从脚本编辑器窗口中选择Templates > BAR > GCD,自动加载脚本并选择正确的脚本语言(本例中为python)。
这是一种为使用运维进行开发提供起点的简单方法。
高级主题
Op 开发了许多便利性本教程未主题,包括:
- 为给定的 Ops 接口创建额外的实现
- Specializing Ops for a variety of input types
- 使用模板自动生成您的Op实现
- 您编写的单元测试以确保覆盖运维的内置方法
然而,所有这些都是在核心§§0§§§项目中完成的,并且可以通过仔细研究来推断。如果您确实花时间独立尝试这些主题中的任何一个,请记录您的经验并将其作为教程在维基上分享。