基于ImageJ的GCaMP实验视频自动化工作流(二):视频ROI的选择和批量Time Series Analyzer V3处理,并导出可用于作图的csv

引言

在 GCaMP 实验中,使用 ImageJ 批量处理视频文件是一项重要任务,尤其是当文件数量较多时,操作流程稍有疏漏就可能导致错误。本教程将介绍如何在 ImageJ 中自动化处理一些 GCaMP 视频数据,包括以下内容:

  1. 视频 ROI 的选择: 在 GCaMP 实验中,准确选择信号和背景区域非常重要。
  2. 批量使用 Time Series Analyzer V3 处理: 自动化处理视频并导出 Time Series 数据。
  3. 生成可用于后续作图的 CSV 文件。

该项目最后附有合并的 Python 脚本文件,直接拖入 ImageJ 中即可运行。

文件准备

在开始操作前,需要整理好实验数据文件,并确保其目录结构规范。以下是项目文件夹的示例:

    ├─Final_Check
    │      2024-12-23 205725.mov
    │      2024-12-23 203914.mov
    │      2024-12-23 194717.mov
    │      2024-12-23 200121.mov

文件说明:

  1. 原始文件.mov 格式的视频文件,保存实验的原始数据。

环境准备

软件准备

  1. 下载并安装 ImageJ
  2. 安装 Time Series Analyzer V3 插件:可以在 ImageJ 的插件库中找到并安装。

Python 环境准备

确保实验所需的 Python 脚本可以在 ImageJ 中运行。以下是一个简单测试代码:

  1. 打开 ImageJ 的 REPL(运行环境)。
  2. 在命令行中输入以下代码,检查是否能成功加载插件:
from ij import IJ
IJ.run("Time Series Analyzer V3", "");

如果插件可以正常运行,说明环境准备就绪。

拆解步骤

导入需要的包

import os
import shutil
from ij import IJ, WindowManager
from ij.plugin.frame import RoiManager
from ij.plugin import ZProjector
import os
import Time_Series_Analyzer_V3
import time
import subprocess
import sys
from java.lang import Runtime  # 用于在 Jython 中执行系统命令
from javax.swing import JOptionPane
import csv
import re

一、创建文件结构

代码部分

函数:创建文件结构并分类整理

setup_project_structure(folder_path2):在该目录下创建一个格式化的文件夹结构,并将视频移动到里面去。

def setup_project_structure(folder_path2):
    """
    Create the directory structure for Project_Folder and organize necessary files into corresponding folders.
    """
    # Define required file types for each category
    required_files = {
        "raw_data": [".mov", ".mp4"],  # At least one .mov or .mp4 file is required
        "converted_data": [".avi"]
    }

    # Define the root project folder
    project_folder = os.path.join(folder_path2, "Project_Folder")

    # Define the directory structure
    folders = {
        "raw_data": os.path.join(project_folder, "raw_data"),                   # Original video files
        "converted_data": os.path.join(project_folder, "converted_data"),       # Converted AVI files
        "results": os.path.join(project_folder, "results"),                     # Output analysis results
        "results_roi": os.path.join(project_folder, "results", "roi"),          # ROI-related files
        "results_gcamp": os.path.join(project_folder, "results", "gcamp_analysis"),  # GCaMP analysis results
        "results_figures": os.path.join(project_folder, "results", "figures")  # Figures generated from data analysis
    }

    # Create the directory structure
    for folder_name, folder_path in folders.items():
        if not os.path.exists(folder_path):  # Check if folder exists
            os.makedirs(folder_path)        # Create folder if it doesn't exist
        print("Folder has been created or confirmed to exist: %s -> %s" % (folder_name, folder_path))

    # Function to check if required video files exist (including in subfolders)
    def has_required_files(folder_path, required_files):
        """
        Check if the folder or its subfolders contain the necessary video files.
        """
        for root, _, files in os.walk(folder_path):  # Traverse the folder and subfolders
            for file in files:
                if file.endswith(tuple(required_files["raw_data"])):  # Check for .mov or .mp4
                    return True
        return False

    # Check if required video files exist
    if not has_required_files(folder_path2, required_files):
        print("No valid .mov or .mp4 files found in the folder or its subfolders. The program has terminated.")
        exit()

    # Move files into corresponding folders
    def move_files_to_folders(folder_path, folders, current_script="workflow.py"):
        for filename in os.listdir(folder_path):
            file_path = os.path.join(folder_path, filename)
            # Skip directories
            if os.path.isdir(file_path):
                continue

            # Skip the current script file
            if filename == current_script:
                continue

            # Categorize files based on extensions
            file_ext = os.path.splitext(filename)[-1].lower()  # Get file extension
            moved = False
            for folder, extensions_or_files in required_files.items():
                # Match file extension
                if folder in ["raw_data", "converted_data"] and file_ext in extensions_or_files:
                    dest_folder = folders[folder]
                    shutil.move(file_path, os.path.join(dest_folder, filename))
                    print("Moved file: %s -> %s" % (filename, dest_folder))
                    moved = True
                    break

            # Unclassified file warning
            if not moved:
                print("Unclassified file: %s, please handle manually." % filename)

    # Start moving files
    move_files_to_folders(folder_path2, folders)

    print("\nThe directory structure has been created and the file classification is complete.")
