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

如何使用新 API 在 TrackMate 中创建具有 ROI 的点

##介绍

第 7 版TrackMate,检测算法仅限于返回阵列的位置及其半径。所有检测均由 frame, x, y, z, radius, quality 形状的元组表示。这非常适合实现“检测算法”,该算法返回对象的位置,但忽略其形状。本教程系列中的第 previous page 展示了如何使用基本代码来实现作为 TrackMate 检测器的算法。

在版本 7 中,我们重写了 TrackMate 的很大一部分,以消除这个限制,至少在 2D 方面是这样。我们更改了数据模型,方便 TrackMate 中的§§0§§§可能_存储形状,同时可以不影响现有的检测器。我们制作了一个新的 API 来促进在 TrackMate 中实现_重构算法,它返回可以对象的形状。它们的形状和形状用于计算形态特征或测量对象构造内部的强度。我们使用这个 API在 TrackMate 中实现7 new segmentation algorithms,集成了 Java 中可用的一些最佳分段工具。本页介绍了如何使用此 API 自行实现自己的分段算法,并使其成为 TrackMate 的一等公民,就像我们在本系列中介绍的其他 TrackMate 模块一样。

我们首先需要回顾一下新的API本身,它基本上可以归结为一个提供静态方法的类。,对于其他检测器,我们需要在检测器工厂中添加一些标志来告诉TrackMate,我们构建的是返回对象形状的分割算法。最后,我们似乎看到对检测器的多线程进行一些调整,以适应您可能需要集成到TrackMate中的各种现有分割工具。我们将使用上述7种分割算法的示例来作为本教程的基础。

创建存储对象目的点。

Spot课程中发生的变化

从版本 7 开始,TrackMate 中的 SpotRoi 类有一个新字段:Spot 对象。

它基本上由存储相对于点中心(x, y, z元组)的对象创建的2D搬运组成。它唯一的字段是两个double[]仓库,用于表示的xpyp坐标,沿着排序。最后一点与第一点相连。

检测算法返回的Spot对象的SpotRoi设置为null。 TrackMate 中分段算法的唯一区别在于,它返回 Spot 对象和非null SpotRoi 对象。

限制

让我们从坏消息开始:我们只能处理 2D 图像的对象。关键是我们还没有一个好的方法来存储斐济的 3D 效果。这可能会改变,但目前您将看到的所有内容仅适用于 2D 效果,即加工。

另外,对象形状必须由 simple polygon 表示。即:不能有孔,操作的线不能互相相交。如果创建具有相交的点,则结果是不确定的。如果您的对象指定了孔,并且尝试为它们创建点,则这些孔将被忽略。

从运营创建Spot

xpyp的两个double[]存储必须相对于点X和Y位置进行设置。您自己可以计算它们,但使用静态方法createSpot()物理更容易,它返回坐标中的xy中的一点:

double[] xp = ...;
double[] yp = ...;
double quality = ...;
Spot spotWithShape = SpotRoi.createSpot(xp, yp, quality);

对于Spot中存储的所有坐标,X和Y必须采用物理单位(例如,如果图像以微米为单位布局,则为微米)。此方法将负责计算点xy中心,计算硬盘的读出点,并正确地对应同一点。

示例:如何在 StarDist 中创建灯光

Fiji StarDist implementation将找到的对象返回为作业。因此,我们只需使用此方法将其桥接到TrackMate。在MaskUtils中,您将找到以下几行:

/*
 * We received the 'polygons' object from the StarDist runner.
 * As for all the other detectors, this instance have to return
 * a list of Spot 'spots' containing all the objects segmented in 
 * the current time-point.
 */

// Create spots from output.
for ( final Integer polygonID : polygons.getWinner() )
{
    // Collect quality = max of proba.
    final PolygonRoi roi = polygons.getPolygonRoi( polygonID );
    proba.setRoi( roi );
    final double quality = proba.getStatistics( Measurements.MIN_MAX ).max;

    // Create ROI.
    final Polygon polygon = roi.getPolygon();
    final double[] xpoly = new double[ polygon.npoints ];
    final double[] ypoly = new double[ polygon.npoints ];
    for ( int i = 0; i < polygon.npoints; i++ )
    {
        /*
         * Here we convert the polygon points in pixel coordinates to 
         * physical coordinates (multiplication by the calibration).
         * We also need to offset them by the interval top-left corner
         * in case the user ask to perform segmentation in a sub-region
         * of the source image.
         */
        xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + polygon.xpoints[ i ] );
        ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + polygon.ypoints[ i ] );
    }
    Spot spot = SpotRoi.createSpot( xpoly, ypoly, quality );
    // 'spots' is the list of Spot this detector will return.
    spots.add( spot );
}

