Click here to Skip to main content
15,892,298 members
Articles / Desktop Programming / WPF

Medical image visualization using WPF

Rate me:
Please Sign up or sign in to vote.
4.96/5 (68 votes)
27 Sep 2012CPOL6 min read 109.6K   7.7K   121  
The article demonstrates the visualization of medical images (DICOM) using WPF.
using System;
using System.Collections.Generic;
using System.Windows.Media.Media3D;

using DICOMViewer.Helper;

namespace DICOMViewer.Volume
{
    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,  Y_Botton_Back ,  Z_Back);
            p[2] = new Point3D( X_Right_Front, Y_Botton_Front , Z_Front);
            p[3] = new Point3D( X_Left_Front,  Y_Botton_Front , Z_Front);
            p[4] = new Point3D( X_Left_Back,   Y_Top_Back ,     Z_Back);
            p[5] = new Point3D( X_Right_Back,  Y_Top_Back ,     Z_Back);
            p[6] = new Point3D( X_Right_Front, Y_Top_Front ,    Z_Front);
            p[7] = new Point3D( X_Left_Front,  Y_Top_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);
        }
    }

    // Implementation of the marching cubes algorithm.
    // For more details, please refer to the article 'Polygonising a scalar field' written by Paul Bourke.
    // http://paulbourke.net/geometry/polygonise/

    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[1], grid.p[2], grid.val[1], grid.val[2]);

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

            if ((EdgeTable.LookupTable[cubeindex] & 8) > 0)
                vertlist[3] = VertexInterp(isolevel, grid.p[3], grid.p[0], 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[5], grid.p[6], grid.val[5], grid.val[6]);

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

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

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

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

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

            if ((EdgeTable.LookupTable[cubeindex] & 2048) > 0)
                vertlist[11] = VertexInterp(isolevel, grid.p[3], grid.p[7], grid.val[3], 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);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Siemens Healthcare
Germany Germany
Currently working as a Requirement Engineer for Siemens Healthcare in Germany.

Comments and Discussions