13

在我的应用程序中,我必须从一组图像(MRC 图像)中加载体积数据并将像素数据保存在内存中。(图像是灰度的,因此每个像素一个字节)。

我的开发环境是 QT 框架,MinGW for Windows 和 GCC for Linux。

目前,我使用一个简单的数据结构将volumedata存储为:

unsigned char *volumeData;

并按如下方式进行大量分配。

volumeData=new unsigned char[imageXsize * imageYsize * numofImages];

以下是在给定平面中访问图像数据的重要方法,例如

unsigned char* getXYPlaneSlice(int z_value);
unsigned char* getYZPlaneSlice(int x_value);
unsigned char* getZXPlaneSlice(int y_value);

使用我简单的数据结构,很容易实现上述方法。

但是将来我们可能需要采用 2000x2000x1000 (~3.7Gb) 的卷大小。而当前的数据结构将无法处理如此庞大的数据。

  1. 如何避免碎片化?现在,即使有 1000x1000x200 的数据,应用程序也会崩溃并给出 bad_alloc。为此更改数据结构的最佳方法是什么?我应该使用像链表这样的东西,每个块的大小为 100mb。

  2. 此外,用户应该能够对体积数据执行一些图像处理过滤器,并且还应该能够重置为原始像素值。这意味着,我应该保留两份卷数据。与当前的实现类似。

    无符号字符 *volumeDataOriginal;

    无符号字符 *volumeDataCurrent;

因此,对于 2000x2000x1000 数据范围,它将使用大约 8Gb(每个卷 4Gb)。但是在Win32中,地址空间是4GB。如何解决呢?我应该使用 64 位应用程序吗?

编辑:这是我的应用程序的快照 在此处输入图像描述

基本上,我加载体积数据(来自一组图像,来自 MRC 格式..等)并将它们显示在不同的平面查看器中(XY,YX,YZ.Image 显示 XY 平面查看器)。我需要保持在上面3 种数据访问方法在特定平面中显示图像。使用滑块用户可以更改要在所选平面中显示的图像)

提前致谢。

4

11 回答 11

14

我认为你应该看看hdf5。这是一种二进制格式,用于存储从望远镜、物理实验和基因测序机等设备收集的大量数据。使用这样的东西有很多好处,但三个直接的想法是:(1)经过测试,(2)支持超板选择,以及(3)您可以免费获得压缩。

有可用的 C/C++、java、python、matlab 库。

于 2010-11-09T07:27:01.420 回答
5

64 位可能是处理此问题的最简单方法……在您使用页面时让操作系统出错。否则,如果不了解您通过数据的访问模式,就很难提出很多建议。如果您定期扫描图像以查找相同像素坐标处的值,那么谈论具有指向图像的指针以按需保存和重新加载是没有意义的。

对于撤消数据,您可以按照您的建议保留完整的备份副本,或者您可以尝试进行撤消操作来查看所做的更改并负责找到有效的实现。例如,如果您只是翻转位,那么这是非破坏性的,您只需要一个函子来执行相同的位翻转操作即可撤消更改。如果将所有像素设置为相同的色调是常见操作(例如填充、清除),那么您可以使用布尔值和单个像素来编码该图像状态,并使用完整缓冲区进行撤消。

于 2010-11-09T07:12:45.947 回答
5

解决问题的最简单方法是使用 64 位地址空间 - 现代 Mac 开箱即用地支持此功能,在 Windows 和 Linux 上,您需要安装 64 位版本的操作系统。我相信 Qt 可以很好地用于构建 64 位应用程序。32 位系统将无法支持您正在谈论的大小的单个分配 - 即使是具有 4 GB 地址空间可用于应用程序的 Mac 也无法进行单个 3.7 GB 分配,因为不会是一个可用大小的连续空间。

对于撤消,我会考虑使用内存映射文件和写时复制来复制块:

http://en.wikipedia.org/wiki/Copy-on-write

这意味着您实际上不必复制所有原始数据,系统将在页面写入时复制它们。如果您的图像比实际内存大得多并且您没有更改图像的每个部分,这将极大地提高性能。看起来具有“私人”访问权限的boost::map_file可能对此有所帮助。

如果你真的,真的需要支持 32 位系统,你唯一的选择就是以某种方式将这些大块分解,通常是平面或子卷。但是,在应用 3D 滤镜等方面,两者都很难使用,所以如果可以的话,我真的会避免这种情况。

如果您确实走子卷路线,一个技巧是将所有子卷保存在内存映射文件中,并仅在需要时将它们映射到您的地址空间。当从地址空间取消映射时,它们应该保留在统一缓冲区缓存中直到被清除,这实际上意味着您可以使用比地址空间更多的 RAM(特别是在 Windows 上,默认情况下 32 位应用程序仅获得 2 GB 的地址空间) .

最后,在 32 位 Windows 上,您还可以查看 boot.ini 中的 /3GB 开关。这允许您为应用程序分配 3 GB 的地址空间,而不是通常的 2 GB。从您描述的问题来看,我认为这不会为您提供足够的地址空间,但是它可以帮助您处理一些较小的卷。请注意,/3GB 开关可能会导致某些驱动程序出现问题,因为它会减少内核可用的地址空间量。

于 2010-11-18T22:55:28.730 回答
4

您可以使用内存映射文件来管理内存有限的大型数据集。但是,如果您的文件大小为 4GB,则建议使用 64 位。boost 项目有一个很好的多平台内存映射库,它的性能非常接近您正在寻找的东西。

http://en.wikipedia.org/wiki/Memory-mapped_file http://www.boost.org/doc/libs/1_44_0/libs/iostreams/doc/classes/mapped_file.html 让你开始。下面的一些示例代码 -