主程序执行

folder_path2:刚储存视频文件的文件夹

# Main workflow
if __name__ == "__main__":
    # ======= 1. Select Input Folder =======
    folder_path2 = IJ.getDirectory("Choose an input folder.")
    folder_path_project = os.path.join(folder_path2, "Project_Folder\\")

    print("=== Setting up project structure ===\n")
    setup_project_structure(folder_path2)  # Create directories and organize files
    print("\n=== Project structure setup complete ===\n")
执行效果

运行后将在 folder_path2 目录下创建如下文件夹结构,并分类整理文件。

  1. converted_data: 储存处理过的avi视频
  2. raw_data: 储存原始mov文件
  3. results: 储存结果
    1. figures: 用于画图的文件
    2. gcamp_analysis: 储存中间产生的csv文件
    3. roi: 储存选择的ROI Region of Interest 区域

这样设计便于文件的规范化管理,提高后续批量处理的效率。

D:.
└─Project_Folder
    ├─converted_data
    ├─raw_data
    └─results
        ├─figures
        ├─gcamp_analysis
        └─roi

二、选择ROI

代码部分

函数:创建ImageJ宏文件(.ijm),用于选择ROI区域

在这一部分,我们将编写一个ImageJ宏文件(.ijm),该文件用于打开AVI视频文件并选择感兴趣区域(ROI)。通过这种方式,我们能够在ImageJ中自动化ROI的选择,并将其保存以供后续分析使用。

