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

如何为 TrackMate 编写自己的边缘特征分析器算法

简介

本页是一个教程,展示如何在 TrackMate 中集成您自己的边缘特征分析器算法。专门用于 TrackMate 扩展的系列教程中的第一篇,愿意扩展 TrackMate 的科学家应该首先阅读它。

所有这些教程都假设您熟悉Java开发。您应该熟悉java核心概念,例如面向对象设计、继承、接口等…理想情况下您甚至会知道maven的存在并且它可以帮助您编译软件。除此之外,教程表示提供您需要了解的内容。

边缘特征分析器是一种算法,可以将一个或多个标量数值特征与边缘或 TrackMate 中两个点之间的链接连接起来。例如,瞬时速度是一个边缘特征(您需要计算两个链接点和循环时间间隔),它正好由称为EdgeAnalyzer的算法提供。

TrackMate 模块

TrackMate通过编写模块进行扩展。模块只是为TrackMate提供GUI和API封装的核心功能的基本算法。有7类模块:

  • 检测算法
  • 贷款融资算法
  • 显现的数字特征(例如平均强度等..)
  • 链接的数字特征(如速度、方向等)
  • 轨道的数字特征(总管道、长度等…)
  • 可视化工具
  • 后处理操作(导出、数据处理等…)

所有这些模块都实现特定于模块类的接口。例如,边缘分析器算法将实现EdgeSpeedAnalyzer.java接口。因此有7个接口。它们确实有一个共同点,那就是它们都扩展了称为this github repository的母模块接口。

TrackMateModule用于两个基本目的:

  • 它本身扩展了SciJavaPlugin接口,这将促进新模块的自动发现。我们将在最后讨论这一点。

  • 它具有GUI集成的基本方法:

    1. getKey() returns a unique string identifier that is used internally to reference the algorithm. For instance: "EDGE_VELOCITY_ANALYZER"
    2. getName() returns a string suitable to be displayed in the GUI that named the algorithm. For instance "Edge velocity".
    3. getIcon() returns an ImageIcon to be displayed in the GUI.
    4. getInfoText() returns a html string that briefly documents what the algorithm does. Basic html markup is accepted, so you can have something like
"<html>Plot the number of spots in each frame as a function <br>of time. Only the
<u>filtered</u> spots are taken into account. </html>"

这些是用于将模块集成到 GUI 中的方法。根据模块的类别,有些可能会被明显忽略。例如,本教程的边缘分析器主题忽略图标和信息文本,因为它们在 GUI 中默默地使用以提供新功能。

项目基本结构

在我们具体讨论边缘分析器之前,您需要设置一个能够简化 TrackMate 模块开发的开发环境。无需列出要求,只需查看 TrackMateModule,然后克隆它。它包含本教程系列的文件,更重要的是,它被配置为依赖于最新的 TrackMate 版本,这将生成可用于您的代码。

使用maven编译此项目将生成一个jar,您可以将其放入fiji插件文件夹中。然后您的模块将被自动检测并集成到TrackMate中。

但稍后会详细介绍。

让我们开始吧

但让我们回到我们的边缘分析器。

对于本教程,我们将做一些简单的事情,至少在数学上。我们将编写一个边缘分析器,它可以返回 XY 平面中链接的角度(以弧度为单位)。

因此,在我们的项目中为您的新分析器创建一个包,例如fiji.plugin.trackmate.examples.edgeanalyzer

在这个包中,创建一个类EdgeAngleAnalyzer并让它实现FeatureAnalyzer接口。你应该得到这样的东西:

    package plugin.trackmate.examples.edgeanalyzer;
    
    import fiji.plugin.trackmate.features.edges.EdgeAnalyzer;
    
    public class EdgeAngleAnalyzer implements EdgeAnalyzer
    {}

值得注意的是,我们提供了一个空白构造函数。这非常:通过我们使用 SciJavaPlugin 集成的方式,我们不能使用构造函数来传递任何对象引用。如果您的分析器需要一些未通过接口方法提供的对象,那么您无法直接使用 TrackMate 进行编码。但是,我们应该用我们拥有的东西来覆盖大多数情况。

特征分析器具体方法

Eclipse 会即时聊天(我猜你正在使用 Eclipse;但是当即时聊天时,一切都趋向于通用)你的类需要实现一些抽象方法。弹出多种方法。

我们看到了上面讨论的通用边界模块方法,以及一些特定于分析器的方法。实际上,大多数新方法对于所有特征分析器(点、形状或边界)都是通用的。这些方法属于EdgeAnalyzer接口,当然,EdgeAnalyzer扩展了该接口。

它们的存在是因为TrackMate需要知道您的特征分析器的作用。由于它计算数字特征,因此它需要知道它计算的特征、它们的名称、短名称(当我们想要在GUI的灭菌部分显示它们时)以及它们的物理尺寸。事实上,TrackMate想要知道您生成的特征的维度,因为它的部分编码是由一位矛盾的物理学家编写的,他不希望将角度和速度均匀在同一个图表上。

