tifffile图像处理

一、简介

tifffile是一个用于读取和写入 TIFF 文件的 Python 库。它提供了灵活的接口,能够处理各种类型的 TIFF 文件,包括单页、多页、多通道等。通过 tifffile 库,你可以轻松地读取、修改和保存 TIFF 格式的图像数据。

TIFF(Tagged Image File Format, 标记图像文件格式):包括两种后缀 .tif.tiff

TIFF文件的读写方式:

二、函数详解

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()

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖墩会武术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值