ijm_script = """
// 1.0 Get input folders .
indir = getArgument();("Choose an input folder.");
avidir = indir + "converted_data\\\\"
roidir = indir + "results\\\\roi\\\\"
list = getFileList(avidir);
listroi = getFileList(roidir);
// 2.0 Get filelist of input folder
list = getFileList(avidir);
listroi = getFileList(roidir);
for (i = 0; i < list.length; i++) {
    if (endsWith(list[i], ".avi")) {
        // Check whether run before
        norun = true;
        if (endsWith(list[i], ".avi")) {
            // Initialize control variables
            for (j = 0; j < listroi.length; j++) {
                roiVideoName = listroi[j].substring(0, listroi[j].lastIndexOf(".avi") + 4);
                if (list[i]==roiVideoName) {
                    norun = false;
                    break;
                }
            }
        }
        if (norun) {
            open(avidir + list[i]);
            setTool("rectangle"); // Select Rectangle
            roiManager("reset"); // clear ROI Manager
            
            keepRunning = true;
            roiIndex = 0; // Number initialized as 0
    
            while (keepRunning) {
                // Prompt the user to select the signal area
                waitForUser("Draw the **Signal Area** ROI first, then click **OK**.\\n \\nDraw the **Background Area** ROI next, then click **OK**.\\n   - Please draw them in pairs: Signal first, then Background.\\n \\n**The first ROI must be drawn during the activation frame** before clicking **OK**.\\n \\nTo stop:\\n   - Draw a rectangle with width or height **less than 10 pixels** starting from the top-left corner (less than (100, 100)) and click **OK**.");
    
                // Obtain the rectangular coordinates of the signal area
                getSelectionBounds(x, y, width, height);
    
                // Check if the user has drawn a too small rectangle
                if ((width < 10 || height < 10) && x <= 100 && y <= 100) {
                    print("The SIGNAL rectangle is too small. Stopping...");
                    keepRunning = false;
                    break; // Exit the while loop
                }
    
                // If the user clicks Cancel, the entire loop will be stopped
                if (width == 0 && height == 0) {
                    print("User canceled the process. Stopping...");
                    keepRunning = false;
                    break; // Exit the while loop
                }
    
                // Save the rectangular information of the signal area to the variable
                signalX = x;
                signalY = y;
                signalWidth = width;
                signalHeight = height;
    
                // Add signal area to ROI Manager
                roiManager("Add");
                
                currentIndex = roiManager("count") - 1; // Get the ROI index just added
                if (currentIndex < 0) {
                    print("Error: Failed to add SIGNAL ROI.");
                    keepRunning = false;
                    break;
                }
                roiManager("select", currentIndex);
                if (currentIndex == 0) {
                    stimuliName = Roi.getName;
                    stimuliFrame = substring(stimuliName, 0, 4);
                    stimuliFrame = parseInt(stimuliFrame);
                }
                roiManager("rename", "Signal_" + roiIndex); // Manually control the numbering, named Signal-X
    
                // **Lock the ROI of the signal area to prevent it from being dragged**
                makeRectangle(signalX, signalY, signalWidth, signalHeight); // Restore signal area
                run("Add Selection..."); // Save signal area as static selection
                run("Duplicate...", "title=Fixed_Signal_ROI"); // Create a fixed copy
    
                // Prompt the user to select the background area
                waitForUser("Drag the rectangle to the BASE (background) area, and confirm.");
                getSelectionBounds(baseX, baseY, baseWidth, baseHeight);
    
                // Check if the user has drawn a background rectangle that is too small
                if ((baseWidth < 10 || baseHeight < 10) && baseX <= 100 && baseY <= 100) {
                    print("The BASE rectangle is too small. Stopping...");
                    keepRunning = false;
                    break;
                }
    
                // If the user does not select the background area (clicks Cancel), stop the entire loop
                if (baseWidth == 0 && baseHeight == 0) {
                    print("User canceled the process. Stopping...");
                    keepRunning = false;
                    break;
                }
    
                // Add background area to ROI Manager and mark it
                roiManager("Add");
                currentIndex = roiManager("count") - 1; // Retrieve the index of the background area
                if (currentIndex < 0) {
                    print("Error: Failed to add BASE ROI.");
                    keepRunning = false;
                    break;
                }
                roiManager("select", currentIndex);
                roiManager("rename", "Base_" + roiIndex); // Manually control the numbering, named Base_X
                
                roiManager("save", roidir + list[i] + "_SF-" + stimuliFrame + ".all_ROIs.zip");
                print("Signal and Base ROIs saved successfully.");
    
                // Number increment to ensure signal and background correspondence
                roiIndex++;
            }
            selectImage(list[i]); // Select current image
            run("Close All"); //Close all images
        }
    }
}
"""

def create_ijm_file(output_dir):
    # 自动生成 .ijm 文件
    ijm_path = os.path.join(output_dir, "temp_script.ijm")
    with open(ijm_path, "w") as f:
        f.write(ijm_script)
    return ijm_path
主程序执行
# Main workflow
if __name__ == "__main__":
    folder_path2 = IJ.getDirectory("Choose an input folder.")
    folder_path_project = os.path.join(folder_path2, "Project_Folder\\")

    # ======= 2. ROI Selection Step =======
    print("=== Selecting ROI ===\n")
    output_dir = ".\\temp"  # Temporary directory
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)  # Create the directory if it doesn't exist
    ijm_file = create_ijm_file(output_dir)  # Generate the IJM file
    IJ.runMacroFile(ijm_file, folder_path_project)  # Run the IJM script
    print("\n=== ROI Selection Complete ===\n")

运行ijm后的操作步骤

1. 绘制信号区域 ROI → 点击确定
2. 绘制背景区域 ROI → 点击确定
3. 重复第 1 和第 2 步,成对绘制 ROI
4. 停止:绘制一个小矩形(宽/高 < 10)→ 点击确定
  1. 绘制信号区域 ROI

    • 首先,绘制一个矩形框,标记 信号区域
      • 第一个信号区域的 ROI 必须让视频停留在激活帧(Stimuli Frame)再单击 确定。因为GCaMP需要记录什么时候被激活,所有的数据都应当据此对齐)
    • 绘制完成后,单击 确定(OK)。
  2. 制背景区域

    • 接下来,拖动矩形框,标记 背景区域&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值