这6种方法是:

  • getFeatures()返回一个字符串列表,用于标识分析器生成的特征。可以有多个。这个列表必须包含可在XML文件中使用的字符串。从历史上看,我们使用大写字符串,以java常量的形式,例如DISPLACEMENT。我们称它们为功能键。

  • getFeatureNames()返回一个将功能键链接到功能名称的映射。例如,在 GUI 中,希望我们显示“地质”而不是“地质”,所以这就是这张地图的。重要的是,该映射目的的键是上面列表中定义的键。

  • getFeatureShortNames()返回另一张具有相同规则的地图。当GUI中需要时,我们只是使用它的值来显示功能的短名称。没有关于如何局部功能名称的一般建议;尝试直到合适的状态。

  • getFeatureDimensions()返回最后一张地图,为您的特征提供维度。物理尺寸列于FeatureModel中。

  • getIsIntFeature()只是关于衣。它返回一个映射,说明哪些特征是整数映射的。例如,如果你有一个计算事物的功能糖,例如邻居数量,则应注意功能映射到此处的true。这个实际上不是“真正”有用的;如果设置不正确,不会有问题,也不会损失精度。这只是为了正确的数字。我希望当有2个邻居时,显示的邻居数量是“2”而不是“2.0000000000001”。在我们的示例中,我们测量角度,因此该特征应映射到false

  • isManualFeature()返回影响此分析器计算的所有特征的单个标志。手动功能是TrackMate v2.3.0中引入的特殊功能。我们暂时把它放在一边。我们的角度特征是由我们即将编写的自动代码计算的。所以这个方法应该返回false

