我正在处理数百个大型高分辨率 .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) 读取它时,我得到一个形状的 numpy 数组 (101, 2048, 2048)
示例文件
S000_t000002_V000_R0000_X000_Y000_C02_I1_D0_P00101.tif
是多页 TIFF。每页中的图像数据未压缩地存储在一个条带中。为了加快从这种特定类型的 TIFF 文件中读取切片数据的速度,请对帧数据进行内存映射,并将切片数据复制到预分配的数组,同时迭代文件中的页面。除非想要保留噪声特征,否则通常最好使用高阶滤波进行下采样,例如使用 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,
# )
我会避免这种微优化以获得很少的速度增益。示例文件中的图像数据只有 ~800 MB,可以轻松放入大多数计算机的 RAM 中。
我用 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
您的示例图像有 100 页,每页都是编码为单个未压缩条带的 2048 x 2048 像素 16 位灰度图像。
使用 pyvips,您需要将所有页面加载为长的垂直条带,分成页面,调整每个页面的大小,然后再次写回(我猜是另一个 TIFF)。也许:
#!/usr/bin/env python
import sys
import pyvips
# scan the image as a single huge vertical strip
image = pyvips.Image.new_from_file(sys.argv[1], n=-1)
# cut into an array of pages
page_height = image.get("page-height")
pages = [image.crop(0, y, image.width, page_height)
for y in range(0, image.height, page_height)]
# size each page down to 512x512
pages = [page.resize(0.25, kernel="linear") for page in pages]
# join back into a huge vertical strip again
image = pyvips.Image.arrayjoin(pages, across=1)
image.set("page-height", page_height / 4)
image.write_to_file(sys.argv[2])
我明白了:
$ /usr/bin/time -f %M:%e ./zac.py ~/pics/S000_t000002_V000_R0000_X000_Y000_C02_I1_D0_P00101.tif x.tif
134628:1.49
因此 130mb 的 RAM 和 1.5 秒的运行时间。这将写入另外 100 页、16 位未压缩的 TIFF,但现在每个页的大小为 512x512。
kernel="linear"
表示下采样的线性插值,因此不应有太多混叠。
正如@cgohike所说,映射文件并直接读出像素值会快很多。