#include <boost/iostreams/device/mapped_file.hpp>
boost::iostreams::mapped_file_source input_source;
input_source.open(std::string(argv[1]));
const char *data = input_source.data();
long size = input_source.size();
input_source.close();

谢谢,内森

于 2010-11-15T21:37:17.463 回答
3

我会考虑的一个选项是内存映射,而不是映射所有图像,而是维护一个延迟加载的图像链接列表。当您的过滤器通过图像列表工作时,请根据需要加载。在加载阶段,映射一个相同大小的匿名(或一些固定的临时文件)块并将图像复制到那里作为备份。当您应用过滤器时,您只需备份到此副本。正如@Tony 上面所说,64 位是您的最佳选择,对于多平台内存映射文件,请查看 boost interprocess。

于 2010-11-09T09:18:53.110 回答
3

使用STXXL:超大型数据集的标准模板库。

我第一次听说它是在SO :)

于 2010-11-16T23:55:21.363 回答
1

您可以使用两级结构:指向单个图像或(更好)一堆图像的指针数组。因此,您可以将 20 个图像保存在一个内存块中,并将指向 20 个图像块的指针放入数组中。在进行随机访问时,这仍然很快(与链表相比)。

然后,您可以实现一个简单的分页算法:首先,数组中的所有指针都是 NULL。当您第一次访问一个图像块时,您将该块的 20 个图像加载到内存中并将指针写入数组。对这些图像的下一次访问不会加载任何内容。

如果由于加载和加载了许多图像块而导致内存变低,则可以删除最少使用的图像块(您应该在指针旁边添加第二个字段,在该指针中放入您计数的计数器的值加载图像块的时间)。具有最低计数器的图像块是最少使用的并且可以被丢弃(内存被重新用于新块并且指针设置为NULL)。

于 2010-11-16T18:25:16.827 回答
1

如今处理大量数据的趋势是将数据分解成更小的数据块,例如 64x64x64。如果你想用光照进行体渲染,那么你应该在相邻的砖块之间有 1 个体素重叠,这样就可以在不需要相邻砖块的情况下渲染单个砖块。如果你想用砖块做更复杂的图像处理,那么你可以增加重叠(以存储为代价)。

这种方法的优点是您只需要将必要的砖块加载到内存中。基于砖块的体积的渲染/处理时间并不比非砖块的基础体积慢很多。

要从体积渲染方面对此进行更深入的讨论,请查看有关 Octreemizer 的论文。 这是 citeseer 上的链接

于 2010-11-17T07:59:17.213 回答
1

主要问题可能是您是否希望完全随机访问您的数据。

最好的方法是考虑您要使用的算法,并且不能将它们写成主要只在一个方向上跨越数据。好吧,这并不总是可能的。

如果你想自己编写一个中等重量的解决方案,你应该这样做:

  • 用于将数据结构的切片mmap()映射到内存中
  • 将数据封装在一个类中,以便您可以访问当前未映射的数据
  • mmap()所需的区域,然后。

(实际上,无论如何,这就是操作系统正在做的事情,如果您mmap()一次处理整个文件,但通过进行一些控制,您可能会随着时间的推移使按需算法更智能,并满足您的要求)。

再说一次,如果你在那些图像体素上跳来跳去,这并不好玩。您的算法必须适合数据访问——对于您选择存储数据的每个解决方案。如果您的数据比您的物理内存大,随机访问将“破坏”一切。

于 2010-11-18T12:59:03.127 回答
1

如果硬件和操作系统允许,我会使用 64 位,并将文件映射到内存(请参阅 Windows 上的 CreateFileMapping 和 Linux 上的 mmap)。

在 Windows 上,您可以查看允许写入时复制的映射文件。我相信您也可以在 Linux 上获得该功能。无论如何,如果您在源文件上创建只读视图,那么这将是您的“原始数据”。然后在源文件上创建一个写时复制视图——这将是“当前数据”。

当您修改当前数据时,修改后的底层页面将被复制并分配给您,源数据的页面将保持不变。如果您确保不将相同的数据写入“当前数据”,您还将获得内存的最佳使用,因为您的当前数据和原始数据将共享内存页面。不过,您确实必须考虑页面对齐,因为写时复制工作在页面基础上。

此外,从当前数据恢复到原始数据是一项简单的工作。您需要做的就是为“当前数据”重新创建映射。

通过使用文件映射,管理内存的繁琐工作将由操作系统处理。它将能够以非常有效的方式使用所有可用内存。比使用普通堆分配完成的效率要高得多。

我将从研究在 Windows 上使用的 CreateFileView() 和 MapViewOfFile() 开始。对于 Linux,你有 mmap(),但据我所知。自 2000 年以来,我没有碰过任何东西 *nix ......

于 2010-11-18T19:18:36.970 回答
0

看看SciDB。我不是这方面的专家,但是从它的示例用例描述它的论文来看,它允许您自然地将数据映射到 3D(时间/版本控制为 +1D)数组中,如下所示:

CREATE ARRAY Pixels [
    x INT,
    y INT,
    z INT,
    version INT
] (
    pixel INT
);

并实现您的查询getXYPlaneSlice

Slice (Pixels, z = 3, version = 1);

为避免在仅更改部分数据时重复数据,您不需要为版本 1 填充整个数组,因为 SciDB 支持稀疏数组。然后,当您需要加载最新数据时,您可以加载 withversion = 0以获取旧版本,并使用另一个加载更新结果version = 1

于 2010-11-16T19:16:07.473 回答