在本教程中,我们的分析器仅返回一个特征,即角度。所以具体的实现可以是:

    package plugin.trackmate.examples.edgeanalyzer;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.swing.ImageIcon;
    
    import fiji.plugin.trackmate.Dimension;
    import fiji.plugin.trackmate.features.edges.EdgeAnalyzer;
    
    public class EdgeAngleAnalyzer implements EdgeAnalyzer
    {
    
        // The string key that identifies our analyzer.
        private static final String KEY = "Edge angle";
    
        // The only feature we compute here.
        private static final String EDGE_ANGLE = "EDGE_ANGLE";
    
        private static final List< String > FEATURES = new ArrayList< String >( 1 );
    
        private static final Map< String, Boolean > IS_INT = new HashMap< String, Boolean >( 1 );
    
        public static final Map< String, String > FEATURE_NAMES = new HashMap< String, String >( 1 );
    
        public static final Map< String, String > FEATURE_SHORT_NAMES = new HashMap< String, String >( 1 );
    
        public static final Map< String, Dimension > FEATURE_DIMENSIONS = new HashMap< String, Dimension >( 1 );
    
        // Let's set the feature list, names, short names and dimensions.
        static
        {
            FEATURES.add( EDGE_ANGLE );
            IS_INT.put( EDGE_ANGLE,  false );
            FEATURE_NAMES.put( EDGE_ANGLE, "Link angle" );
            FEATURE_SHORT_NAMES.put( EDGE_ANGLE, "Angle" );
            FEATURE_DIMENSIONS.put( EDGE_ANGLE, Dimension.ANGLE );
        }
    
        private long processingTime;
    
        /*
         * TRACKMATEMODULE METHODS
         */
    
        @Override
        public String getKey()
        {
            return KEY;
        }
    
        // Return a user-compliant name for this analyzer.
        @Override
        public String getName()
        {
            return "Edge angle";
        }
    
        // We do not use info texts for any feature actually.
        @Override
        public String getInfoText()
        {
            return "";
        }
    
        // The same: we don't use icons for features.
        @Override
        public ImageIcon getIcon()
        {
            return null;
        }
    
        @Override
        public List< String > getFeatures()
        {
            return FEATURES;
        }
    
        @Override
        public Map< String, String > getFeatureShortNames()
        {
            return FEATURE_SHORT_NAMES;
        }
    
        @Override
        public Map< String, String > getFeatureNames()
        {
            return FEATURE_NAMES;
        }
    
        @Override
        public Map< String, Dimension > getFeatureDimensions()
        {
            return FEATURE_DIMENSIONS;
        }
    
        @Override
        public Map<String, Boolean> getIsIntFeature()
        {
            return Collections.unmodifiableMap(IS_INT);
        }
    
        @Override
        public boolean isManualFeature() 
        {
            // This feature is calculated automatically.
            return false;
        }

多线程和基准测试方法

还有 4 种方法,我们现在将跳过。它们与分析器的多线程方面有关。您可以对分析器进行编码以利用多线程环境,TrackMate 将通过以下方法对其进行配置:

        @Override
        public void setNumThreads()
        {
            // We ignore multithreading for this tutorial.
        }
    
        @Override
        public void setNumThreads( final int numThreads )
        {
            // We ignore multithreading for this tutorial.
        }
    
        @Override
        public int getNumThreads()
        {
            // We ignore multithreading for this tutorial.
            return 1;
        }

还有

    public long getProcessingTime()

返回计算特征所花费的毫秒数。

核心方法

真正重要的是实际执行工作的两种方法:

  • isLocal()
  • process( final Collection< DefaultWeightedEdge > edges, final Model model )

让我们看看他们如何找到我们的角度分析仪示例。

isLocal()

此方法只有一个布尔值,表明您计算的特征是否是“局部”特征。我所说的局部是指:一条边的特征值是否依赖于其他边?如果不是,那么它是一个局部返回特征:它不会影响其他边缘。如果是,那么它不是局部的。请注意,它适用于分析仪提供的所有功能。

区别促进了 TrackMate 中的一些优化。您知道 TrackMate 可以自动和手动跟踪。事实证明,在同一个软件中执行这两个操作对具有认知的编码来说,特别是当您在手动修改非常大的数据集时提供良好的性能时。当您手动数据修改希望时,TrackMate会实时重新计算所有功能,以便它们始终保持同步。但是,如果您对一条边进行一次准时修改,则您重新重新计算该边的特征,而不是为所有其他边(如果它们不行驶)重新计算特征。如果功能是本地的,TrackMate可以做到这一点。这就是这个方法存在的原因。

边缘局部特征的一个例子是瞬间速度。边的速度只依赖于边,而不取决于其余边。你可能会说,如果你修改一个点的位置,所有接触该点的边缘都会受到影响,所以它不是局部的。但不会:所有接触该点的边都将被修改,因此将被标记为更新,但其他未的边的速度不会受到影响。所以速度是一个局部特征。

非局部边缘特征的一个例子是与最近邻居的距离的边缘。如果移动边,其自身的特征值就会受到影响。但同时也会影响到许多其他边缘的最近距离。所以它是非一条的,我们先验必须为所有边重新计算它。

在我们的例子中,我们正在编写一个边缘分析器,它返回单个的角度,而不考虑其他边缘的角度。因此,它是局部的特色。

process( final Collection< DefaultWeightedEdge > edges, final Model model )

实际执行工作的方法则不太详细。具体实现由edges(要计算其特征的边缘的集合)和model(包含您需要的TrackMate模型的所有信息)提供。

想要知道一件事:计算出特征的数值后,您需要将其存储在 Dimension enum 中。特征模型是主模型的一部分。

它的工作原理类似于2D地图:

        final FeatureModel fm = model.getFeatureModel();
        Double val = Double.valueOf(3.1451564);
        String FEATURE = "MY_AWESOME_EDGE_FEATURE";
        fm.putEdgeFeature( edge, FEATURE, val );

对于我们的XY边缘角度,方法内容如下:

        @Override
        public void process( final Collection< DefaultWeightedEdge > edges, final Model model )
        {
            final FeatureModel fm = model.getFeatureModel();
            for ( final DefaultWeightedEdge edge : edges )
            {
                final Spot source = model.getTrackModel().getEdgeSource( edge );
                final Spot target = model.getTrackModel().getEdgeTarget( edge );
    
                final double x1 = source.getDoublePosition( 0 );
                final double y1 = source.getDoublePosition( 1 );
                final double x2 = target.getDoublePosition( 0 );
                final double y2 = target.getDoublePosition( 1 );
    
                final double angle = Math.atan2( y2 - y1, x2 - x1 );
                fm.putEdgeFeature( edge, EDGE_ANGLE, Double.valueOf( angle ) );
            }
        }
    
        @Override
        public boolean isLocal()
        {
            return true;
        }

使用分析器可发现

现在,您的分析仪可以正常工作。它编译并返回预期结果。一切都很好。

只是 TrackMate 甚至不知道它的存在。它的第一个在他孤独的紫色里,毫无用处。

在 TrackMate v2.2.0 中,除了修改或分叉它,然后从头开始重新编译和重新部署之外,没有其他方法可以扩展 TrackMate。在 v2.2.0 中,我们受益于 ImageJ2 团队的插件努力,他们构建了一个非常简单且非常聪明的发现机制,该机制允许简单地将 jar 放在 Fiji 的文件夹中并让 TrackMate 察觉。最重要的是,它简单明了。

请在类声明之前添加以下行:

    @Plugin( type = EdgeAnalyzer.class )
    public class EdgeAngleAnalyzer implements EdgeAnalyzer
    {
    ...

就是这样。让我重复一遍:

To make a TrackMate module discoverable in TrackMate, just annotate its class with @Plugin( type = TheTrackMateModuleClassYouAreExtending.class ).

@Plugin( type = EdgeAnalyzer.class )行就足够了。还有一些机制允许对优先级、可见性(在GUI菜单中)或启用/禁用进行强度,但稍后会看到。

现在,只需编译您的项目,将生成的 jar 放入 Fiji 插件文件夹中。这是你得到的:

Develop edge analyzer

总结

太棒了,不是吗?

您可以找到此示例的完整源代码here。它还可以用于分析仪的模板。

Jean-Yves Tinevez January 2014