我知道编辑这个网站吗?
the ImageJ2 platform本页介绍与 the ImageJ2 platform 相关的内容。点击徽标查看详情。

Jython 脚本

This page describes how to write SciJava scripts in Jython, the JVM-based flavor of Python. Jython is one of several Python options in the ImageJ ecosystem — see the Python page for a comparison of all of them. To call ImageJ2 functions from CPython programs, see PyImageJ.

简介

Jython 是在 Java 虚拟机上运行的 Python 编程语言的实现。 [^1] 在 ImageJ2Fiji 中,Jython 是多个 supported scripting languages 之一。

何时使用 Jython

Jython 在 Python options for Fiji 中有一个特定的利基市场:

  • 在 JVM 中,零生成开销。 对 Java API 的调用是普通方法调用 — 无序列化,无子进程边界。
  • 仅限 Python 2.7 语法。 Jython 不实现 Python 3。
  • 没有本机代码模块。 numpyscipyscikit-image以及依赖于本机代码的其他模块不可用。如果您需要它们,请使用 Fiji Python mode (PyImageJ)Appose 代替 — 请参阅the Python page
  • Python 标准库可用,Fiji 类路径上的任何 Java 类也是如此。

如果您需要 Python 语法、快速访问 ImageJImageJ2 Java API,并且不需要第三方 pip 包,那么 Jython 是一个不错的选择。如果您是编程新手并且只需要编写 Fiji 脚本,则 Groovy 通常是一个比较温和的起点。

ImageJ 的 Jython 基础知识

For an introduction in ImageJ scripting visit the page Scripting basics.

简介

本页面的目的不是教授如何使用 Python 编程。 documentation of Python 2 可以更好地实现这一目的。本页面的重点是展示 Python 语言的功能如何用于 ImageJ 脚本编写。

这就是为什么使用功能齐全的更复杂的示例。只需将代码复制到Script Editor并自行尝试即可。大量的内联文档用于解释实现。

你好世界

- 带打印

有两种方法可以将一些信息打印回给用户。
第一个是经典的 python print 语句,它将向控制台打印一些信息。
print "Hello world"
您可以打印任何类型的变量和对象。

print "This is a string followed by an int", 10

NB1 :如果在插件中使用,并且没有打开控制台窗口,则用户将看不到打印的信息(与下面的log功能相反)

NB2:在插件中使用时,使用大量打印语句可能会减慢执行时间(从脚本解释器执行时不会观察到)。

- 使用 IJ.log()

from ij import IJ
IJ.log("Hello world")
IJ.log("This is a string followed by an int " + str(10))

与 print 语句相反,日志函数将一些输出显示到日志窗口中(如果尚未打开,则新打开),并且仅接受字符串作为参数。

使用 GenericDialog 类选择图像

此示例脚本将创建最多 10 个新图像,并创建一个 GenericDialog 以选择其中的 3 个。最后,所选图像的名称将打印到日志窗口。建议将代码复制到Script Editor中自行运行。

以下列表链接到所用 Python 功能的文档:

使用脚本参数

第二个示例的灵感来自于用透射电子显微镜 (TEM) 记录的原子分辨率图像。此类图像显示出规则的结构(晶体),但由于信号低,图像有噪声。通过使用傅立叶滤波器可以增强对比度。

该脚本将创建一个周期性结构并添加一些随机噪声。用户可以控制创建的图像的参数。这是使用Script parameters实现的。傅里叶滤波是使用Recorder创建的。最后使用一个简单的图像计算器来展示函数可以作为参数传递。

此列表链接到此示例中引入的 Python 功能的文档:

使用 os.walk() 的批量开启器

我们还引入了一些内置于 Python 中的强大函数。另一个是来自 os 模块的walk()。它可用于遍历目录结构并处理所包含的文件。在此示例中,walk() 用于通过 ImageJ 的函数openImage() 批量打开图像。

要了解有关所用功能的更多信息,以下列表提供了其他信息的链接:

导入 Java 模块和类

Jython 的另一个重要功能是可以使用位于 imageJ 的 jar 文件夹中的 Java jar 包中的函数。

ImageJ 和 Fiji API

以下 API 文档列出了所有可用的模块和函数:

这些包是 Fiji 内置的,但只要您知道类的路径,就可以导入 jars 文件夹中的任何包。

例如,主要的内置 ImageJ 包之一称为 ij,Jython 脚本通常会在顶部编写如下内容:

from ij import IJ
# do stuff below....

这样做可以让您访问位于 ij 中的IJ 。您可以找到 ijhere 的说明。我们可以用 IJ 类做什么?单击 IJ 链接会将您带到 class documentationIJ 页面。此类包含“静态实用方法”,这意味着您可以在不实例化(调用构造函数)IJ类的情况下调用它们。稍后我们将介绍构造函数。查看 IJ 的文档,让我们重点关注方法 createImage (docs here)。可以像调用 Python 类上的方法一样调用此方法。文档显示您需要提供以下参数(括号中的类型):

  1. 标题(字符串)
  2. 宽度(整数)
  3. 高度(整数)
  4. 深度(整数)
  5. 位深度(int)

它返回一个 ImagePlus 对象。 ImagePlus对象在ImageJ中非常重要,您将了解它们的文档here。下面是如何导入和使用 IJ 类上的静态方法来创建图像的示例。

from ij import IJ # read this as: "from the ij package import the IJ class"
test_img = IJ.createImage("Test image", 512, 512, 1, 8)
# now check the type of test_img
print(type(test_img))
# <type 'ij.ImagePlus'>

这段代码表明我们已经成功创建了一个ImagePlus对象。查看 ImagePlus class 的文档,让我们使用一些方法来确保正确创建图像。

from ij import IJ
test_img = IJ.createImage("Test image", 512, 512, 1, 8)
# check the type:
print(type(test_img))
# <type 'ij.ImagePlus'>
title = test_img.getTitle()
width = test_img.width
height = test_img.height
print("{} is {} wide and {} tall.".format(title, width, height))
test_img.show()

我们使用 getTitle() method 访问标题,它不带参数并返回图像名称。我们通过访问test_img字段来访问图像的宽度和高度。这些不是方法,但包含有关类的信息。我们也可以使用 getWidth()getHeight() 方法。然后,我们在测试图像上调用 show() 方法,并且应该会弹出一个(非常无聊的)512X512 8 位图像。

这是我们使用 ImageJ 包和 RoiManager 类的另一个示例。根据 javadoc,RoiManager 类位于 ij.plugin.frame 中。因此代码将如下所示:

from ij.plugin.frame import RoiManager
RM = RoiManager()        # we create an instance of the RoiManager class
rm = RM.getRoiManager()  # "activate" the RoiManager otherwise it can behave strangely

使用 Jython 中的 OpenCV

OpenCV 可以通过 IJ-OpenCV 项目(作为 update site 提供)从 Jython 中使用,或者直接通过 JavaCPP org.bytedeco.javacpp.opencv_core 绑定使用。有关当前选项,请参阅OpenCV wiki page。请注意,之前存在于此处的 OpenCV-from-Jython 示例是几年前编写的,最近尚未经过验证;欢迎贡献新的例子。

为 ImageJ 自行编写的 Jython 模块

在 Jython 中,您可以将所有命令逐行写入单个文件中并执行它。要创建简洁的程序,可以使用functionsclasses来构建代码。为了防止对经常使用的函数和类使用复制和粘贴,可以选择modules。模块是包含要导入其他文件的函数和类的文件。

要加载模块,必须将它们保存到 Jython 可以找到它们的目录中。两行代码将向您显示这些目录:

from sys import path
print(path)

运行此代码时,结果是类似的输出

['/home/michael/Software/ImageJ.app/jars/Lib', '/home/michael/Software/ImageJ.app/jars/jython-shaded-2.7.0.jar/Lib', '__classpath__', '__pyclasspath__/']

这告诉我们 ImageJ/Fiji 目录中的文件夹 jars/Lib/ 是保存模块的正确位置。由于 Lib/ 默认不存在,我们必须创建它。

当第一次导入模块时,Jython 会将其编译为 Java 代码。如果存在名为 myModule.py 的模块,Jython 将创建一个名为 myModule$py.class 的文件。下次导入模块时,jython 解释器将使用 .class 文件而不是 .py 文件,即使此 .py 文件已被修改。

