Skip to main content

读写 TIFF 文件

项目描述

Tifffile 是一个 Python 库

  1. 将 NumPy 数组存储在 TIFF(标记图像文件格式)文件中,以及

  2. 从生物成像中使用的类似 TIFF 的文件中读取图像和元数据。

图像和元数据可以从 TIFF、BigTIFF、OME-TIFF、STK、LSM、SGI、NIHImage、ImageJ、MicroManager、FluoView、ScanImage、SEQ、GEL、SVS、SCN、SIS、BIF、ZIF(可缩放图像文件格式)读取、QPTIFF (QPI)、NDPI 和 GeoTIFF 文件。

图像数据可以从条带、平铺、页面 (IFD)、SubIFD、高阶序列和金字塔级别读取为 NumPy 数组或 Zarr 数组/组。

图像数据可以多页、体积、金字塔形、内存映射、平铺、预测或压缩形式写入 TIFF、BigTIFF、OME-TIFF 和 ImageJ 超堆栈兼容文件。

Tifffile 还可用于检查 TIFF 结构、从多维文件序列读取图像数据、为 TIFF 文件和图像文件序列编写 fsspec ReferenceFileSystem、修补 TIFF 标签值以及解析许多专有元数据格式。

作者

克里斯托夫·戈尔克

执照

BSD 3 条款

版本

2022.8.12

DOI :

10.5281/zenodo.6795860

安装

从 Python 包索引安装 tifffile 包和推荐的依赖项:

python -m pip install -U tifffile imagecodecs matplotlib lxml zarr fsspec

Tifffile 也可用于其他软件包存储库,例如 Anaconda、Debian 和 MSYS2。

要求

此版本已通过以下要求和依赖项进行测试(其他版本可能有效):

修订

2022.8.12

  • 通过 4918 次测试。

  • 修复使用 hyperstack 参数编写 ImageJ 格式。

  • 修复禁用元数据的写入描述。

  • 添加选项以禁用在 TiffWriter 中写入形状元数据。

