当前位置:首页>python>如何用python读写钙成像的tiff stack:使用tifffile

如何用python读写钙成像的tiff stack:使用tifffile

  • 2026-09-12 20:20:18
如何用python读写钙成像的tiff stack:使用tifffile

安装 tifffile

GitHub:https://github.com/cgohlke/tifffile

pip install tifffile

tifffile 的数据维度说明

用 tifffile 读取钙成像的 tiff stack 时,最常见的数据格式/维度是:

‍✦ 单帧图像:(Height, Width)

‍✦ 多帧 movie:(Frames, Height, Width)

‍  ‍  ○ 第 0 维:时间 / 帧数 Frames

‍  ‍  ○ 第 1 维:图像高度 Height

‍  ‍  ○ 第 2 维:图像宽度 Width

最简单的读写方式

如果tiff stack文件比较小,可以直接用tifffile.imread把所有帧都加载进内存

import tifffile# 读取为 numpy 数组img_stack = tifffile.imread(input_path)# 查看数据形状和类型print(f"数据形状: {img_stack.shape}") # 通常是 (Frames, Height, Width)print(f"数据类型: {img_stack.dtype}") # 钙成像通常是 uint16

写入:直接把内存的数据直接一次性写入

# 写入 numpy 数组为 TIFFtifffile.imwrite("out.tif", img)

分批读取(适用于大文件)

如果文件比较大,要对tiff进行读取,比如配准、提取钙信号数据,无法直接加载进内存,可以每次只读取部分帧

方式一:用tifffile.imread(input_path, key=range(i, end_idx))直接读取帧范围

import tifffilebatch_size = 300# 先获取总帧数with tifffile.TiffFile(input_path) as tif:    total_frames = len(tif.pages)# 分批读取for i inrange(0, total_frames, batch_size):    end_idx = min(i + batch_size, total_frames)# 每次只读取指定范围的帧    imgs = tifffile.imread(input_path, key=range(i, end_idx))print(f"当前读取帧范围: {i+1} ~ {end_idx}, 数据形状: {imgs.shape}")

方式二:用tif.asarray(key=range(i, end_idx))

import tifffileimport numpy as npbatch_size = 300# 分批读取(节省内存)with tifffile.TiffFile(input_path) as tif:    total_frames = len(tif.pages)# 获取基本信息print(f"总帧数: {total_frames}")print(f"图像尺寸: {tif.pages[0].shape}")print(f"数据类型: {tif.pages[0].dtype}")# 分批遍历for i inrange(0, total_frames, batch_size):        end_idx = min(i + batch_size, total_frames)# 读取当前批次并转换为 numpy 数组        frames = tif.asarray(key=range(i, end_idx))print(f"当前批次形状: {frames.shape}")

✏️ Note

本质上tifffile.imread(input_path, key=range(i, end_idx))也是调用tif.asarray(key=range(i, end_idx))实现读取指定帧范围的

分批写入

使用tif.write(frame, contiguous=True),可以实现分批读取处理后,再分批写入。

# 逐帧写入(适合超大数据)with tifffile.TiffWriter(output_path, imagej=True) as tif:for i inrange(1000):        frame = np.random.randint(0, 65535, (512, 512), dtype=np.uint16)        tif.write(frame, contiguous=True)

✏️ Note

contiguous=True参数是必须的,