要强制解释器使用 py 脚本的最新版本,有两种可能性:

  • 关闭斐济,删除myModule$py.class文件并重新启动斐济
  • 使用以下代码行(位于 stackoverflow)将强制 Jython 重新编译所有模块
    # Use this to recompile Jython modules to class files.
    from sys import modules
    modules.clear()
    # Imports of Jython modules are placed below:
    import myModule
    

    添加自定义目录

如果您不想使用jars/Lib/来保存模块,则必须扩展数组sys.path

from sys import path
from java.lang.System import getProperty

# extend the search path by $FIJI_ROOT/bin/
# 'fiji.dir' works for plain ImageJ, too.
path.append(getProperty('fiji.dir') + '/bin')
# an alternative can be the users home directory
# path.append(getProperty('user.home') + '/JythonModules')

# Now you can import $FIJI_ROOT/bin/myModule.py
import myModule

函数getProperty()接受更多的字符串。列表可在The Java Tutorials中找到。

为 ImageJ 自行编写 Jython 包

在完美组织 Jython 代码的过程中,下一步是 packages。 Jython 包是一个文件夹,其中包含一组模块脚本以及 __init__.py 文件。该文件可以为空。以下是 ImageJ.app/jars/Lib 文件夹的典型结构:

    ImageJ.app/jars/Lib/
    -- myModule.py
    -- myPackage/
       -- __init__.py
       -- mathTools.py
       -- customFilters.py
       -- fftTools.py
    -- myPackage2/
       -- __init__.py
       -- mathTools.py
       -- stackProcessing.py

有两个包和一个模块。第一个包包含三个模块,第二个包包含两个模块。我们可以通过不同的方式导入模块:

# Import the single module using the default name:
import myModule

# Import mathTools from the first package
import myPackage.mathTools
# Use a function from the imported module
myPackage.mathTools.aFunction()

# Import mathTools from the second package
from myPackage2 import mathTools
# Use a function from the imported module without prefixing the package
mathTools.aFunction()

# Import customFilters from the first package and rename it
from myPackage import customFilters as filters
# Use a function from customFilters.py
filters.aFunction()

# Importing all module from a package
from myPackage2 import *
# The next line will fail
stackProcessing.aFunction()

最后一次导入失败的原因是__init__.py为空。使用import *时,我们必须定义导入包的哪些模块。这是通过在 __init__.py 中设置变量 __all__ 来完成的。对于myPackage2,需要这行代码:

__all__ = ["mathTools", "stackProcessing"]

除了设置此变量之外,__init__.py 文件还可以包含在导入包时执行的普通 Jython 代码。

将软件包打包到 JAR 文件中

Jython 的一个有趣功能是搜索 JAR (file format) 内的包和模块。可以通过将所有内容打包到单个 myPackages.jar 中来修改上一节中的文件夹结构。 JAR 文件的名称并不重要。所有导入的工作方式都与之前解释的相同。

    ImageJ.app/jars/Lib/
    -- myPackages.jar
       -- myModule.py
       -- myPackage/
          -- __init__.py
          -- mathTools.py
          -- customFilters.py
          -- fftTools.py
       -- myPackage2/
          -- __init__.py
          -- mathTools.py
          -- stackProcessing.py

这种方法的优点是您可以轻松共享您的包。例如,您可以将 JAR 文件上传到 update site。也可以上传 .py 脚本来更新站点,而无需打包到 jar 中。 jar 的优点是它们允许更系统地定义依赖关系。

与 Jars/Lib 中的脚本相反,菜单宏脚本未编译,并且如上所述,它们不能导入到其他脚本中,因为默认情况下 Plugin 文件夹不驻留在 Jython 搜索路径中。

这就是为什么给定项目分布在 2 个不同的 jar 文件中,如第 here 节所述。

使用maven构建包

使用 Maven,您可以自动将 Jython 代码打包到 JAR 文件中。仅当您已经使用 Maven 时才建议使用此方法,因为安装和学习如何使用 Maven 不值得节省自动化打包的时间。

在 GitHub 上,您会找到可用作模板的 example project。只要运行mvn package,maven就会在target目录下生成一个JAR文件。

链接

参考文献

[^1]:Wikipedia entry on Jython