如何使用 C# 中的行进立方体算法从 CT 切片 (.raw) 生成 3D 网格?

问题描述

我正在尝试使用行进立方体算法从 CT 切片(.raw 文件生成 3D 网格(等值面提取)。 RAW 数据为 8 位 512x512 像素和 207 个切片。 所以输入是 CT 原始数据,输出是 CT 对象的 3D 网格 你能帮我吗?因为我被困在这个超过 1 周 你能给我编程工作流程来实现吗?

接下来是我从 http://paulbourke.net/geometry/polygonise/ 转换而来的 C# 类

class TriTable
{
    public static int[,] LookupTable = new int[256,16] 
    {
        {-1,-1,-1},{0,8,3,1,9,{1,2,10,{9,{2,{3,11,{4,7,4,{8,{11,5,{5,{10,{7,6,{6,{-1,-1}
    };
}

}

public class Triangle
{
    public Point3D[] p = new Point3D[3];
}

public class GridCell
{
    public Point3D[] p = new Point3D[8];
    public Int32[] val = new Int32[8];

    // Creates a new GridCell for adjacent CT Slices.
    // For the index convention,please refer to the article 'polygonising a scalar field' written by Paul Bourke.
    // http://paulbourke.net/geometry/polygonise/

    public GridCell(int theSliceIndex,int theRowIndex,int theColumnIndex,CTSliceInfo CTSliceFront,CTSliceInfo CTSliceBack)
    {
        double X_Left_Front = CTSliceFront.UpperLeft_X + (theColumnIndex * CTSliceFront.PixelSpacing_X);
        double X_Right_Front = X_Left_Front + CTSliceFront.PixelSpacing_X;

        double X_Left_Back = CTSliceBack.UpperLeft_X + (theColumnIndex * CTSliceBack.PixelSpacing_X);
        double X_Right_Back = X_Left_Back + CTSliceBack.PixelSpacing_X;

        double Y_Top_Front = CTSliceFront.UpperLeft_Y + (theRowIndex * CTSliceFront.PixelSpacing_Y);
        double Y_Botton_Front = Y_Top_Front + CTSliceFront.PixelSpacing_Y;

        double Y_Top_Back = CTSliceBack.UpperLeft_Y + (theRowIndex * CTSliceBack.PixelSpacing_Y);
        double Y_Botton_Back = Y_Top_Back + CTSliceBack.PixelSpacing_Y;

        double Z_Front = CTSliceFront.UpperLeft_Z;
        double Z_Back = CTSliceBack.UpperLeft_Z;

        p[0] = new Point3D( X_Left_Back,Y_Botton_Back,Z_Back);
        p[1] = new Point3D( X_Right_Back,Z_Back);
        p[2] = new Point3D( X_Right_Front,Y_Botton_Front,Z_Front);
        p[3] = new Point3D( X_Left_Front,Z_Front);
        p[4] = new Point3D( X_Left_Back,Y_Top_Back,Z_Back);
        p[5] = new Point3D( X_Right_Back,Z_Back);
        p[6] = new Point3D( X_Right_Front,Y_Top_Front,Z_Front);
        p[7] = new Point3D( X_Left_Front,Z_Front);

        val[0] = CTSliceBack.GetHounsfieldPixelValue(theRowIndex + 1,theColumnIndex);
        val[1] = CTSliceBack.GetHounsfieldPixelValue(theRowIndex + 1,theColumnIndex + 1);
        val[2] = CTSliceFront.GetHounsfieldPixelValue(theRowIndex + 1,theColumnIndex + 1);
        val[3] = CTSliceFront.GetHounsfieldPixelValue(theRowIndex + 1,theColumnIndex);
        val[4] = CTSliceBack.GetHounsfieldPixelValue(theRowIndex,theColumnIndex);
        val[5] = CTSliceBack.GetHounsfieldPixelValue(theRowIndex,theColumnIndex + 1);
        val[6] = CTSliceFront.GetHounsfieldPixelValue(theRowIndex,theColumnIndex + 1);
        val[7] = CTSliceFront.GetHounsfieldPixelValue(theRowIndex,theColumnIndex);
    }
}



class marchingCubes
{
    // Given a grid cell and an isolevel,calculate the triangular facets required to represent the isosurface through the cell.
    // Return the number of triangular facets,the array "triangles" will be loaded up with the vertices at most 5 triangular facets.
    // 0 will be returned if the grid cell is either totally above of totally below the isolevel.
    public static void polygonise(GridCell grid,double isolevel,ref List<Triangle> theTriangleList)
    {
        // Determine the index into the edge table which tells us which vertices are inside of the surface
        int cubeindex = 0;
        if (grid.val[0] < isolevel) cubeindex |= 1;
        if (grid.val[1] < isolevel) cubeindex |= 2;
        if (grid.val[2] < isolevel) cubeindex |= 4;
        if (grid.val[3] < isolevel) cubeindex |= 8;
        if (grid.val[4] < isolevel) cubeindex |= 16;
        if (grid.val[5] < isolevel) cubeindex |= 32;
        if (grid.val[6] < isolevel) cubeindex |= 64;
        if (grid.val[7] < isolevel) cubeindex |= 128;

        // Cube is entirely in/out of the surface 
        if (EdgeTable.LookupTable[cubeindex] == 0)
            return;

        Point3D[] vertlist = new Point3D[12];

        // Find the vertices where the surface intersects the cube 
        if ((EdgeTable.LookupTable[cubeindex] & 1) > 0)
            vertlist[0] = VertexInterp(isolevel,grid.p[0],grid.p[1],grid.val[0],grid.val[1]);

        if ((EdgeTable.LookupTable[cubeindex] & 2) > 0)
            vertlist[1] = VertexInterp(isolevel,grid.p[2],grid.val[1],grid.val[2]);

        if ((EdgeTable.LookupTable[cubeindex] & 4) > 0)
            vertlist[2] = VertexInterp(isolevel,grid.p[3],grid.val[2],grid.val[3]);

        if ((EdgeTable.LookupTable[cubeindex] & 8) > 0)
            vertlist[3] = VertexInterp(isolevel,grid.val[3],grid.val[0]);

        if ((EdgeTable.LookupTable[cubeindex] & 16) > 0)
            vertlist[4] = VertexInterp(isolevel,grid.p[4],grid.p[5],grid.val[4],grid.val[5]);

        if ((EdgeTable.LookupTable[cubeindex] & 32) > 0)
            vertlist[5] = VertexInterp(isolevel,grid.p[6],grid.val[5],grid.val[6]);

        if ((EdgeTable.LookupTable[cubeindex] & 64) > 0)
            vertlist[6] = VertexInterp(isolevel,grid.p[7],grid.val[6],grid.val[7]);

        if ((EdgeTable.LookupTable[cubeindex] & 128) > 0)
            vertlist[7] = VertexInterp(isolevel,grid.val[7],grid.val[4]);

        if ((EdgeTable.LookupTable[cubeindex] & 256) > 0)
            vertlist[8] = VertexInterp(isolevel,grid.val[4]);

        if ((EdgeTable.LookupTable[cubeindex] & 512) > 0)
            vertlist[9] = VertexInterp(isolevel,grid.val[5]);

        if ((EdgeTable.LookupTable[cubeindex] & 1024) > 0)
            vertlist[10] = VertexInterp(isolevel,grid.val[6]);

        if ((EdgeTable.LookupTable[cubeindex] & 2048) > 0)
            vertlist[11] = VertexInterp(isolevel,grid.val[7]);

        // Create the triangle 
        for (int i = 0; TriTable.LookupTable[cubeindex,i] != -1; i += 3)
        {
            Triangle aTriangle = new Triangle();

            aTriangle.p[0] = vertlist[TriTable.LookupTable[cubeindex,i]];
            aTriangle.p[1] = vertlist[TriTable.LookupTable[cubeindex,i + 1]];
            aTriangle.p[2] = vertlist[TriTable.LookupTable[cubeindex,i + 2]];

            theTriangleList.Add(aTriangle);
        }
    }

    public static Point3D VertexInterp(double isolevel,Point3D p1,Point3D p2,double valp1,double valp2)
    {
        double mu;
        Point3D p = new Point3D();

        if (Math.Abs(isolevel-valp1) < 0.00001)
            return(p1);

        if (Math.Abs(isolevel-valp2) < 0.00001)
            return(p2);

        if (Math.Abs(valp1-valp2) < 0.00001)
            return(p1);

        mu = (isolevel - valp1) / (valp2 - valp1);
        
        p.X = p1.X + mu * (p2.X - p1.X);
        p.Y = p1.Y + mu * (p2.Y - p1.Y);
        p.Z = p1.Z + mu * (p2.Z - p1.Z);

        return(p);
    }
}

}

    private void displayImage08(string fileName)
    {
        // Open a binary reader to read in the pixel data. 
        // We cannot use the usual image loading mechanisms since this   is raw 
                
        try
        {
            BinaryReader br = new BinaryReader(File.Open(fileName,FileMode.Open));
            byte pixByte;
            int iTotalSize = (int)br.BaseStream.Length;

            canvas.Width = width;
            canvas.Height = height;
            img.Width = width;
            img.Height = height;
            pix08 = new byte[iTotalSize];

            for (int i = 0; i < iTotalSize; ++i)
            {
                pixByte = (byte)(br.ReadByte());
                pix08[i] = pixByte;
            }
            br.Close();

            int bitsPerPixel = 8;
            stride = (width * bitsPerPixel) / 8;

            // Schleife über das komplette Array mit 207 Schichten
            var singleImage = new byte[512 * 512];
            
            var counter = 0;
            var singleImageIndex = 0;
            for ( int i = 0; i < iTotalSize; i++)
            {
                if(singleImageIndex == 512*512 - 1)
                {
                    // bitmap erstellen nachdem eine Schicht gelesen ist
                    arrayBitmap[counter] = BitmapSource.Create(width,height,96,PixelFormats.Gray8,null,singleImage,stride);
                    counter++;
                    singleImageIndex = 0;
                }

                singleImage[singleImageIndex] = pix08[i];
                singleImageIndex++;
            }

            // Beispiel code um die Schicht 111 anzuzeigen
            img.source = arrayBitmap[127];  
        }
        catch (Exception e)
        {
            MessageBox.Show(e.Message,"Error");
        }
    }

问题现在是,如何处理输入的 CT 数据(.raw 文件)?? 我不知道如何准备数据,我可以将行进立方体算法应用于它。这是我第一次使用这样的数据类型。 非常感谢您的帮助!

解决方法

这段代码一次读取一行数据。该列表包含一组 2D 字节数组。每个数组存储 512 x 512 个 8 位值。

private static List<byte[,]> GetCTSlices(string path)
{
    const int size = 512;
    List<byte[,]> data = new List<byte[,]>();

    try
    {
        using (FileStream fsSource = new FileStream(path,FileMode.Open,FileAccess.Read))
        {

            byte[] bytes = new byte[fsSource.Length];

            int n = fsSource.Read(bytes,(int)fsSource.Length);

            data = ConvertToByteArray2D(bytes,size);
        }
    }
    catch (FileNotFoundException ioEx)
    {
        Console.WriteLine(ioEx.Message);
    }
    return data;
}

private static List<byte[,]> ConvertToByteArray2D(byte[] bytes,int size)
{
    var data = new List<byte[,]>();
    int imageSize = size * size;


    int s = 0;
    while (bytes.Length - s*imageSize > 0)
    {
        var byteArray2D = new byte[size,size];

        for (int i = s*imageSize; i < (s+1)*imageSize; i++)
        {
            int row = (i % imageSize) / size;
            int col = (i % imageSize ) - row * size;
            byteArray2D[row,col] = bytes[i];
        }
        s++;
        data.Add(byteArray2D);
    }
    return data;
}