如您所见,这相当简单。它说明了如何在 TrackMate 中插入任何返回作业的东西并总计创建一个新的检测器。如果您想集成返回图像掩码、概率图或标签的技术,我们还为这些情况制作了实用方法。

从计算器图像或阈值图像创建演示集合

MaskUtils.fromThresholdWithROI()方法

您可以使用许多上述方法来实现您自己的分割算法。我们还提供了一个简单的 API 来促进在 TrackMate 中集成现有的分割算法。现有算法或控制器图像、标签图像,或返回某种阈值概率图来生成对象。 该 API 主要由类 StarDistDetector.java 中的实用方法组成,这些方法接受此类输入并输出包含对象和 Spot 集合。 让我们从如何输入蒙版图像开始。

为了获得最大的灵活性,TrackMate 中的控件图像可以是任何类型,只要它使用标量、像素类型。我们简单地说,对象是通过连接所有值严格大于 0 的像素而形成的。 因此,这样的模具制作溶液的方法就是导入阈值图像的方法:

MaskUtils.fromThresholdWithROI()

	/**
	 * Creates spots <b>with their ROIs</b> from a <b>2D</b> grayscale image,
	 * thresholded to create a mask. A spot is created for each
	 * connected-component of the mask, with a size that matches the mask size.
	 * The quality of the spots is read from another image, by taking the max
	 * pixel value of this image with the ROI.
	 * 
	 * @param <T>
	 *            the type of the input image. Must be real, scalar.
	 * @param <S>
	 *            the type of the quality image. Must be real, scalar.
	 * @param input
	 *            the input image. Must be 2D.
	 * @param interval
	 *            the interval in the input image to analyze.
	 * @param calibration
	 *            the physical calibration.
	 * @param threshold
	 *            the threshold to apply to the input image.
	 * @param simplify
	 *            if <code>true</code> the polygon will be post-processed to be
	 *            smoother and contain less points.
	 * @param numThreads
	 *            how many threads to use for multithreaded computation.
	 * @param qualityImage
	 *            the image in which to read the quality value.
	 * @return a list of spots, with ROI.
	 */
	public static final < T extends RealType< T >, S extends NumericType< S > > List< Spot > fromThresholdWithROI(
			final RandomAccessible< T > input,
			final Interval interval,
			final double[] calibration,
			final double threshold,
			final boolean simplify,
			final int numThreads,
			final RandomAccessibleInterval< S > qualityImage )

该方法适用于二维图像。将创建具有对象的点。让我们回顾一下它的输入:

  • input 是空码输入。它必须是 T 类型的 RandomAccessible,这是 TrackMate 自动提供给其检测器的经典框架。
  • interval 是要分析的输入中的间隔。与所有其他检测器一样,TrackMate 将像素信息作为无界 RandomAccessible 返回,我们需要指定要分析图像的哪一部分。同样,这对于所有检测器都是通用的,并由 TrackMate 提供。
  • calibrationdouble[] 3个元素的阵列,其中包含图像的像素大小(像素宽度、高度和深度)。它可以从您在斐济设置的输入布局中读取的。同样,所有这些对于所有都是通用的。
  • threshold 是一个双精度值,该值的强度将被视为对象的标准。这是特定于该检测器的并且由用户设置。对于控制器图像,它是 0。
  • simplify是一个布尔标志,表明用户是否平滑和简化创建。这对于正确的测量形态特征非常重要,我们在其他地方完整记录了它。
  • numThreads。以这种方式创建点是多线程的,您可以在此处设置要使用的线程数。同样,如果您将帐篷声明为 Multithreaded,则 TrackMate 将自动设置帐篷的 numThreads 值,并且您可以在此处使用它。如果您的检测器不是多线程的,请使用值 1。
  • qualityImage是一张图像,我们表格中读取所创建显示的质量值。它必须在与interval参数相同的间隔上定义,并且像素必须为NumericType和标量。如果您无法设置通道或图像的质量(如蒙版),只需将null传递给此参数,对象的质量值将设置区域。否则,这将是质量图像中对象内部的最大像素值。

示例:面罩检测器

