分類: 程式設計

使用 C 語言讀取 AmiraMesh 檔案內容,解析 Scalar 與 Vector Fields

AmiraMesh 是 Amira 原生的檔案格式,學術版的 Amira 是由 ZIB 所發展的,商業版的則有 Visage ImagingVSG

AmiraMesh 的檔案內容可以分為檔頭(header)與資料(data)兩部分,檔頭部分都是以 ASCII 的編碼來儲存,其中包含許多的 meta 資訊,而資料的部分則有 ASCII 與二進位(binary)兩種,這裡我們示範讀取二進位資料的方式。


這是一個 AmiraMesh 格式的範例檔案:
# AmiraMesh BINARY-LITTLE-ENDIAN 2.1 1

define Lattice 4 6 8 2

Parameters {
    BoundingBox -1 0 0 1 -0.5 0.5, 3
    CoordType "uniform" 4
}

Lattice { float[2] Data } @1 5

# Data section follows 6
@1 7
[二進位資料]

1 AmiraMesh 檔案檔頭,標示這個檔案的資料格式為 little-endian 的二進位檔案。

2 指定 x, y, z 方向的資料點數。

3 定義 uniform grid 的 bounding box。

4 定義 grid 為 uniform。

5 定義 @1 資料中每一點的資料型態。

6 在資料區塊之前,會有一行提示訊息,實際的資料從這裡開始。

7 在資料區塊中的每一筆資料都會有一個編號,編號 @1 的資料從這裡開始,以下是二進位的資料內容。

這裡的二進位資料是以下面這些規則來儲存的:

  • Little-endian:目前一般 PC 的記憶體都是使用 little-endian 格式在儲存資料的,所以我們可以直接將資料讀進記憶體,不需要另外轉換,如果是 big-endian 的伺服器,就要加上轉換的動作。
  • x-fastest:若要以迴圈的方式存取整個 grid,則 x 軸的迴圈在最內層,z 軸在最外層。
  • Interleaved components:在每一個資料點當中,如果有超過一個以上的數值,則依序存取,例如 (u0, v0, w0), (u0, v0, w0), ...,也就是說不同變量會交錯儲存。

以下是 C 語言的實作。

#include <stdio.h>
#include <string.h>
#include <assert.h>

/** Find a string in the given buffer and return a pointer
    to the contents directly behind the SearchString.
    If not found, return the buffer. A subsequent sscanf()
    will fail then, but at least we return a decent pointer.
*/
const char* FindAndJump(const char* buffer, const char* SearchString)
{
    const char* FoundLoc = strstr(buffer, SearchString);
    if (FoundLoc) return FoundLoc + strlen(SearchString);
    return buffer;
}