2022.8.8

  • 使用 imread out 参数修复回归 (#147)。

  • 修复 imshow 显示参数。

  • 支持 fsspec OpenFile。

2022.8.3

  • 修复回归写入默认分辨率单元(#145)。

  • 添加解析常用日期时间格式的 strptime 函数。

2022.7.31

  • 修复读取损坏的 WebP 压缩段缺少 alpha 通道 (#122)。

  • 修复回归读取压缩的 ImageJ 文件。

2022.7.28

  • 将 FileSequence.labels 属性重命名为 dims(中断)。

  • 将 tifffile_geodb 模块重命名为 geodb(中断)。

  • 将 TiffFile._astuple 方法重命名为 astuple(中断)。

  • 将 noplots 命令行参数重命名为 maxplots(中断)。

  • 修复读取具有非 TZC 顺序的 ImageJ 超堆栈。

  • 修复由 Bio-Formats 编码的 JPEG 片段的色彩空间。

  • 修复 HELIOS FIB-SEM 的 fei_metadata(#141,需要测试)。

  • 将 xarray 样式属性添加到 TiffPage (WIP)。

  • 添加选项以为 TiffFile 指定 OME-XML。

  • 在 ZarrTiffStore 中添加控制多尺度的选项。

  • 支持写入未压缩的 ZarrTiffStore。

  • 支持平铺写入空图像。

  • 支持覆盖 NDPI 中的一些标签值 (#137)。

  • 支持 Jetraw 压缩(实验性)。

  • 标准化分辨率参数和属性。

  • 弃用写入时的第三个分辨率参数(使用 resolutionunit)。

  • 在写入时弃用元组类型压缩参数(使用 compressionargs)。

  • 弃用 TIFF 命名空间中的枚举(使用模块中的枚举)。

  • 提高默认线程数以写入压缩段 (#139)。

  • 将 metaseries 时间值解析为日期时间对象 (#143)。

  • 将内部读写缓冲区增加到 256 MB。

  • 将一些警告转换为调试消息。

  • 宣布所有类最终。

  • 添加脚本以通过 Sphinx 生成文档。

  • 使用 Sphinx 指令将文档字符串转换为 Google 样式。

2022.5.4

  • 允许写入 NewSubfileType=0 (#132)。

  • 支持编写条带或平铺字节的迭代器。

  • 写入时将可迭代对象(不是迭代器)转换为 NumPy 数组。

  • 显式指定 imread 和 imwrite 的可选关键字参数。

  • 从 FileHandle 写入函数返回写入字节数。

2022.4.28

  • 添加选项以指定 fsspec 版本 1 URL 模板名称 (#131)。

  • 忽略 UIC 标签中的无效日期 (#129)。

  • 修复 zlib_encode 和 lzma_encode 以使用非连续数组 (#128)。

  • 修复 delta_encode 以保留 ndarray 的字节顺序。

  • 将 Imagecodecs 后备功能移动到私有模块并添加测试。

2022.4.26

  • 修复 TiffFile.shape_metadata 中的 AttributeError (#127)。

  • 使用预打包的二进制值修复 TiffTag.overwrite。

  • 如果 tile 迭代器包含 None,则写入稀疏 TIFF。

  • 写入样本太少的光度模式时引发 ValueError。

  • 提高测试覆盖率。

2022.4.22

  • 为 Python 3.10 (WIP) 添加类型提示。

  • 修复 Mypy 错误(中断)。

  • 将许多参数标记为仅位置或仅关键字(中断)。

  • 从 imread(中断)中删除不推荐使用的 pages 参数。

  • 删除不推荐使用的 compress 和 ijmetadata 写入参数(中断)。

  • 从 TiffFile 中删除不推荐使用的 fastij 和电影参数(破坏)。

  • 从 TiffFile 中删除不推荐使用的多文件参数(中断)。

  • 从 TiffTag.overwrite 中删除不推荐使用的 tif 参数(中断)。

  • 从 FileSequence.asarray 中删除不推荐使用的文件参数(中断)。

  • 删除将 imread 类传递给 FileSequence 的选项(中断)。

  • 从 __str__ 函数中删除可选参数(中断)。

  • 将 TiffPageSeries.offset 重命名为 dataoffset(中断)

  • 如果不存在 SubIFD(中断),请将 TiffPage.pages 更改为 None。

  • 将 TiffPage.index 更改为 int(中断)。

  • 将 TiffPage.is_contiguous、is_imagej 和 is_shape 更改为 bool(中断)。

  • 添加 TiffPage imagej_description 和 shape_description 属性。

  • 添加 TiffFormat 抽象基类。

  • 弃用lazyattr 并改用 functools.cached_property (破坏)。

  • Julian_datetime 为第 1 年之前的日期引发 ValueError(中断)。

  • 由于键入而导致导入时间倒退。

2022.4.8

有关旧版本,请参阅 CHANGES 文件。

笔记

TIFF,即标记图像文件格式,由 Aldus Corporation 和 Adob​​e Systems Incorporated 创建。STK、LSM、FluoView、SGI、SEQ、GEL、QPTIFF、NDPI、SCN、SVS、ZIF、BIF 和 OME-TIFF 是由 Molecular Devices (Universal Imaging Corporation)、Carl Zeiss MicroImaging、Olympus、Silicon Graphics 定义的自定义扩展International、Media Cyber​​netics、Molecular Dynamics、PerkinElmer、Hamamatsu、Leica、ObjectivePathology、Roche Digital Pathology 和 Open Microscopy Environment consortium。

Tifffile 支持 TIFF6 规范的子集,主要是 8、16、32 和 64 位整数、16、32 和 64 位浮点、灰度和多样本图像。具体来说,CCITT 和 OJPEG 压缩、没有 JPEG 压缩的色度子采样、色彩空间转换、不同类型的样本或 IPTC、ICC 和 XMP 元数据都没有实现。

除了经典的 TIFF,tifffile 还支持几种不严格遵守 TIFF6 规范的类 TIFF 格式。某些格式允许文件和数据大小超过经典 TIFF 的 4 GB 限制:

  • BigTIFF由版本号 43 标识,并使用不同的文件头、IFD 和具有 64 位偏移量的标记结构。该格式还添加了 64 位数据类型。Tifffile 可以读写 BigTIFF 文件。

  • ImageJ 超堆栈在第一个 IFD 之后连续存储可能超过 4 GB 的所有图像数据。> 4 GB 的文件仅包含一个 IFD。最多 6 维图像数据的大小和形状可以从第一个 IFD 的 ImageDescription 标签中确定,该标签是 Latin-1 编码的。Tifffile 可以读取和写入 ImageJ 超堆栈。

  • OME-TIFF文件在一个或多个 TIFF 或 BigTIFF 文件中最多存储 8 维图像数据。在第一个 IFD 的 ImageDescription 标记中找到的 UTF-8 编码的 OME-XML 元数据定义了 TIFF IFD 在高维图像数据中的位置。Tifffile 可以读取 OME-TIFF 文件并将 NumPy 数组写入单文件 OME-TIFF。

  • Carl Zeiss LSM文件存储所有低于 4 GB 的 IFD,并环绕指向高于 4 GB 的图像数据的 32 位 StripOffset。每个系列和位置的 StripOffsets 需要单独展开。StripByteCounts 标记包含未压缩数据的字节数。Tifffile 可以读取任何大小的 LSM 文件。

  • MetaMorph Stack,STK文件包含在第一页的图像数据之后连续存储的附加图像平面。平面总数等于 UIC2tag 的计数。Tifffile 可以读取 STK 文件。

  • ZIF是可缩放图像文件格式,是 BigTIFF 的子规范,具有 SGI 的 ImageDepth 扩展和其他压缩方案。仅允许使用 JPEG、PNG、JPEG XR 和 JPEG 2000 压缩的小端、平铺、交错、每个样本图像 8 位。Tifffile 可以读写 ZIF 文件。

  • Hamamatsu NDPI文件在文件头、IFD 和标签结构中使用一些 64 位偏移量。单个 LONG 类型的标记值可以超过 32 位。64 位标记值和偏移量的高字节存储在 IFD 结构之后。Tifffile 可以读取 > 4 GB 的 NDPI 文件。尺寸 >65530 或缺少重新启动标记的 JPEG 压缩段无法使用常见的 JPEG 库进行解码。Tifffile 通过在重新启动标记之间单独解码 MCU 来解决此限制,这表现不佳。BitsPerSample、SamplesPerPixel 和 PhotometricInterpretation 标签可能包含错误值,可以使用标签 65441 的值进行更正。

  • 飞利浦 TIFF幻灯片为平铺页面存储错误的 ImageWidth 和 ImageLength 标记值。可以使用第一页的 XML 格式描述的 DICOM_PIXEL_SPACING 属性更正这些值。Tifffile 可以读取飞利浦幻灯片。

  • Ventana/Roche BIF幻灯片将切片和元数据存储在 BigTIFF 容器中。根据 XMP 标记中的 TileJointInfo 元素,瓷砖可能会重叠并需要拼接。体积扫描使用 ImageDepth 扩展存储。Tifffile 可以读取 BIF 并解码单个图块,但不执行拼接。

  • ScanImage可选择允许大于 2 GB 的损坏的非 BigTIFF 文件。StripOffsets 和 StripByteCounts 的值可以使用整个文件中 IFD 和标签值的偏移量的恒定差异来恢复。如果图像数据连续存储在每个页面中,则 Tifffile 可以读取此类文件。

  • GeoTIFF 稀疏文件允许条带或平铺偏移量和字节数为 0。此类段在读取时被隐式设置为 0 或 NODATA 值。Tifffile 可以读取 GeoTIFF 稀疏文件。

  • Tifffile 形文件将多维图像系列的数组形状和用户提供的元数据以 JSON 格式存储在系列第一页的 ImageDescription 标记中。该格式允许多个系列、subifd、零偏移量和字节数的稀疏段以及截断系列,其中仅存在系列的第一页,并且图像数据连续存储。除了 Tifffile 之外,没有其他软件支持截断格式。

其他用于从 Python 读取、写入、检查或操作科学 TIFF 文件的库包括 aicsimageioapeer-ometiff-librarybigtifffabio.TiffIOGDALimreadlarge_imageopenslide-pythonopentilepylibtiffpylsmpymimagepython -bioformatspytiffscanimagetiffreader-pythonSimpleITKslideiotiffslidetifftoolstyfxtiff

参考

例子

将 NumPy 数组写入单页 RGB TIFF 文件:

>>> data = numpy.random.randint(0, 255, (256, 256, 3), 'uint8')
>>> imwrite('temp.tif', data, photometric='rgb')

从 TIFF 文件中读取图像作为 NumPy 数组:

>>> image = imread('temp.tif')
>>> image.shape
(256, 256, 3)

将 3 维 NumPy 数组写入多页 16 位灰度 TIFF 文件:

>>> data = numpy.random.randint(0, 2**12, (64, 301, 219), 'uint16')
>>> imwrite('temp.tif', data, photometric='minisblack')

从 TIFF 文件中读取整个图像堆栈作为 NumPy 数组:

>>> image_stack = imread('temp.tif')
>>> image_stack.shape
(64, 301, 219)
>>> image_stack.dtype
dtype('uint16')

从 TIFF 文件的第一页读取图像作为 NumPy 数组:

>>> image = imread('temp.tif', key=0)
>>> image.shape
(301, 219)

从选定的页面范围读取图像:

>>> images = imread('temp.tif', key=range(4, 40, 2))
>>> images.shape
(18, 301, 219)

遍历 TIFF 文件中的所有页面并连续读取图像:

>>> with TiffFile('temp.tif') as tif:
...     for page in tif.pages:
...         image = page.asarray()

在不读取任何图像数据的情况下获取有关 TIFF 文件中图像堆栈的信息:

>>> tif = TiffFile('temp.tif')
>>> len(tif.pages)  # number of pages in the file
64
>>> page = tif.pages[0]  # get shape and dtype of image in first page
>>> page.shape
(301, 219)
>>> page.dtype
dtype('uint16')
>>> page.axes
'YX'
>>> series = tif.series[0]  # get shape and dtype of first image series
>>> series.shape
(64, 301, 219)
>>> series.dtype
dtype('uint16')
>>> series.axes
'QYX'
>>> tif.close()

检查 TIFF 文件第一页中的“XResolution”标签:

>>> with TiffFile('temp.tif') as tif:
...     tag = tif.pages[0].tags['XResolution']
>>> tag.value
(1, 1)
>>> tag.name
'XResolution'
>>> tag.code
282
>>> tag.count
1
>>> tag.dtype
<DATATYPE.RATIONAL: 5>

遍历 TIFF 文件中的所有标签:

>>> with TiffFile('temp.tif') as tif:
...     for page in tif.pages:
...         for tag in page.tags:
...             tag_name, tag_value = tag.name, tag.value

覆盖现有标签的值,例如 XResolution:

>>> with TiffFile('temp.tif', mode='r+') as tif:
...     _ = tif.pages[0].tags['XResolution'].overwrite((96000, 1000))

使用 BigTIFF 格式、分离颜色分量、平铺、Zlib 压缩级别 8、水平差分预测器和附加元数据编写一个 5 维浮点数组:

>>> data = numpy.random.rand(2, 5, 3, 301, 219).astype('float32')
>>> imwrite(
...     'temp.tif',
...     data,
...     bigtiff=True,
...     photometric='rgb',
...     planarconfig='separate',
...     tile=(32, 32),
...     compression='zlib',
...     compressionargs={'level': 8},
...     predictor=True,
...     metadata={'axes': 'TZCYX'}
... )

将 xyz 体素大小为 2.6755x2.6755x3.9474 micron^3 的 10 fps 时间序列卷写入 ImageJ 超堆栈格式的 TIFF 文件:

>>> volume = numpy.random.randn(6, 57, 256, 256).astype('float32')
>>> imwrite(
...     'temp.tif',
...     volume,
...     imagej=True,
...     resolution=(1./2.6755, 1./2.6755),
...     metadata={
...         'spacing': 3.947368,
...         'unit': 'um',
...         'finterval': 1/10,
...         'axes': 'TZYX'
...     }
... )

从 ImageJ 文件中读取卷和元数据:

>>> with TiffFile('temp.tif') as tif:
...     volume = tif.asarray()
...     axes = tif.series[0].axes
...     imagej_metadata = tif.imagej_metadata
>>> volume.shape
(6, 57, 256, 256)
>>> axes
'TZYX'
>>> imagej_metadata['slices']
57
>>> imagej_metadata['frames']
6

创建一个包含空图像的 TIFF 文件并写入内存映射的 NumPy 数组(注意:这不适用于压缩或平铺):

>>> memmap_image = memmap(
...     'temp.tif',
...     shape=(256, 256, 3),
...     dtype='float32',
...     photometric='rgb'
... )
>>> type(memmap_image)
<class 'numpy.memmap'>
>>> memmap_image[255, 255, 1] = 1.0
>>> memmap_image.flush()
>>> del memmap_image

内存映射并读取 TIFF 文件中的连续图像数据:

>>> memmap_image = memmap('temp.tif')
>>> memmap_image.shape
(256, 256, 3)
>>> memmap_image[255, 255, 1]
1.0
>>> del memmap_image

将两个 NumPy 数组写入一个多系列 TIFF 文件(注意:其他 TIFF 阅读器将无法识别这两个系列;使用 OME-TIFF 格式以获得更好的互操作性):

>>> series0 = numpy.random.randint(0, 255, (32, 32, 3), 'uint8')
>>> series1 = numpy.random.randint(0, 1023, (4, 256, 256), 'uint16')
>>> with TiffWriter('temp.tif') as tif:
...     tif.write(series0, photometric='rgb')
...     tif.write(series1, photometric='minisblack')

从 TIFF 文件中读取第二个图像系列:

>>> series1 = imread('temp.tif', series=1)
>>> series1.shape
(4, 256, 256)

连续将一个连续系列的帧写入 TIFF 文件:

>>> data = numpy.random.randint(0, 255, (30, 301, 219), 'uint8')
>>> with TiffWriter('temp.tif') as tif:
...     for frame in data:
...         tif.write(frame, contiguous=True)

将图像系列附加到现有的 TIFF 文件(注意:这不适用于 ImageJ 超堆栈或 OME-TIFF 文件):

>>> data = numpy.random.randint(0, 255, (301, 219, 3), 'uint8')
>>> imwrite('temp.tif', data, photometric='rgb', append=True)

从切片生成器创建 TIFF 文件:

>>> data = numpy.random.randint(0, 2**12, (31, 33, 3), 'uint16')
>>> def tiles(data, tileshape):
...     for y in range(0, data.shape[0], tileshape[0]):
...         for x in range(0, data.shape[1], tileshape[1]):
...             yield data[y : y + tileshape[0], x : x + tileshape[1]]
>>> imwrite(
...     'temp.tif',
...     tiles(data, (16, 16)),
...     tile=(16, 16),
...     shape=data.shape,
...     dtype=data.dtype,
...     photometric='rgb'
... )

编写带有元数据的多维、多分辨率(金字塔形)、多系列 OME-TIFF 文件。亚分​​辨率图像被写入 SubIFD。缩略图被写为一个单独的图像系列:

>>> data = numpy.random.randint(0, 1023, (8, 2, 512, 512, 3), 'uint16')
>>> subresolutions = 2
>>> pixelsize = 0.29  # micrometer
>>> with TiffWriter('temp.ome.tif', bigtiff=True) as tif:
...     metadata={
...         'axes': 'TCYXS',
...         'SignificantBits': 10,
...         'Channel': {'Name': ['Channel 1', 'Channel 2']},
...         'TimeIncrement': 0.1,
...         'TimeIncrementUnit': 's',
...         'PhysicalSizeX': pixelsize,
...         'PhysicalSizeXUnit': 'µm',
...         'PhysicalSizeY': pixelsize,
...         'PhysicalSizeYUnit': 'µm',
...     }
...     options = dict(
...         photometric='rgb',
...         tile=(128, 128),
...         compression='jpeg',
...         resolutionunit='CENTIMETER'
...     )
...     tif.write(
...         data,
...         subifds=subresolutions,
...         resolution=(1e4 / pixelsize, 1e4 / pixelsize),
...         metadata=metadata,
...         **options
...     )
...     # save pyramid levels to the two subifds
...     # in production use resampling to generate sub-resolution images
...     for level in range(subresolutions):
...         mag = 2**(level + 1)
...         tif.write(
...             data[..., ::mag, ::mag, :],
...             subfiletype=1,
...             resolution=(1e4 / mag / pixelsize, 1e4 / mag / pixelsize),
...             **options
...         )
...     # add a thumbnail image as a separate series
...     # it is recognized by QuPath as an associated image
...     thumbnail = (data[0, 0, ::8, ::8] >> 2).astype('uint8')
...     tif.write(thumbnail, metadata={'Name': 'thumbnail'})

访问金字塔形 OME-TIFF 文件中的图像级别:

>>> baseimage = imread('temp.ome.tif')
>>> second_level = imread('temp.ome.tif', series=0, level=1)
>>> with TiffFile('temp.ome.tif') as tif:
...     baseimage = tif.series[0].asarray()
...     second_level = tif.series[0].levels[1].asarray()

迭代并解码 TIFF 文件中的单个 JPEG 压缩图块:

>>> with TiffFile('temp.ome.tif') as tif:
...     fh = tif.filehandle
...     for page in tif.pages:
...         for index, (offset, bytecount) in enumerate(
...             zip(page.dataoffsets, page.databytecounts)
...         ):
...             _ = fh.seek(offset)
...             data = fh.read(bytecount)
...             tile, indices, shape = page.decode(
...                 data, index, jpegtables=page.jpegtables
...             )

使用 Zarr 读取 TIFF 文件中的平铺金字塔图像的一部分:

>>> import zarr
>>> store = imread('temp.ome.tif', aszarr=True)
>>> z = zarr.open(store, mode='r')
>>> z
<zarr.hierarchy.Group '/' read-only>
>>> z[0]  # base layer
<zarr.core.Array '/0' (8, 2, 512, 512, 3) uint16 read-only>
>>> z[0][2, 0, 128:384, 256:].shape  # read a tile from the base layer
(256, 256, 3)
>>> store.close()

从 Zarr 存储中加载基础层作为 dask 数组:

>>> import dask.array
>>> with imread('temp.ome.tif', aszarr=True) as store:
...     dask.array.from_zarr(store, 0)
dask.array<...shape=(8, 2, 512, 512, 3)...chunksize=(1, 1, 128, 128, 3)...

以 JSON 格式将 Zarr 存储写入 fsspec ReferenceFileSystem:

>>> with imread('temp.ome.tif', aszarr=True) as store:
...     store.write_fsspec('temp.ome.tif.json', url='file://')

以 Zarr 组的形式打开 fsspec ReferenceFileSystem:

>>> import fsspec
>>> import imagecodecs.numcodecs
>>> imagecodecs.numcodecs.register_codecs()
>>> mapper = fsspec.get_mapper(
...     'reference://', fo='temp.ome.tif.json', target_protocol='file'
... )
>>> z = zarr.open(mapper, mode='r')
>>> z
<zarr.hierarchy.Group '/' read-only>

创建一个包含空的平铺图像系列的 OME-TIFF 文件,并通过 Zarr 接口写入(注意:这不适用于压缩):

>>> imwrite(
...     'temp.ome.tif',
...     shape=(8, 800, 600),
...     dtype='uint16',
...     photometric='minisblack',
...     tile=(128, 128),
...     metadata={'axes': 'CYX'}
... )
>>> store = imread('temp.ome.tif', mode='r+', aszarr=True)
>>> z = zarr.open(store, mode='r+')
>>> z
<zarr.core.Array (8, 800, 600) uint16>
>>> z[3, 100:200, 200:300:2] = 1024
>>> store.close()

从一系列 TIFF 文件中读取图像作为 NumPy 数组:

>>> imwrite('temp_C001T001.tif', numpy.random.rand(64, 64))
>>> imwrite('temp_C001T002.tif', numpy.random.rand(64, 64))
>>> image_sequence = imread(['temp_C001T001.tif', 'temp_C001T002.tif'])
>>> image_sequence.shape
(2, 64, 64)
>>> image_sequence.dtype
dtype('float64')

从一系列 TIFF 文件中读取图像堆栈,文件名模式为 NumPy 或 Zarr 数组:

>>> image_sequence = TiffSequence(
...     'temp_C0*.tif', pattern=r'_(C)(\d+)(T)(\d+)'
... )
>>> image_sequence.shape
(1, 2)
>>> image_sequence.axes
'CT'
>>> data = image_sequence.asarray()
>>> data.shape
(1, 2, 64, 64)
>>> with image_sequence.aszarr() as store:
...     zarr.open(store, mode='r')
<zarr.core.Array (1, 2, 64, 64) float64 read-only>
>>> image_sequence.close()

以 JSON 格式将 Zarr 存储写入 fsspec ReferenceFileSystem:

>>> with image_sequence.aszarr() as store:
...     store.write_fsspec('temp.json', url='file://')

将 fsspec ReferenceFileSystem 作为 Zarr 数组打开:

>>> import fsspec
>>> import tifffile.numcodecs
>>> tifffile.numcodecs.register_codec()
>>> mapper = fsspec.get_mapper(
...     'reference://', fo='temp.json', target_protocol='file'
... )
>>> zarr.open(mapper, mode='r')
<zarr.core.Array (1, 2, 64, 64) float64 read-only>

从命令行检查 TIFF 文件:

$ python -m tifffile temp.ome.tif