我们来看看它是如何用在口罩检测器上的。由于模拟器图像被简单地视为阈值为 0 的灰度图像,因此模拟器检测器实际上是在 MaskUtils.fromThresholdWithROI() 类中实现的。(this line返回值阈值设置为 0 的ThresholdDetector。参见MaskDetectorFactory。) 以下是process()方法的内容:

	@Override
	public boolean process()
	{
		final long start = System.currentTimeMillis();
		if ( input.numDimensions() == 2 )
		{
			/*
			 * 2D: we compute and store the contour.
			 */
			spots = MaskUtils.fromThresholdWithROI( input, interval, calibration, threshold, simplify, numThreads, null );

		}
		else if ( input.numDimensions() == 3 )
		{
			/*
			 * 3D: We create spots of the same volume that of the region.
			 */
			spots = MaskUtils.fromThreshold( input, interval, calibration, threshold, numThreads );
		}
		else
		{
			errorMessage = baseErrorMessage + "Required a 2D or 3D input, got " + input.numDimensions() + "D.";
			return false;
		}

		final long end = System.currentTimeMillis();
		this.processingTime = end - start;

		return true;
	}

请注意,对 2D 和 3D 图像的处理方式不同。如上所述,新 API 仅支持 2D 图像的对象。方法§§§3图像§§§是对 3D 的MaskUtils.fromThresholdWithROI()的补充,但返回没有的Spot对象。这里创建具有一个半径的点,使得具有该半径的球体具有与分段对象相同的体积。

使用此 API 可以使检测器代码变得非常短。您可以采用相同的方法来集成分割器来输出代理图像或阈值图像。例如,这就是我们集成_Traininable Weka 分段_插件和_ilastik_像素分类器所做的事情。

示例:Weka 检测器

Weka 的义务不是很复杂。调用 Weka 的大部分工作是在 WekaRunner.computeProbabilities() 类中完成的。 运行Weka是在WekaRunner方法中完成的。它返回指定输入和指定类别的概率分类。我们就不详细说了。 但根据这个概率创建点很简单。方法IlastikRunner方法,类似于上一段中描述的方法:

	public List< Spot > getSpots( final RandomAccessibleInterval< T > proba, final double[] calibration, final double threshold, final boolean simplify )
	{
		final List< Spot > spots;
		if ( isProcessing3D )
		{
			spots = MaskUtils.fromThreshold(
					proba,
					proba,
					calibration,
					threshold,
					numThreads,
					proba );
		}
		else
		{
			spots = MaskUtils.fromThresholdWithROI(
					proba,
					proba,
					calibration,
					threshold,
					simplify,
					numThreads,
					proba );
		}
		return spots;
	}

这里分割概率图的阈值由用户设置。 由于我们有概率图,因此我们可以用它来计算概率导出的质量值。

示例:ilastik 芭

ilastik 的工作原理完全相同。它有一个 WekaRunner.getSpots() 类,负责调用 ilastik 放置结果转换为现货集合。 ilastik 检测器只是对其进行简单的调用。

然而,我们的算法使用了特殊的时间点片段。事实上,ilastik 运行程序需要立即接收_所有_要处理的时间点,在它们上运行 ilastik,然后返回。我们将在下一节讨论这个问题。

以下是对运行程序代码的一些解释:

/*
 * This corresponds roughyl to the lines 94-110 of the IlastikRunner class.
 */

// Path to the ilastik project, provided by the users.
final File projectFile = new File( projectFilePath );

// Create an ilastik pixel classifier, configured with the classifier in the specified project.
final PixelClassification classifier = new PixelClassification(
  executableFilePath,
  projectFile,
  logService,
  statusService,
  numThreads,
  maxRamMb );
final PixelPredictionType predictionType = PixelPredictionType.Probabilities;

// Run the classifier on the 'cropped' source image. This will result in the PixelClassification
// Actually RUNNING ilastik in the background, passing input and output images as files saved
// in a temp folder. But this is transparent to us.
final ImgPlus< T > output = classifier.classifyPixels( cropped, predictionType );

// The output has one channel per class in the classifier, so we need to get the channel that
// contains the probability for our object of interest only (specified by the user via the classID 
// parameter).
final ImgPlus< T > proba = ImgPlusViews.hyperSlice( output, output.dimensionIndex( Axes.CHANNEL ), classId );

// Etc.
...
  
// Not we just have to import this probability map as TrackMate ROIs. Since we received the proba
// for ALL time-points at once, we need to process it time-point by time-point:
  
