
GitHub:https://github.com/cgohlke/tifffile
pip install 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("处理完成!")如果 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("处理完成")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和Python 如何读取和保存ImageJ ROI manager的ROI.zip文件
✦ Olympus OlyVIA使用笔记丨如何处理共聚焦荧光显微镜拍摄的免疫荧光染色结果
✦ ImageJ丨如何设置特定大小的ROI(正的矩形、倾斜矩形)并裁剪ROI区域
✦ matlab自定义lut,用于展示显微图片,得到colorbar