‍1. 可以保证写入的帧是同一series,具体见[tifffile中pages和series的区别](siyuan://blocks/20260403161005-jnoinh5)

‍2. 连续写入的文件可以直接使用memmap来实现内存映射

这种方式非常适合处理超大文件,比如在不撑爆内存的情况下对数 GB 的钙成像数据进行滤波、运动校正等操作。下面是示例代码:

import tifffileimport numpy as npfrom pathlib import Pathdefprocess(frames):"""    示例处理函数    可替换为你的实际批处理逻辑    """return framesinput_path = r"raw_calcium_stack.tif"output_path = r"processed_calcium_stack.tif"batch_size = 300# 动态判断是否使用 BigTIFFfile_size = Path(input_path).stat().st_sizeuse_bigtiff = file_size > 4 * 1024**3# > 4GBwith tifffile.TiffFile(input_path) as tif_in:    total_frames = len(tif_in.pages)# 读取第一页的分辨率元数据    tags = tif_in.pages[0].tags    xres = tags.get("XResolution").value if"XResolution"in tags elseNone    yres = tags.get("YResolution").value if"YResolution"in tags elseNone    resunit = tags.get("ResolutionUnit").value if"ResolutionUnit"in tags elseNoneprint(f"Original XResolution: {xres}, YResolution: {yres}, ResolutionUnit: {resunit}"    )print(f"File size: {file_size} bytes, use_bigtiff: {use_bigtiff}")# 预构建写入参数,避免重复判断    write_kwargs = dict(        contiguous=True,        resolution=(xres, yres) if xres and yres elseNone,        resolutionunit=resunit,        photometric="minisblack",    )with tifffile.TiffWriter(output_path, bigtiff=use_bigtiff) as tif_out:for i inrange(0, total_frames, batch_size):            end_idx = min(i + batch_size, total_frames)# 分批读取            frames_batch = tif_in.asarray(key=range(i, end_idx))# 分批处理            processed_batch = process(frames_batch)# 逐帧写入for frame_idx inrange(processed_batch.shape[0]):                tif_out.write(                    processed_batch[frame_idx],  # 单帧 2D array                    **write_kwargs,                )print(f"进度:已处理并写入 {end_idx}/{total_frames} 帧")print("处理完成!")

内存映射memmap

如果 tiff stack很大,除了分批读取和写入,还可以用 memmap来实现对超大文件的部分读取和写入

memmap(memory map,内存映射)是一种不把整个文件一次性读入内存的访问方式。

它会把磁盘文件“映射”为一个类似 NumPy 数组的对象,可以使用切片方式进行读写,只有真正访问到某一部分数据时,操作系统才会把对应内容读入内存

所以它特别适合

‍✦ 文件很大时,需要频繁访问和修改文件的局部

✏️ Note

memmap 并不是对所有 TIFF 都有效,它要求 TIFF 中图像数据是比较规则、连续、可直接映射的。

通常要注意以下几点:

‍1. 最好是连续写入

‍  ‍  ○ 使用 contiguous=True

‍  ‍  ○ 这样所有帧更可能存放为一个连续数据块

‍2. 不能使用压缩

memmap读取文件

import tifffileimport numpy as npimg_stack = tifffile.memmap("processed_calcium_stack.tif")# 此时几乎不占用任何物理内存!print(f"虚拟加载完毕,形状: {img_stack.shape}, 类型: {img_stack.dtype}")# 获取第一帧(此时这 1 帧的像素数据才会被真正读入内存)frame_0 = img_stack[0]# 计算前 100 帧的均值mean_image = np.mean(img_stack[:100], axis=0)

使用memmap也可以写入文件,可以像写入numpy 数组自由写入/修改文件

创建新文件并写入:在创建新的用于写入的 memmap 文件时,必须预先指定图像的 shape 和 dtype

import tifffileimport numpy as np# 创建一个新的 memmap TIFF 文件(shape 和 dtype 需要提前指定)img_out = tifffile.memmap("output_stack.tif",    shape=(500, 512, 512),   # (帧数, 高, 宽)    dtype=np.uint16)# 像操作 NumPy 数组一样写入数据img_out[0] = np.zeros((512, 512), dtype=np.uint16)   # 写入第 0 帧img_out[1:10] = np.ones((9, 512, 512), dtype=np.uint16) * 1000# 写入第 1-9 帧# 写入完成后刷新到磁盘img_out.flush()

修改已有文件的局部内容:

img_stack = tifffile.memmap("processed_calcium_stack.tif")# 直接对某帧进行修改img_stack[5] = img_stack[5] * 2# 修改完成后刷新到磁盘img_stack.flush()

前面的分批写入代码可以修改为

import tifffileimport numpy as npfrom pathlib import Pathdefprocess(frames):"""    示例处理函数    可替换为你的实际批处理逻辑    """return framesinput_path = r"raw_calcium_stack.tif"output_path = r"processed_calcium_stack_memmap.tif"batch_size = 300# 以内存映射方式打开输入文件(只读,不加载进内存)img_in = tifffile.memmap(input_path)total_frames, height, width = img_in.shapeprint(f"总帧数: {total_frames}, 尺寸: {height}x{width}, 数据类型: {img_in.dtype}")# 创建输出文件的内存映射(预分配磁盘空间)img_out = tifffile.memmap(    output_path, dtype=img_in.dtype, shape=(total_frames, height, width))# 分批处理并写入for i inrange(0, total_frames, batch_size):    end_idx = min(i + batch_size, total_frames)# 从映射中读取当前批次(此时才真正从磁盘读取)    frames_batch = img_in[i:end_idx]# 处理    processed_batch = process(frames_batch)# 直接赋值写入输出映射(直接写入磁盘,不占用额外内存)    img_out[i:end_idx] = processed_batchprint(f"进度:已处理并写入 {end_idx}/{total_frames} 帧")# 刷新确保所有数据写入磁盘img_out.flush()print("处理完成")

注意:tifffile中pages和series的区别

pages和series区别

‍✦ pages:存储的一张张图片,可以获取到所有series存储的所有图像

‍✦ series:series代表逻辑上的一组图像数据集,普通tiff只会有一个series,OME-TIFF格式(主要用于荧光显微镜和全片扫描图像的存储)可以存储多个series。对于钙成像movie而言,一般用不到series,使用多个series可能会导致一些程序无法读出movie只能读出单帧图像。

简单理解:

‍✦ 单张 tiff:通常只有 1 个 page,1 个 series

‍✦ 普通 tiff stack:通常有很多个 page,但仍然只有 1 个 series

代码:

读取tif文件的pages和series数目

import tifffilewith tifffile.TiffFile('input.tif') as tif:    page = tif.pages[0]print("Pages Number:",len(tif.pages)) # 50print("Pages Shape:",page.shape) # (100, 100)    series = tif.series[0]print("Series Number",len(tif.series)) # 只有1个序列print("Series Shape",series.shape) # (50, 100, 100)

⚠️ Warning

注意:每次调用tif.write,都会创建新的series,比如用下面代码,每次存储单张图片就创建新的series,可能会导致一些程序读取只能读取出一帧,读不出多帧。

import numpy as npimport tifffilewith tifffile.TiffWriter('multi_frame_noncontiguous.tif') as tif:for i inrange(100):        frame = np.random.randint(0, 256, (100, 100), dtype=np.uint8)        tif.write(frame)with tifffile.TiffFile('multi_frame_noncontiguous.tif') as tif:    page = tif.pages[0]print("Pages Number:",len(tif.pages)) # 50print("Pages Shape:",page.shape) # (100, 100)    series = tif.series[0]print("Series Number",len(tif.series)) # 50个序列print("Series Shape",series.shape) # (100, 100),一个series就存储一个图片

如果不想创建序列,需要给tif.write添加contiguous=True参数

import numpy as npimport tifffilewith tifffile.TiffWriter('multi_frame_contiguous.tif') as tif:for i inrange(50):        frame = np.random.randint(0, 256, (100, 100), dtype=np.uint8)        tif.write(frame, contiguous=True)with tifffile.TiffFile('multi_frame_contiguous.tif') as tif:    page = tif.pages[0]print("Pages Number:",len(tif.pages)) # 50print("Pages Shape:",page.shape) # (100, 100)    series = tif.series[0]print("Series Number",len(tif.series)) # 只有1个序列print("Series Shape",series.shape) # (50, 100, 100)

相关笔记

‍✦ Matlab 如何读取、查看、保存Tiff

‍✦ Matlab和Python 如何读取和保存ImageJ ROI manager的ROI.zip文件

‍✦ Olympus OlyVIA使用笔记丨如何处理共聚焦荧光显微镜拍摄的免疫荧光染色结果

‍✦ ImageJ Overlay:如何隐藏、显示和导出

‍✦ ImageJ 丨如何画固定长度和角度的线

‍✦ ImageJ 丨设置固定的contrast(对比度)

‍✦ ImageJ丨如何设置特定大小的ROI(正的矩形、倾斜矩形)并裁剪ROI区域

‍✦ ImageJ 丨如何测量显微镜的成像视野

‍✦ matlab自定义lut,用于展示显微图片,得到colorbar

‍

最新文章

随机文章