/** A simple routine to read an AmiraMesh file
    that defines a scalar/vector field on a uniform grid.
*/
int main()
{
    const char* FileName = "testscalar.am";
    //const char* FileName = "testvector2c.am";
    //const char* FileName = "testvector3c.am";

    FILE* fp = fopen(FileName, "rb");
    if (!fp)
    {
        printf("Could not find %s\n", FileName);
        return 1;
    }

    printf("Reading %s\n", FileName);

    //We read the first 2k bytes into memory to parse the header.
    //The fixed buffer size looks a bit like a hack, and it is one, but it gets the job done.
    char buffer[2048];
    fread(buffer, sizeof(char), 2047, fp);
    buffer[2047] = '\0'; //The following string routines prefer null-terminated strings

    if (!strstr(buffer, "# AmiraMesh BINARY-LITTLE-ENDIAN 2.1"))
    {
        printf("Not a proper AmiraMesh file.\n");
        fclose(fp);
        return 1;
    }

    //Find the Lattice definition, i.e., the dimensions of the uniform grid
    int xDim(0), yDim(0), zDim(0);
    sscanf(FindAndJump(buffer, "define Lattice"), "%d %d %d", &xDim, &yDim, &zDim);
    printf("\tGrid Dimensions: %d %d %d\n", xDim, yDim, zDim);

    //Find the BoundingBox
    float xmin(1.0f), ymin(1.0f), zmin(1.0f);
    float xmax(-1.0f), ymax(-1.0f), zmax(-1.0f);
    sscanf(FindAndJump(buffer, "BoundingBox"), "%g %g %g %g %g %g", &xmin, &xmax, &ymin, &ymax, &zmin, &zmax);
    printf("\tBoundingBox in x-Direction: [%g ... %g]\n", xmin, xmax);
    printf("\tBoundingBox in y-Direction: [%g ... %g]\n", ymin, ymax);
    printf("\tBoundingBox in z-Direction: [%g ... %g]\n", zmin, zmax);

    //Is it a uniform grid? We need this only for the sanity check below.
    const bool bIsUniform = (strstr(buffer, "CoordType \"uniform\"") != NULL);
    printf("\tGridType: %s\n", bIsUniform ? "uniform" : "UNKNOWN");

    //Type of the field: scalar, vector
    int NumComponents(0);
    if (strstr(buffer, "Lattice { float Data }"))
    {
        //Scalar field
        NumComponents = 1;
    }
    else
    {
        //A field with more than one component, i.e., a vector field
        sscanf(FindAndJump(buffer, "Lattice { float["), "%d", &NumComponents);
    }
    printf("\tNumber of Components: %d\n", NumComponents);

    //Sanity check
    if (xDim <= 0 || yDim <= 0 || zDim <= 0
        || xmin > xmax || ymin > ymax || zmin > zmax
        || !bIsUniform || NumComponents <= 0)
    {
        printf("Something went wrong\n");
        fclose(fp);
        return 1;
    }

    //Find the beginning of the data section
    const long idxStartData = strstr(buffer, "# Data section follows") - buffer;
    if (idxStartData > 0)
    {
        //Set the file pointer to the beginning of "# Data section follows"
        fseek(fp, idxStartData, SEEK_SET);
        //Consume this line, which is "# Data section follows"
        fgets(buffer, 2047, fp);
        //Consume the next line, which is "@1"
        fgets(buffer, 2047, fp);

        //Read the data
        // - how much to read
        const size_t NumToRead = xDim * yDim * zDim * NumComponents;
        // - prepare memory; use malloc() if you're using pure C
        float* pData = new float[NumToRead];
        if (pData)
        {
            // - do it
            const size_t ActRead = fread((void*)pData, sizeof(float), NumToRead, fp);
            // - ok?
            if (NumToRead != ActRead)
            {
                printf("Something went wrong while reading the binary data section.\nPremature end of file?\n");
                delete[] pData;
                fclose(fp);
                return 1;
            }

            //Test: Print all data values
            //Note: Data runs x-fastest, i.e., the loop over the x-axis is the innermost
            printf("\nPrinting all values in the same order in which they are in memory:\n");
            int Idx(0);
            for(int k=0;k<zDim;k++)
            {
                for(int j=0;j<yDim;j++)
                {
                    for(int i=0;i<xDim;i++)
                    {
                        //Note: Random access to the value (of the first component) of the grid point (i,j,k):
                        // pData[((k * yDim + j) * xDim + i) * NumComponents]
                        assert(pData[((k * yDim + j) * xDim + i) * NumComponents] == pData[Idx * NumComponents]);

                        for(int c=0;c<NumComponents;c++)
                        {
                            printf("%g ", pData[Idx * NumComponents + c]);
                        }
                        printf("\n");
                        Idx++;
                    }
                }
            }

            delete[] pData;
        }
    }

    fclose(fp);
    return 0;
}

參考資料:weinkauf

G. T. Wang

個人使用 Linux 經驗長達十餘年,樂於分享各種自由軟體技術與實作文章。

Share
Published by
G. T. Wang
標籤: C/C++

Recent Posts

光陽 KYMCO GP 125 機車接電發動、更換電瓶記錄

本篇記錄我的光陽 KYMCO ...

2 年 ago

[開箱] YubiKey 5C NFC 實體金鑰

本篇是 YubiKey 5C ...

3 年 ago

[DIY] 自製竹火把

本篇記錄我拿竹子加上過期的苦茶...

3 年 ago