for ( int t = 0; t < proba.dimension( timeIndex ); t++ )
		{
			final List< Spot > spotsThisFrame;
			final ImgPlus< T > probaThisFrame = TMUtils.hyperSlice( proba, 0, t );

			if ( DetectionUtils.is2D( probaThisFrame ) )
			{
				/*
				 * 2D: we compute and store the contour.
				 */
				final boolean simplify = true;
        
        // In 2D we again use the MaskUtils utilities. Note that we use the '...WithROI()'
        // method version. In 2D we can import objects with their contours.
				spotsThisFrame = MaskUtils.fromThresholdWithROI(
						probaThisFrame,
						probaThisFrame,
						calibration, 
						probaThreshold, 
						simplify, 
						numThreads, 
						probaThisFrame );
			}
			else
			{
				/*
				 * 3D: We create spots of the same volume that of the region.
				 * So we use the methods without the '...WithROI()'.
				 */
				spotsThisFrame = MaskUtils.fromThreshold(
						probaThisFrame,
						probaThisFrame,
						calibration,
						probaThreshold,
						numThreads,
						probaThisFrame );
			}
  // etc...

控制时间点的切片

通常,TrackMate 会自动执行多个时间点的多线程处理。当您开发检测器时,第 SpotDetector 实例应该仅在一个时间点运行。TrackMate 中的处理逻辑将负责向该检测器提供单个时间点图像,并将检测结果合并到 SpotCollection 中的正确位置。

但是因为我们想要与ilastik等算法和工具集成,所以我们需要另外提供一种处理时间点的方法。例如,ilastik希望立即接收其所有时间点,进行处理,并返回所有时间点的概率。这比多次调用ilastik(每个时间点调用一次)要快的部分。

因此,从 v7 开始,TrackMate 中的SpotDetectorFactory 有一个新的层次结构:

SpotDetectorFactory

SpotDetectorFactory is the initial interface for spot detector factories that generate one detector per time-point. It is will suited to detection and segmentation algorithms that can run concurrently on several time-points at once without a penalty too large. All detectors I know of, except the ilastik detector, derive from this interface.

注意其唯一的具体方法如下:

	/**
	 * Returns a new {@link SpotDetector} configured to operate on the given
	 * target frame. This factory must be first given the <code>ImgPlus</code>
	 * and the settings map, through the <code>#setTarget(ImgPlus, Map)</code>
	 * method.
	 *
	 * @param interval
	 *            the interval that determines the region in the source image to
	 *            operate on. This must <b>not</b> have a dimension for time
	 *            (<i>e.g.</i> if the source image is 2D+T (3D), then the
	 *            interval must be 2D; if the source image is 3D without time,
	 *            then the interval must be 3D).
	 * @param frame
	 *            the frame index in the source image to operate on
	 */
	public SpotDetector< T > getDetector( final Interval interval, int frame );

因此将在每个时间点生成一个SpotDetector实例。这样的检测器具有用于输出的§§2§§§,预计该检测器仅包含在其配置运行的帧中找到的点。TrackMate将负责构成SpotCollection中不同时间点的所有List< Spot >

SpotGlobalDetectorFactory

SpotGlobalDetectorFactory is a new interface that does not slice time-points. Its unique specific method returns a SpotGlobalDetector that is expected to process all time-points at once. Such a detector is instantiated only once per detector run.

	/**
	 * Returns a new {@link SpotDetector} configured to operate on all the
	 * time-points. This factory must be first given the <code>ImgPlus</code>
	 * and the settings map, through the <code>#setTarget(ImgPlus, Map)</code>
	 * method.
	 *
	 * @param interval
	 *            the interval that determines the region in the source image to
	 *            operate on. This must <b>not</b> have a dimension for time
	 *            (<i>e.g.</i> if the source image is 2D+T (3D), then the
	 *            interval must be 2D; if the source image is 3D without time,
	 *            then the interval must be 3D).
	 */
	public SpotGlobalDetector< T > getDetector( final Interval interval );

SpotGlobalDetector输出SpotCollection,包含电影所有时间点的所有付费。

基本SpotDetectorFactoryBase接口和has2Dsegmentation标志

上面的两个接口派生自包含公共方法的母接口:SpotDetectorFactoryBase。如果正在为 TrackMate 构建分段算法,它包含一个非常重要的方法:

	/**
	 * Return <code>true</code> for the detectors that can provide a spot with a
	 * 2D <code>SpotRoi</code> when they operate on 2D images.
	 * <p>
	 * This flag may be used by clients to exploit the fact that the spots
	 * created with this detector will have a contour that can be used
	 * <i>e.g.</i> to compute morphological features. The default is
	 * <code>false</code>, indicating that this detector provides spots as a X,
	 * Y, Z, radius tuple.
	 * 
	 * @return <code>true</code> if the spots created by this detector have a 2D
	 *         contour.
	 */
	public default boolean has2Dsegmentation()
	{
		return false;
	}

如果您的支架可以返回 2D 的图像光斑形状,请重写此方法以使其返回 true。这对于让 TrackMate 知道它在检测器创建的位置计算形态特征非常重要。如果您在 GUI(区域,…)中看不到形态特征,这很可能是由于此方法造成的。

正如您在本页上面的示例中看到的,新检测器的方法返回 true。