一、简介
tifffile
是一个用于读取和写入 TIFF 文件的 Python 库。它提供了灵活的接口,能够处理各种类型的 TIFF 文件,包括单页、多页、多通道等。通过 tifffile 库,你可以轻松地读取、修改和保存 TIFF 格式的图像数据。
TIFF(Tagged Image File Format, 标记图像文件格式)
:包括两种后缀.tif
和.tiff
。TIFF文件的读写方式:
- tifffile官网:基于 Python 库
- libtiff官网:基于 C语言 库
二、函数详解
2.1、tifffile.imread():读取图像
import tifffile
import tkinter
from tkinter import filedialog
import time
import os
# 加载路径
root = tkinter.Tk() # 创建根窗口
root.withdraw() # 隐藏根窗口
load_path = tkinter.filedialog.askopenfilename() # 打开文件选择对话框
print("选择的文件路径:", load_path)
##############################################################
# (1)加载图像(整个)
start_time = time.time()
image_stack = tifffile.imread(load_path)
print("长宽高=", image_stack.shape)
print(f"总运行时间: {time.time() - start_time:.2f} 秒")
##############################################################
# (2)加载图像(单帧)
"""
3GB以内,可以任意指定图像中的帧
3GB以上,若指定key=0以上,将提示报错。IndexError: index out of range
"""
start_time = time.time()
with tifffile.TiffFile(load_path) as tif:
frame = tif.asarray(key=20)
print("长宽高=", frame.shape)
print(f"总运行时间: {time.time() - start_time:.2f} 秒")
##############################################################
# (3)加载图像(多帧)
"""
3GB以内,可以任意指定图像中的多帧
3GB以上,首帧必须为0,否则报错。StopIteration
"""
start_time = time.time()
data = tifffile.imread(load_path, key=slice(0, 2)) # 读取指定范围的帧图像
print("长宽高=", frame.shape)
print(f"总运行时间: {time.time() - start_time:.2f} 秒")
##############################################################
# (4)加载图像(多核心)
start_time = time.time()
image_stack = tifffile.imread(load_path, maxworkers=os.cpu_count())
print("长宽高=", image_stack.shape)
print(f"总运行时间: {time.time() - start_time:.2f} 秒")
##############################################################
# (5)加载图像(多文件)
start_time = time.time()
image_stack = tifffile.imread(load_path, _multifile=True)
print("长宽高=", image_stack.shape)
print(f"总运行时间: {time.time() - start_time:.2f} 秒")
##############################################################
# (6)加载图像(多线程)
start_time = time.time()
with tifffile.TiffFile(load_path) as tif:
frames = tif.asarray(maxworkers=os.cpu_count())
print("长宽高=", frames.shape)
print(f"总运行时间: {time.time() - start_time:.2f} 秒")
"""#######################################################################
# import tifffile
# 函数作用:用于读取 TIFF 文件的图像
# 函数说明:tifffile.imread(file, key=None, maxworker=None, _multifile=None, ...)
# 输入参数:
# (1)file: TIFF文件的路径或文件对象。
# (2)key: 用于选择要读取的图像数据的关键索引。
# data = tifffile.imread(file, key=None) # 读取整个文件(默认None)
# data = tifffile.imread(file, key=0) # 读取单帧(3GB以上,若指定key=0以上,将提示报错。IndexError: index out of range)
# data = tifffile.imread(file, key=slice(0, 2)) # 读取多帧(3GB以上,首帧必须为0,否则报错。StopIteration)
# (3)maxworker: 用于多线程加载图像
# data = tifffile.imread(load_path, maxworkers=4)
# (4)_multifile: 用于多文件加载图像
# data = tifffile.imread(load_path, _multifile=True)
#######################################################################"""
2.2、tifffile.imwrite():保存图像
import numpy as np
import tifffile
if __name__ == "__main__":
np.random.seed(42)
image_stack = np.random.randint(0, 256, size=(1024, 1024, 1024), dtype=np.uint8) # 生成一个随机的图像数据
load_path = "test_image.tif" # 指定路径
tifffile.imwrite(load_path, image_stack) # 保存图像
"""#######################################################################
# import tifffile
# 函数作用:用于将图像写入 TIFF 文件
# 函数说明:tifffile.imwrite(file, data, ...)
# 输入参数:
# (1)file: TIFF 文件的路径
# (2)data: 图像数据的数组。通常是一个3D或4D NumPy 数组。
# (3)shape: 图像数据的形状。默认根据输入数组的形状进行推断。
# (3)dtype: 图像的数据类型。默认使用输入数组的数据类型。
# (4)imagej: 是否使用 ImageJ 格式。默认为 False。
# (5)bigtiff: 是否使用 BigTIFF 格式。默认为 False。
# (6)compress: 压缩级别或压缩类型。默认为 0(无压缩)。例如,可以使用 'zstd'、'lzw'、'jpeg' 等。
#######################################################################"""
2.3、TIFF.pages[0].tags:获取 TIFF 图像页的元数据标签
TiffFile对象
表示整个TIFF文件
TiffPage对象
表示TIFF文件中的一个图像页
(或一个帧图像)。
- 每个图像页包含了一系列的元数据标签(TiffTag 对象),这些标签描述了图像的各种属性和信息(如:像素类型、图像大小、色彩信息等)。
import tifffile
TIFF = tifffile.TiffFile(r'brain.tif') # 打开 TIFF 文件
tiff_pages_tags = TIFF.pages[0].tags # 获取第一页的元数据标签
###########################################################
# (1)打印原数据的所有标签
for tag in tiff_pages_tags.values(): # 遍历元数据标签
print("Tag Name:", tag.name) # 标签的名称
print("Tag Value:", tag.value) # 标签的值
print("Tag Code:", tag.code) # 标签的唯一标识符
print("-"*50)
###########################################################
# (2)提取 TIFF 图像页的元数据标签:min + max
if 'ImageDescription' in tiff_pages_tags: # 检查是否存在 ImageDescription 标签
image_description_tag = tiff_pages_tags['ImageDescription']
image_description = image_description_tag.value
# 使用正则表达式匹配 min 和 max 属性的值,直接匹配 µ 字符
match = re.search(r'min\s*=\s*([0-9.]+)\s*max\s*=\s*([0-9.]+)', image_description)
if match:
min_value = round(float(match.group(1)))
max_value = round(float(match.group(2)))
print(f"min={min_value}, max={max_value}")
else:
print(f"No match found in image_description: {image_description}")
"""
Tag Name: NewSubfileType Tag Value: FILETYPE.UNDEFINED Tag Code: 254
Tag Name: ImageWidth Tag Value: 7702 Tag Code: 256
Tag Name: ImageLength Tag Value: 2400 Tag Code: 257
Tag Name: BitsPerSample Tag Value: 16 Tag Code: 258
Tag Name: Compression Tag Value: COMPRESSION.NONE Tag Code: 259
Tag Name: PhotometricInterpretation Tag Value: PHOTOMETRIC.MINISBLACK Tag Code: 262
Tag Name: ImageDescription Tag Value: ImageJ=1.54f
images=11485
slices=11485
unit=\u00B5m
loop=false
min=0.0
max=2809.0
xorigin=5735.0
yorigin=3851.0
zorigin=1270.0 Tag Code: 270
Tag Name: StripOffsets Tag Value: (326,) Tag Code: 273
Tag Name: SamplesPerPixel Tag Value: 1 Tag Code: 277
Tag Name: RowsPerStrip Tag Value: 2400 Tag Code: 278
Tag Name: StripByteCounts Tag Value: (36969600,) Tag Code: 279
Tag Name: XResolution Tag Value: (1000000, 1000000) Tag Code: 282
Tag Name: YResolution Tag Value: (1000000, 1000000) Tag Code: 283
Tag Name: ResolutionUnit Tag Value: RESUNIT.NONE Tag Code: 296
-------------------
min=0, max=2159
"""
2.4、tifffile.memmap():访问大型 TIFF 文件的部分数据,而无需一次性读取整个文件。
2.4.1、tifffile.imread()与tifffile.memmap()速度对比
import numpy as np
import tifffile
import time
# start_time = time.time() # 记录开始时间
# memmap_data = np.random.randint(0, 65535, size=(1024*50, 1024, 1024), dtype=np.uint16)
# print(f"图像内存: {memmap_data.nbytes / (1024 ** 3)} GB")
# print(f"生成图像: {time.time() - start_time:.4f} 秒")
#
# start_time = time.time() # 记录开始时间
# tifffile.imwrite('test_image.tiff', memmap_data)
# print(f"保存图像: {time.time() - start_time:.4f} 秒")
##############################################################
start_time = time.time() # 记录开始时间
imread_data = tifffile.imread(r'test_image.tiff')
print(f"imread加载图像: {time.time() - start_time:.4f} 秒")
import napari
viewer = napari.Viewer() # 创建napari视图
start_time = time.time() # 记录开始时间
viewer.add_image(imread_data, name="memmap_data") # 添加图像
print(f"imread显示图像: {time.time() - start_time:.4f} 秒")
napari.run()
###############################################################
start_time = time.time() # 记录开始时间
memmap_data = tifffile.memmap(r'test_image.tiff')
print(f"memmap加载图像: {time.time() - start_time:.4f} 秒")
import napari
viewer = napari.Viewer() # 创建napari视图
start_time = time.time() # 记录开始时间
viewer.add_image(memmap_data, name="memmap_data") # 添加图像
print(f"memmap显示图像: {time.time() - start_time:.4f} 秒")
napari.run()
###############################################################
"""
图像内存: 100 GB
生成图像: 80 秒
保存图像: 400 秒
imread加载图像: 70.1708 秒
imread显示图像: 0.2282 秒
memmap加载图像: 0.0638 秒
memmap显示图像: 0.1551 秒
"""
2.4.2、tifffile.memmap()参数详解
import numpy as np
import tifffile
"""(1)创建了一个包含三个序列、每个序列包含四个级别的金字塔图像。"""
num_series = 3
num_levels = 4
image_shape = (512, 512)
pyramid_images = []
for _ in range(num_series):
series_images = []
for level in range(num_levels):
# 生成随机图像数据
random_image = np.random.randint(0, 255, size=image_shape, dtype=np.uint8)
series_images.append(random_image)
# 对图像大小进行下采样
image_shape = tuple(dim // 2 for dim in image_shape)
pyramid_images.append(series_images)
##############################################################################################
"""(2)将金字塔图像保存为TIFF文件"""
filename = 'pyramid_image.tif'
with tifffile.TiffWriter(filename) as tif:
for series_idx, series_images in enumerate(pyramid_images):
for level_idx, image_data in enumerate(series_images):
tif.save(image_data, description=f'Series {series_idx}, Level {level_idx}')
##############################################################################################
"""(3)读取 TIFF 文件"""
with tifffile.TiffFile(filename) as tif:
# (3.1)获取 TIFF 文件的页面数、序列数和金字塔级别数
num_pages = len(tif.pages)
num_series = len(tif.series)
num_levels = len(tif.series[0].levels)
# 获取每个页面、序列和级别的形状
page_shapes = [page.shape for page in tif.pages]
series_shapes = [(series.shape, series.levels[0].shape) for series in tif.series]
# 打印信息
print(f"Number of pages: {num_pages}")
print(f"Number of series: {num_series}")
print(f"Number of levels in first series: {num_levels}")
print("Shapes of pages:", page_shapes)
print("Shapes of series:", series_shapes)
# (3.2)读取特定页面、序列和级别的数据
page_data = tif.pages[0].asarray()
series_data = tif.series[0].asarray()
level_data = tif.series[0].levels[0].asarray()
# 打印数据形状
print("Shape of page data:", page_data.shape)
print("Shape of series data:", series_data.shape)
print("Shape of level data:", level_data.shape)
"""#############################################################################################
# 函数功能:用于加载 TIFF 文件并将其映射到内存中 ———— 支持访问大型 TIFF 文件的部分数据而无需一次性读取整个文件,可以显著减少内存占用和初始加载时间。
# 函数说明:memmap = tifffile.memmap(filename, shape=None, dtype=None, page=None, series=0, level=0, mode='r+', **kwargs)
# 参数说明:
# filename: 字符串,指定内存映射的 TIFF 文件的路径。
# shape: 可选参数,指定图像的形状。若给定文件,则自动获取。
# dtype: 可选参数,指定图像的数据类型。若给定文件,则自动获取。
# page: 可选参数,指定 TIFF 文件中的页码。None表示选择整个系列的第一个页。
# series: 可选参数,指定 TIFF 文件中的系列。TIFF 文件可以包含多个系列,每个系列代表一个独立的图像或图像集。
# level: 可选参数,指定 TIFF 文件中的金字塔层级。金字塔层级表示同一图像的不同分辨率层级。
# mode: 可选参数,表示内存映射文件的打开模式。默认为 'r+',表示读写模式。其他选项包括 'r'(只读模式)和 'w+'(写模式)。
# kwargs: 可选参数,表示传递给 imwrite 或 TiffFile 的附加参数。
# 返回值:
# memmap:返回一个 numpy.memmap 对象,用于高效地读取和写入 TIFF 图像数据。
#
# 使用建议:加载部分数据用于查看,加载全部数据用于计算。
#############################################################################################"""
三、项目实战
(3D-GRAY) to (3D-RGB):使用颜色映射的方式,将灰度值映射到彩色空间中的特定颜色。
3.1、三维数组:10 x 12 x 14
2.1.1、channel重复:将【灰度图】分别赋值给【R/G/B图】,显示彩色图。
import numpy as np
import tifffile
# (1)加载16位灰度图像,并转换数据类型
image_data = tifffile.imread('gray_image_raw.tif').astype(np.uint32) # 原始-灰度图像(16-bit): 50, 444, 1112
# (2)将灰度图像转换为彩色图像
color_image = np.zeros((*image_data.shape, 3), dtype=np.uint8)
color_image[..., 0] = (image_data >> 8) & 0xFF # 红色通道
color_image[..., 1] = (image_data >> 4) & 0xFF # 绿色通道
color_image[..., 2] = image_data & 0xFF # 蓝色通道
# (3)将彩色图像保存为新的tif文件
tifffile.imwrite('color_image.tif', color_image)
"""##################################################################
# (image_data >> 8) & 0xFF
#
# 操作: 通过右移8位(相当于除以256)来实现的,然后使用位运算AND操作符&和掩码0xFF来保留最低8位。
# 作用: 将16位图像的灰度范围缩放到0-255,适合于8位彩色图像的显示或处理。
##################################################################"""
3.1.2、channel叠加:将【灰度图】只赋值给【R图】,然后将【灰度图】与【G图】进行叠加,显示【红色底层+绿色顶层】。
import numpy as np
import tifffile
"""image_data与image_data_gray的图像尺度必须一致"""
# (1)读取图像
gray_image_raw = tifffile.imread('gray_image_raw.tif') # 原始-灰度图像(16-bit):[50, 444, 1112]
gray_image_circle = tifffile.imread('gray_image_circle.tif') # 空心圆-灰度图像(16-bit):[50, 444, 1112]
# (2)新建彩色图像
color_image = np.zeros((*gray_image_raw.shape, 3), gray_image_raw.dtype) # 空心圆-彩色图像(16-bit):[50, 444, 1112, 3]
"""*gray_image_raw.shape表示将gray_image_raw.shape中的元素展开,并将它们作为参数传递给函数或操作。"""
color_image[..., 0] = gray_image_raw
# color_image[..., 1] = gray_image_raw
# color_image[..., 2] = gray_image_raw
# (3)将gray_image_circle中65535的值(空心圆=65535)赋给color_image
# color_image[gray_image_circle == 65535, 0] = 65535 # 65535表示灰度图像中的最大像素值,0表示红色通道(红色显示)。
color_image[gray_image_circle == 65535, 1] = 65535 # 65535表示灰度图像中的最大像素值,1表示绿色通道(绿色显示)。
# color_image[gray_image_circle == 65535, 2] = 65535 # 65535表示灰度图像中的最大像素值,2表示蓝色通道(蓝色显示)。
# (4)保存图像
tifffile.imwrite('color_image.tif', color_image)
print(color_image.shape[0], color_image.shape[1], color_image.shape[2], color_image.shape[3])
# [50, 444, 1112, 3]
3.1.3、channel重复 + channel叠加:将【灰度图】分别赋值给【R/G/B图】,然后将【灰度图】与【G图】进行叠加,显示【灰色底层+绿色顶层】。
import numpy as np
import tifffile
"""image_data与image_data_gray的图像尺度必须一致"""
# (1)读取图像
gray_image_raw = tifffile.imread('gray_image_raw.tif') # 原始-灰度图像(16-bit):[50, 444, 1112]
gray_image_circle = tifffile.imread('gray_image_circle.tif') # 空心圆-灰度图像(16-bit):[50, 444, 1112]
# (2)新建彩色图像
color_image = np.zeros((*gray_image_raw.shape, 3), gray_image_raw.dtype) # 空心圆-彩色图像(16-bit):[50, 444, 1112, 3]
"""*gray_image_raw.shape表示将gray_image_raw.shape中的元素展开,并将它们作为参数传递给函数或操作。"""
color_image[..., 0] = gray_image_raw
color_image[..., 1] = gray_image_raw
color_image[..., 2] = gray_image_raw
# (3)将gray_image_circle中65535的值(空心圆=65535)赋给color_image
# color_image[gray_image_circle == 65535, 0] = 65535 # 65535表示灰度图像中的最大像素值,0表示红色通道(红色显示)。
color_image[gray_image_circle == 65535, 1] = 65535 # 65535表示灰度图像中的最大像素值,1表示绿色通道(绿色显示)。
# color_image[gray_image_circle == 65535, 2] = 65535 # 65535表示灰度图像中的最大像素值,2表示蓝色通道(蓝色显示)。
# (4)保存图像
tifffile.imwrite('color_image.tif', color_image)
print(color_image.shape[0], color_image.shape[1], color_image.shape[2], color_image.shape[3])
# [50, 444, 1112, 3]
3.2、二维数组:10 x 12
channel叠加:10x12x1+10x12x3=10x12x3
import cv2
import numpy as np
# (1)读取图像
rgb_image = cv2.imread('image.jpg', cv2.IMREAD_COLOR)
gray_image = cv2.cvtColor(rgb_image, cv2.COLOR_BGR2GRAY)
# (2)创建一个空白的多通道图像
height, width, _ = rgb_image.shape
merged_image = np.zeros((height, width, 3), dtype=np.uint8)
# (3)将灰度图像的数据复制到第四个通道
merged_image[..., 0] = gray_image
merged_image[..., 1] = gray_image
merged_image[..., 2] = gray_image
merged_image[..., 2] = rgb_image[..., 2]
# (4)显示合并后的图像
cv2.imshow("Merged Image", merged_image)
cv2.imshow("gray_image", gray_image)
cv2.waitKey(0)
cv2.destroyAllWindows()