阅读时对大型 .tif 图像进行缩减采样

Downsample large .tif images while reading

我正在处理数百张大型高分辨率 .tif 图像,这些图像需要大量内存才能读取 Python。幸运的是,我经常可以通过在加载这些图像后对其进行下采样来处理这些图像的低分辨率版本。我想知道是否有一种方法可以只将部分图像而不是整个图像读入内存以提高读取速度。

下面的代码显示了我想要的示例,但是,这仍然会在返回下采样数组之前将整个图像读入内存。是否可以只将每第n个像素值读入内存以提高读取速度?

from tifffile import imread

def standardOpen(f):
    im = imread(f)
    return(im)

def scaledOpen(f):
    im = imread(f)[::,::4,::4]
    return(im)

f_path = '/file_name.tif'

im = standardOpen(f_path)
print(im.shape)
>>(88, 2048, 2048)

im_scaled = scaledOpen(f_path)
print(im_scaled.shape)
>>(88, 512, 512)

编辑:我已将样本图片上传到保管箱:https://www.dropbox.com/s/xkm0bzudcv2sw5d/S000_t000002_V000_R0000_X000_Y000_C02_I1_D0_P00101.tif?dl=0

这张图片是 101 个 2048x2048 像素的切片。当我使用 tifffile.imread(image_path) 读取它时,我得到一个形状为 (101, 2048, 2048)

的 numpy 数组

我用 pyvips 做了一些实验来模拟您的工作流程。

首先,我创建了一个 6.7GB 的 TIF,尺寸为 60,000 x 40,000 像素。然后我用 pyvips 加载它并缩小它以适应 1,000 x 1,000 的矩形并保存结果:

#!/usr/bin/env python3

import pyvips

# Resize to no more than 1000x1000 pixels
out = pyvips.Image.thumbnail('big.tif', 1000)

# Save with LZW compression
out.tiffsave('result.tif', tile=True, compression='lzw')

这花费了 3 秒并使用了 440MB 的 RAM - 包括 Python 解释器。然后你可以把它变成一个像这样的常规 Numpy 数组——它实际上只是一行代码——只有一些映射:

# map vips formats to np dtypes
format_to_dtype = {
   'uchar': np.uint8,
   'char': np.int8,
   'ushort': np.uint16,
   'short': np.int16,
   'uint': np.uint32,
   'int': np.int32,
   'float': np.float32,
   'double': np.float64,
   'complex': np.complex64,
   'dpcomplex': np.complex128,
}

# vips image to numpy array
def vips2numpy(vi):
    return np.ndarray(buffer=vi.write_to_memory(),
                  dtype=format_to_dtype[vi.format],
                  shape=[vi.height, vi.width, vi.bands])

# Do actual conversion from vips image to Numpy array
na = vips2numpy(out)

您可以在终端中执行相同的操作,顺便说一下 vipsthumbnail

vipsthumbnail big.tif result.tif --size=1000 --vips-leak
memory: high-water mark 372.78 MB

示例文件 S000_t000002_V000_R0000_X000_Y000_C02_I1_D0_P00101.tif 是 multi-page TIFF。每页中的图像数据未压缩地存储在一个条带中。为了加快从这种特定类型的 TIFF 文件中读取切片数据的速度,memory-map 帧数据并将切片数据复制到 pre-allocated 数组,同时遍历文件中的页面。除非想要保留噪声特性,否则通常最好使用更高阶的滤波进行下采样,例如使用 OpenCV 进行插值:

import numpy
import tifffile
import cv2  # OpenCV for fast interpolation

filename = 'S000_t000002_V000_R0000_X000_Y000_C02_I1_D0_P00101.tif'

with tifffile.Timer():
    stack = tifffile.imread(filename)[:, ::4, ::4].copy()

with tifffile.Timer():
    with tifffile.TiffFile(filename) as tif:
        page = tif.pages[0]
        shape = len(tif.pages), page.imagelength // 4, page.imagewidth // 4
        stack = numpy.empty(shape, page.dtype)
        for i, page in enumerate(tif.pages):
            stack[i] = page.asarray(out='memmap')[::4, ::4]
            # # better use interpolation instead:
            # stack[i] = cv2.resize(
            #     page.asarray(),
            #     dsize=(shape[2], shape[1]),
            #     interpolation=cv2.INTER_LINEAR,
            # )

我会避免这种 micro-optimization 因为速度增益很小。示例文件中的图像数据只有 ~800 MB,可以轻松放入大多数计算机的 RAM 中。