Olympus是一家知名显微镜解决方案提供商,提供的高端光学显微设备与计算机结合,对微观世界进行摄录,并保存为计算机中的文件。但是这种文件不是开放的格式,而是Olympus自研的OIR格式,不能用常规的媒体播放器查看。
现有工具
如果你只是想快速找到打开OIR图像的方法,可了解以下现有工具:
Image5D
GitHub源码https://github.com/Silver-Fang/Image5DMATLAB工具箱https://ww2.mathworks.cn/matlabcentral/fileexchange/114435-image5d-oir-tiff此工具为作者自研,只提供API接口,没有用户图形界面,不能直接查看OIR图像。适合熟悉C++或MATLAB,但不了解OIR格式本身的开发者使用。本库将复杂的OIR文件格式细节隐藏,允许开发者像操作5D数组一样,操作OIR格式文件。
优点:性能仅次于官方软件,100㎇ OIR 图像序列,初次需数十秒建立索引,后续一般不超过10s。支持简单的OIR文件序列拼接。直接操作原始数据,功能灵活性高。
缺点:仅支持Windows操作系统,需要C++或MATLAB编程基础,没有友好用户界面
Olympus FluoView
官方下载https://www.olympus-lifescience.com/en/downloads/detail-iframe/?0%5Bdownloads%5D%5Bid%5D=847252002此工具为Olympus官方发布,提供用户界面,可以快速查看OIR图像,不需要编程基础。支持丰富的编辑、标注、测量功能,以及导出为通用TIFF格式。
优点:性能最高,打开100㎇ OIR 图像序列不超过10s。用户界面友好,不需要编程
缺点:使用某些功能需要激活/付费,甚至配套Olympus显微镜硬件设备才能使用。操作系统兼容性差,不仅不支持Windows以外的操作系统,而且实测在较新的Win10、11上也表现稳定性不佳。不能操作原始数据,因此只能使用软件中提供的有限的分析方法。虽然支持导出为TIFF,但文件大小不能超过4㎇。不提供任何API接口,无法编程。
Fiji ImageJ
官方主页https://imagej.net/imagej-wiki-static/Fiji此工具为第三方发布,提供用户界面可以查看OIR图像,不需要编程基础,但有Java编程基础可以使用更高级的功能。支持丰富的编辑、标注、测量功能,以及导出为通用TIFF格式。
优点:用户界面友好,不需要编程,但Java编程可以实现更复杂功能。有英文支持社区。跨平台,支持三大主流PC系统。
缺点:性能最差,每次打开100㎇ OIR 图像序列都需要数十分钟到一小时左右
OME Bioformats
官网主页https://www.openmicroscopy.org/bio-formats/此工具实际上是 Fiji ImageJ 依赖的基础库,因此不提供用户界面,需要Java或MATLAB编程基础。如需导出为TIFF格式,还需要对TIFF格式有一定了解。提供了优化性能的缓存方法,初次打开速度和Fiji相同,但建立缓存后,再次打开则速度则仅比Image5D的再次打开速度稍慢。
优点:有英文支持社区。跨平台,支持三大主流PC系统。
缺点:初次打开大型OIR文件极慢,对100㎇ OIR 图像序列往往需要数十分钟到一小时。不提供用户界面,需要Java或MATLAB编程基础。
格式解析
如果你希望从头开始开发OIR读入器,或者对现有开源工具进行改进,你将需要了解OIR文件格式的具体规范。以下假定你具有C++编程基础,用到的C++特性将不作额外解释。
此格式解析并非官方提供文档,而是作者自行逆向工程得到,因此存在一些未知字段,不影响一般使用。
OIR文件存储未经压缩的图像原始数据和相关元数据信息。对于小于1㎇的OIR图像,所有信息保存在一个单独的.oir文件中。对于超过1㎇的OIR图像,将拆分成多个OIR文件,组成带编号的序列,序列头文件具有扩展名.oir,后续文件仅具有编号,没有扩展名,每个文件1㎇左右。虽然拆分成多个文件,但每个文件的字节排布格式结构都是大致相同的,都包含三个大区:文件头、基块序列和基块索引。
文件头
#pragma pack(4)
struct Oir文件头
{
const char OLYMPUSRAWFORMAT[16] = { 'O','L','Y','M','P','U','S','R','A','W','F','O','R','M','A','T' };
const uint32_t 未知字段[4] = { 12,0,1,2 };
uint64_t 文件大小;
uint64_t 索引位置;
};
首先是一个固定的16字节字符串,然后是16字节未知字段,然后是uint64_t文件大小和索引位置。其中索引位置指的是基块索引大区的起始位置。这里的位置指的是相对于文件起始的字节数偏移,以后不再解释。
基块索引
struct 基块索引大区
{
const int32_t 大区头 = -1;
uint64_t* 索引列表() { return (uint64_t*)(this + 1); }
//此函数的意义是,索引列表紧随大区头之后开始,排列多个uint64_t,但数目不确定。后续还会有类似这样的语法表示,不再注释。
}
基块索引大区以一个int32_t类型的-1起始,随后排列多个uint64_t索引直到文件尾。列表中的索引值,标识的就是基块序列大区中,每个基块的位置。可以通过基块索引快速定位到基块序列大区中的任何一个基块。
基块序列
所有的图像像素数据和元数据都零碎排列在基块序列大区的基块中,但这种排布是有规律的,而不是胡乱排布。基块序列大区的基本结构:
成员 | 重复 |
---|---|
REF块 | 0~ |
帧属性块 |
1 |
UID-像素块 | C通道数×每图面分块数 |
元数据块 | 1 |
空块 | 1 |
帧属性-(UID-像素块)×C通道数×每图面分块数 | 1~ |
头文件中为BMP块,后续文件中为空块 | 1 |
元数据块 | 1 |
先看单个基块的基本结构:
enum class Oir基块类型 :uint32_t
{
元数据,
帧属性,
BMP,
UID,
像素,
空,
};
struct Oir基块
{
uint32_t 长度;
Oir基块类型 类型;
};
首先是uint32_t的长度。这个长度,取决于Oir基块类型,并不总是能准确表示基块实际在文件中占用的字节数,而且Oir基块在文件中也不一定是紧密排列的,因此该字段的作用需要根据具体的基块类型区别使用。
随后是基块类型,共有6种基块类型,依次用 uint32_t 0~5 表示,下面将按照它们在基块序列大区中的排列规律依次展开阐述。
读入OIR文件时,我们通常先从文件头取得索引位置,找到基块索引,然后用这个索引在基块序列大区中遍历每个基块。首先遍历到的,往往是一类称为REF的基块,它们同样具有6种Oir基块类型之一,但不包含我们关心的像素值或元数据,可能为Olympus内部使用,我们首先需要从头开始逐一遍历并跳过这些基块,因为它们的数目是不确定的(也可能是0个)。
识别REF块最简单的方法就是看Oir基块类型。REF块可能具有各种基块类型,但唯一例外的是帧属性:REF基块不可能具有帧属性类型。因此,只要扫描到第一个帧属性类型的基块,就意味着REF基块序列的结束,此文件内后续将不再出现REF基块。
帧属性块
此类型基块在整个文件中重复多次出现,标识特定于一“帧”的元数据。这里帧的概念,对于单Z层图像,一个时间Cycle循环所拍摄的各通道图面的总和为一帧;对于多Z层图像,一个循环所拍摄的多个Z层各自为一帧。简单来说,这里的“帧”可以理解为Z层,也就是说,每次扫过一个Z层都会产生一个对应的帧属性块。
帧属性块仅包含帧特定的XML元数据,对于批量读入操作来说意义不大,因此本文不展开讨论。
首个帧属性块之后是一个UID块,此基块结构扩展了一般的Oir基块:
UID块
struct UID块 :Oir基块
{
uint32_t 前像素块长度;
uint32_t 后像素块长度;
uint32_t UID长度;
char* UID字符串() { return (char*)(this + 1);}
};
UID块中,我们主要关心UID长度和UID字符串。UID字符串不是C样式的0结尾字符串,因此需要一个前置UID长度字段来限制其长度。一个典型的UID字符串如下:
z002t17000_0_1_4b0b6a28-7bc9-46f6-be65-5e6406aa0b9f_0
可以看出,这个UID字符串又可分为多个子字段,其中许多似乎都是内部使用的,作用未知。我们只需要关心它的首字符,如果这个首字符是z,表示图象是多Z层的,否则就是单层。这个标志仅用于判断多Z层拍摄功能是否启用,具体有几个Z层将在后续块中得到。
只要第一个帧属性块后的第一个UID块具有Z标志,此文件内后续所有UID块都将具有Z标志,可以认为整个文件序列都使用了多Z层拍摄功能,因此无需反复检查。
每个UID块之后一定会紧随(这里的紧随是指基块索引大区中紧随,不是基块序列大区的字节级紧随,后续不再赘述)一个像素类型的基块。
像素块
struct 像素块 :Oir基块
{
uint16_t* 像素值() { return (uint16_t*)(this + 1); }
};
像素块结构十分简单,就是在Oir基块基础上跟随紧密排列的uint16_t像素值。Oir基块中的长度字段,就是此像素序列的字节数。OIR格式采用uint16_t类型的像素值,每个像素占2字节,数值范围0~1023,因此字节数是实际像素数的2倍。
UID-像素块循环
一个像素块仅包含1个循环、1个Z层、1个通道的部分像素值,维度顺序先X后Y。反过来说,1个循环、1个Z层、1个通道的像素值XY图面,会被分割到多个像素块中,这些像素块的大小还不一定相同。但是,同一个文件中所有图面的像素块分割方式总是相同的。例如,一个512×512的图面有262144个像素,假设其被拆分的第1个像素块具有485376字节,即242688像素,即512×474;第2个像素块包含剩余字节。那么,在整个OIR文件序列中,所有其它循环、其它Z层、其它通道的图面,也将被分割成2个像素块,其中第1个像素块具有242688像素。因此,读入器只需要确认1个图面的像素块分割方式,就可以记住并应用到所有其它图面中。
属于同一个图面的像素块必定按顺序排列,但不是连续的,中间会间隔其它基块。在同一帧(Z层)中,基块的排列顺序是:帧属性块、(UID块、像素块)×通道数×图面分块数。例如,假定图像有3个通道,每个图面分2块,则一帧中的基块排列顺序将是:
帧属性块、通道1的UID块1、通道1的像素块1、通道2的UID块1、通道2的像素块1、通道3的UID块1、通道3的像素块1、通道1的UID块2、通道1的像素块2、通道2的UID块2、通道2的像素块2、通道3的UID块2、通道3的像素块2
在实际读入文件时,包含Z层、C通道个数等重要信息的元数据块,其实是在第一帧之后的,所以在扫描第一帧时,读入器并不知道C通道个数,不能计算出第一帧究竟有多少个UID-像素块循环,因此只能逐个扫描。当扫描到一个基块不再是UID或像素块,而是元数据块时,即标志第一帧的结束。
可以看到,UID-像素块的重复出现了6个循环。读入器应当记住这6个像素块的长度,等后续从元数据中读到C通道个数以后,将它们分组从而得到每个图面的分割方式。
元数据块
元数据块紧随第一帧的UID-像素块循环之后。元数据块同样具有Oir基块的2个基本成员,但在单个元数据块内还划分为多个子块,每个子块具有一个子块头,子块之间字节级紧密排列,但不会记录在基块索引中,因为子块是整个元数据基块的一部分,不是单独的基块。
元数据基块头(就是Oir基块)、子块1、子块2……
enum class 子块类型 :uint32_t
{
fileinfo = 1,
lsmimage,
annotation,
overlay,
lut,
};
struct 元数据子块
{
子块类型 类型;
uint32_t 未知字段[8];
uint32_t 长度;
char* XML() { return (char*)(this + 1); }
//lut子块比较特殊,子块头后面不是XML,下面详解
};
子块头结构,第一个uint32_t字段标识该子块的类型,接下来是8个未知的uint32_t字段,最后是一个uint32_t字段标识子块体的长度。
子块有5种类型,分别用 uint32_t 1~5 标识。对于lut以外类型的子块,字节级紧随子块头之后的就是子块体XML文本,字符数目记录在块头的长度字段中。一般整个元数据块中也就是按照1~5的顺序依次排列各个子块,读入器可以按需获取XML文本并解析出所需的元数据。一般来说,最重要的是lsmimage和lut两个子块。lsmimage包含OIR图像的XYCZ尺寸信息、扫描仪类型、光电倍增管参数、采样帧率等重要信息,lut包含各通道颜色信息。
lsmimage
<?xml version="1.0" encoding="ASCII"?>
<lsmimage:imageProperties xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:base="http://www.olympus.co.jp/hpf/model/base" xmlns:commonimage="http://www.olympus.co.jp/hpf/model/commonimage" xmlns:commonparam="http://www.olympus.co.jp/hpf/model/commonparam" xmlns:commonphase="http://www.olympus.co.jp/hpf/model/commonphase" xmlns:dye="http://www.olympus.co.jp/hpf/model/dye" xmlns:fvBi="http://www.olympus.co.jp/fluoview/model/fv_bi" xmlns:fvCommonimage="http://www.olympus.co.jp/fluoview/model/fv_commonimage" xmlns:fvCommonparam="http://www.olympus.co.jp/fluoview/model/fv_commonparam" xmlns:fvCommonphase="http://www.olympus.co.jp/fluoview/model/fv_commonphase" xmlns:fvLsmimage="http://www.olympus.co.jp/fluoview/model/fv_lsmimage" xmlns:fvLsmparam="http://www.olympus.co.jp/fluoview/model/fv_lsmparam" xmlns:lsmimage="http://www.olympus.co.jp/hpf/model/lsmimage" xmlns:lsmparam="http://www.olympus.co.jp/hpf/model/lsmparam" xmlns:opticalelement="http://www.olympus.co.jp/hpf/model/opticalelement" xmlns:roi="http://www.olympus.co.jp/hpf/model/roi" xmlns:root="http://www.olympus.co.jp/hpf/model/root" version="1.1.0.0">
<commonimage:acquisition xsi:type="lsmimage:LSMAcquisition" embedSaturationInfo="false">
<commonimage:imagingParam xsi:type="lsmparam:LSMImagingParam">
<commonparam:axis xsi:type="commonparam:ZAxisParam" enable="true" paramEnable="false">
<commonparam:maxSize>2</commonparam:maxSize>
<commonparam:paramName>Start End</commonparam:paramName>
</commonparam:axis>
<commonparam:axis xsi:type="commonparam:ZAxisParam" enable="true" paramEnable="false">
<commonparam:maxSize>21</commonparam:maxSize>
<commonparam:paramName>Range</commonparam:paramName>
</commonparam:axis>
<commonparam:axis xsi:type="fvCommonparam:PiezoZAxisParam" enable="true" paramEnable="true">
<commonparam:maxSize>2</commonparam:maxSize>
<commonparam:paramName>Piezo</commonparam:paramName>
</commonparam:axis>
</commonimage:imagingParam>
<commonimage:phase xsi:type="lsmimage:LSMPhase" id="4d9b3011-513c-46d7-b618-ae6d02b33ab9">
<commonphase:group id="ca4b116d-eb69-493e-bcf9-3c1f69ae8f40">
<commonphase:channel xsi:type="lsmimage:NormalChannel" id="4b0b6a28-7bc9-46f6-be65-5e6406aa0b9f" order="1" enable="true" detectorId="__FV30-AGAPD_D_4__">
<commonphase:deviceName>RNDD4G</commonphase:deviceName>
</commonphase:channel>
</commonphase:group>
<commonphase:group id="3e38c1da-64c1-4e9a-984c-27d1a1d566de">
<commonphase:channel xsi:type="lsmimage:NormalChannel" id="156c0f13-0c1a-40da-8e60-1ef1f60b685f" order="2" enable="true" detectorId="__FV30-ANALOG_D_1__">
<commonphase:deviceName>CD1</commonphase:deviceName>
</commonphase:channel>
</commonphase:group>
<commonphase:group id="593c687e-5d32-4d18-9c7c-c18fa677e0bd">
<commonphase:channel xsi:type="lsmimage:NormalChannel" id="dc1be3ad-2278-421a-8033-53af232f6e60" order="3" enable="true" detectorId="__FV30-ANALOG_D_2__">
<commonphase:deviceName>CD2</commonphase:deviceName>
</commonphase:channel>
</commonphase:group>
</commonimage:phase>
<commonimage:configuration xsi:type="lsmimage:LSMConfiguration">
<commonimage:zdcMode>OneShot</commonimage:zdcMode>
<lsmimage:scannerType>Resonant</lsmimage:scannerType>
</commonimage:configuration>
<lsmimage:scannerSettings type="Galvano">
<lsmimage:param>
<lsmparam:imageSize>
<commonparam:width>512</commonparam:width>
<commonparam:height>512</commonparam:height>
</lsmparam:imageSize>
<lsmparam:speed>
<commonparam:roundtrip>false</commonparam:roundtrip>
<commonparam:speedInformation>
<commonparam:seriesInterval>2238.71</commonparam:seriesInterval>
</commonparam:speedInformation>
</lsmparam:speed>
</lsmimage:param>
</lsmimage:scannerSettings>
<lsmimage:scannerSettings type="Resonant">
<lsmimage:param>
<lsmparam:imageSize>
<commonparam:width>512</commonparam:width>
<commonparam:height>512</commonparam:height>
</lsmparam:imageSize>
<lsmparam:speed>
<commonparam:roundtrip>true</commonparam:roundtrip>
<commonparam:speedInformation>
<commonparam:seriesInterval>124.7148288973384</commonparam:seriesInterval>
</commonparam:speedInformation>
</lsmparam:speed>
</lsmimage:param>
</lsmimage:scannerSettings>
</commonimage:acquisition>
</lsmimage:imageProperties>
以上是一段示例 lsmimage XML 元数据,省略了许多不重要的节点,只保留关键信息和层级骨架。实际文件中真正的 lsmimage XML 可长达80㎅量级。
commonimage:imagingParam节点中存储Z层信息。OIR支持 Start End, Range, Piezo 三种Z轴,三选一,根据commonparam:axis的paramEnable属性判断选哪个:只有一个会是true。在正确的Z轴下的commonparam:maxSize节点内,就是Z尺寸了。
Olympus显微镜还支持多个不同的设备同时工作,作为多个不同通道的信号源。commonimage:phase节点下有多个commonphase:group,每个commonphase:group下有一个commonphase:channel。读入器一般应当记录commonphase:channel节点的id、order和enable属性。具有enable=”true”的通道总数即为有效C通道个数,这些通道按照order顺序排列,这个顺序就是UID-像素块循环中通道排列的顺序。id属性也应当记录,在lut子块中将会被引用到。再往下一级还有commonphase:deviceName节点,记录通道设备名称,可按需记录。
lsmimage:scannerType记录了使用的扫描仪类型,一般是Resonant或Galvano,记录这个值主要用于选择接下来的lsmimage:scannerSettings节点,因为同名节点有多个,应其type属性是否与lsmimage:scannerType相同来选择。在正确的节点下,可以找到commonparam:width节点,记录图像宽度;commonparam:height节点,记录图像高度;commonparam:seriesInterval节点,记录循环周期毫秒数,用1000/seriesInterval就是循环速率FPS,可用于将循环数换算到真实的事件。
lut
lut子块头紧随其后的不是XML文本,而是多个UID-XML循环:
成员 | 类型 |
---|---|
UID长度 | uint32_t |
UID字符串 | char[UID长度] |
XML长度 | uint32_t |
XML文本 | char[XML长度] |
循环次数等于有效通道个数。这里的UID,对应的就是lsmimage中记录的commonphase:channel节点的id属性,表示接下来的XML描述的是该通道的lut。lut XML中最重要的就是通道颜色信息,示例如下:
<lut:LUT xmlns:lut="http://www.olympus.co.jp/hpf/model/lut" applicationVersion="2.3.1.163" platformVersion="1.23.2.205" version="2.2" id="1ebf8287-05a9-4f54-a261-cc80af13e7de">
<lut:red>
<lut:contrast>0.0</lut:contrast>
</lut:red>
<lut:green>
<lut:contrast>1.0</lut:contrast>
</lut:green>
<lut:blue>
<lut:contrast>0.0</lut:contrast>
</lut:blue>
<lut:alpha>
<lut:contrast>0.0</lut:contrast>
</lut:alpha>
</lut:LUT>
同样,省略了许多不重要的节点,只保留关键信息和层级骨架。实际文件中真正的 lut XML 可长达500㎅量级。
lut XML 中最重要的信息是通道颜色。从示例中可以清楚看到,通道颜色分为RGBA三个分量,分别记录在 lut:red、lut:green、lut:blue、lut:alpha 4 个节点的lut:contrast子节点下,用浮点值0~1表示,示例中表示的就是一个纯绿色的通道颜色。
每个有效通道,都会对应一个 lut UID-XML 对组,因此整个lut子块读完以后,应当就能获知所有有效通道的颜色RGBA分量。
空块
元数据块后紧随的是一个空块。空块似乎无任何有效信息,仅作分隔符使用。
帧循环
空块之后就是占据文件绝大部分体积的帧循环了。一个帧循环以一个帧属性块开头,然后是一串UID-像素块子循环,子循环的次数是C通道数×每图面分块数。
Fiji和 OME Bioformats 在这一步,都选择扫描整个文件中的所有帧循环,获取每个像素块的位置。但实际上,读入器到达这里时,应当已经记录了充分的信息,可以计算出这个子循环数,进而计算出每个帧循环包含多少个基块,以及每个基块的类型,最终得到每个像素块的位置,而不再需要实际通过基块索引去访问那个基块,只需要扫描一个uint64_t的小小索引即可。相比于通过磁盘或网络等I/O途径访问外存,直接在内存中计算出所有像素块的位置要快上百倍,这也是作者自研的Image5D工具能拥有仅次于官方工具的性能的根本原因。
一个OIR文件只能保存1㎇左右的信息,达到限制时会被截断,写入下一个文件。当然这截断也不是任意的,而是会至少保证一个帧循环写完才会进入下一个文件。再次提醒,帧循环不是时间循环,时间循环可以包含多个Z层,每个Z层都是一个独立的帧循环,因此可能会存在一个文件交界处的时间循环被拆分到两个文件中的情形。
从帧循环的结构可知,一个帧循环至少包含一个帧属性块、一个UID块和一个像素块,也就是至少3块。因此,如果遍历完某个帧循环后,文件中剩余基块索引的数目已不足3个,即可认为帧循环已结束,需要考虑处理下一个文件了。
BMP块
所有帧循环结束后,或者因达到文件大小限制而被迫结束帧循环后,紧跟的就是BMP块。此块包含一个二进制BMP文件,似乎仅供内部使用,对读入器来说无甚用途。此外,仅OIR序列的头文件中包含BMP块,在后续文件中,此位置仅包含一个空块作为占位符。
BMP块或空块之后就是OIR文件的最后一个基块,一般是一个元数据块。此块和文件头部的元数据块完全重复,没有必要再次读取。
以上便是OIR文件格式规范的全部内容。一般来说,当我们首次读取文件序列,得到所有像素块的位置后,应当将这些位置信息保存到一个缓存文件中,这样下次再次打开同样的文件就可以更快,因为不再需要重新建立索引了。
此外,由于OIR文件序列中的每个文件都具有基本相同的结构,所以实际上两个各项元数据参数设置相同的OIR文件序列,可以直接通过修改文件名的方式串联在一起,只需要在读入器的设计上注意处理一些连接处的小小细节即可。作者自研的Image5D工具即支持这种文件名串联,直接讲两个序列当作一个序列读入,而不需要修改文件内的任何字节。但是,这样串联后的文件序列无法用官方工具打开,请谨慎使用。
今天的文章olympus image share软件_相机cr2怎么转换成jpg格式分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/83512.html