Example #1
0
        public static WriteableBitmap BuildColorBitmap(CTSliceInfo ct,
                                                       byte[,]     normalizedPixelBuffer)
        {
            byte[] imageDataArray = new byte[ct.RowCount * ct.ColumnCount * 4];

            int i = 0;
            for (int r = 0; r != ct.RowCount; ++r)
            {
                for (int c = 0; c != ct.ColumnCount; ++c)
                {
                    byte aGrayValue = normalizedPixelBuffer[r, c];

                    // Black/White image: all RGB values are set to same value
                    // Alpha value is set to 255
                    imageDataArray[i * 4] = aGrayValue;
                    imageDataArray[i * 4 + 1] = aGrayValue;
                    imageDataArray[i * 4 + 2] = aGrayValue;
                    imageDataArray[i * 4 + 3] = 255;

                    ++i;
                }
            }

            // Allocate the Bitmap
            WriteableBitmap bitmap = new WriteableBitmap(ct.ColumnCount, ct.RowCount, 96, 96, PixelFormats.Pbgra32, null);

            // Write the Pixels
            Int32Rect imageRectangle = new Int32Rect(0, 0, ct.ColumnCount, ct.RowCount);
            int imageStride = ct.ColumnCount * bitmap.Format.BitsPerPixel / 8;
            bitmap.WritePixels(imageRectangle, imageDataArray, imageStride, 0);

            return bitmap;
        }
Example #2
0
        // 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[theRowIndex + 1, theColumnIndex];
            val[1] = CTSliceBack[theRowIndex + 1, theColumnIndex + 1];
            val[2] = CTSliceFront[theRowIndex + 1, theColumnIndex + 1];
            val[3] = CTSliceFront[theRowIndex + 1, theColumnIndex];
            val[4] = CTSliceBack[theRowIndex, theColumnIndex];
            val[5] = CTSliceBack[theRowIndex, theColumnIndex + 1];
            val[6] = CTSliceFront[theRowIndex, theColumnIndex + 1];
            val[7] = CTSliceFront[theRowIndex, theColumnIndex];
        }
Example #3
0
        // Gray Level Thresholding
        public static short GLThresholding(CTSliceInfo ct, int sr, short threshold,
                                           short tissc, short lungc)
        {
            int nr = ct.RowCount;
            int nc = ct.ColumnCount;

            short[,] bm = ct.HounsfieldPixelBuffer;

            // iterative
            Tuple <float, float> q = AverageThreshold(ct, sr, threshold, -900);
            short newthr           = Round(0.5f * (q.Item1 + q.Item2));

            q         = AverageThreshold(ct, sr, newthr, -900);
            threshold = Round(0.5f * (q.Item1 + q.Item2));

            for (int r = 0; r != nr; ++r)
            {
                for (int c = 0; c != nc; ++c)
                {
                    short v = bm[r, c];

                    bm[r, c] = tissc;
                    if (v < threshold)
                    {
                        bm[r, c] = lungc;
                    }
                }
            }

            return(threshold);
        }
Example #4
0
        // 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[theRowIndex + 1, theColumnIndex];
            val[1] = CTSliceBack[theRowIndex + 1, theColumnIndex + 1];
            val[2] = CTSliceFront[theRowIndex + 1, theColumnIndex + 1];
            val[3] = CTSliceFront[theRowIndex + 1, theColumnIndex];
            val[4] = CTSliceBack[theRowIndex, theColumnIndex];
            val[5] = CTSliceBack[theRowIndex, theColumnIndex + 1];
            val[6] = CTSliceFront[theRowIndex, theColumnIndex + 1];
            val[7] = CTSliceFront[theRowIndex, theColumnIndex];
        }
Example #5
0
        public void AddImageSlice(CTSliceInfo ct)
        {
            ImageSlice newImageSlice = new ImageSlice(ct, the3DModel);

            // Insert new Image Slice at the end
            _ImageSliceList.Add(_ImageSliceList.Count, newImageSlice);
        }
Example #6
0
        // Create new ImageSlice
        public ImageSlice(CTSliceInfo ct, ModelVisual3D the3DModel)
        {
            _ct = ct;

            _FileName = ct.FileName;
            _ZValue   = ct.UpperLeft_Z.ToString();

            _3DModel = the3DModel;
        }
Example #7
0
        // Create new ImageSlice
        public ImageSlice(CTSliceInfo ct, ModelVisual3D the3DModel)
        {
            _ct = ct;

            _FileName  = ct.FileName;
            _ZValue    = ct.UpperLeft_Z.ToString();

            _3DModel = the3DModel;
        }
Example #8
0
        // true if value is replaced
        public bool Add(CTSliceInfo ct)
        {
            string fname = ct.FileName;
            int sliceloc = ct.SliceLoc;

            bool rc = _slices_by_locn.ContainsKey(sliceloc);

            _slices_by_name[fname]    = ct;
            _slices_by_locn[sliceloc] = ct;

            return true;
        }
Example #9
0
        private static List <Triangle> ComputeTriangles(CTSliceInfo[] slices, int theIsoValue)
        {
            // 1. Calculate the Center Point
            // =============================
            // For moving the 3D model with the mouse, the implementation is taken from Code Project 'WPF 3D Primer'.
            // See also: 'http://www.codeproject.com/Articles/23332/WPF-3D-Primer#'
            // However, this implementation needs the 3D model to be centered in the origin of the coordinate system.
            // As a consequence, all CT Slices have to be shifted by the Center Point (method 'AdjustPatientPositionToCenterPoint()')
            CTSliceInfo firstCT = slices[0];
            CTSliceInfo lastCT  = slices[slices.Length - 1];

            double Center_X = firstCT.UpperLeft_X + (firstCT.PixelSpacing_X * firstCT.ColumnCount / 2);
            double Center_Y = firstCT.UpperLeft_Y + (firstCT.PixelSpacing_Y * firstCT.RowCount / 2);

            // CT Slices are already sorted ascending in Z direction
            double Center_Z = firstCT.UpperLeft_Z + ((lastCT.UpperLeft_Z - firstCT.UpperLeft_Z) / 2);

            // Create the Center Point
            Point3D aCenterPoint = new Point3D(Center_X, Center_Y, Center_Z);

            // 2. The Marching Cubes algorithm
            // ===============================
            // For each Voxel of the CT Slice, an own GridCell is created.
            // The IsoValue and the x/y/z information for each corner of the GridCell is taken from the direct neighbor voxels (of the same or adjacant CT Slice).
            // Looping has to be done over all CT Slices.

            List <Triangle> triangles = new List <Triangle>();

            CTSliceInfo slice1 = null;
            CTSliceInfo slice2 = slices[0];

            slice2.AdjustPatientPositionToCenterPoint(aCenterPoint);

            for (int sliceIdx = 1; sliceIdx != slices.Length; ++sliceIdx)
            {
                slice1 = slice2;
                slice2 = slices[sliceIdx];
                slice2.AdjustPatientPositionToCenterPoint(aCenterPoint);

                for (int r = 0; r != slice1.RowCount - 1; ++r)
                {
                    for (int c = 0; c != slice1.ColumnCount - 1; ++c)
                    {
                        GridCell aGridCell = new GridCell(sliceIdx, r, c, slice1, slice2);
                        MarchingCubes.Polygonise(aGridCell, theIsoValue, ref triangles);
                    }
                }
            }
            return(triangles);
        }
Example #10
0
        // Creates the Volume View out of a given list of CT Slices for the specified Iso Value.
        public void CreateVolume(CTSliceInfo[] slices, int theIsoValue)
        {
            CTSliceInfo firstCT = slices[0];
            CTSliceInfo lastCT  = slices[slices.Length - 1];

            List <Triangle> triangles = ComputeTriangles(slices, theIsoValue);
            MeshGeometry3D  mesh      = ComputeMesh(triangles);

            // Last step is to give the mesh to the WPF viewport in order to render it.
            mGeometryModel           = new GeometryModel3D(mesh, new DiffuseMaterial(new SolidColorBrush(Colors.Red)));
            mGeometryModel.Transform = new Transform3DGroup();

//            GeometryModel3D qqq = new GeometryModel3D(ComputeMesh(ComputeTriangles(slices, theIsoValue-100)),
//                                                      new DiffuseMaterial(new SolidColorBrush(Colors.Blue)));
//            qqq.Transform = mGeometryModel.Transform;

            Model3DGroup a3DGroup = new Model3DGroup();

            a3DGroup.Children.Add(mGeometryModel);
//            a3DGroup.Children.Add(qqq);
            m3DModel.Content = a3DGroup;

            // Dump some useful size information.
            mInfoLabel.Text  = string.Format("CT Pixel Data\n");
            mInfoLabel.Text += string.Format("{0} x {1} x {2} = {3:### ### ### ###}\n\n", firstCT.RowCount, firstCT.ColumnCount, slices.Length, firstCT.RowCount * firstCT.ColumnCount * slices.Length);
            mInfoLabel.Text += string.Format("Marching Cubes\n");
            mInfoLabel.Text += string.Format("IsoValue: {0}, Triangle Count: {1:### ### ### ###}\n\n", theIsoValue, triangles.Count);
            mInfoLabel.Text += string.Format("Mesh Size\n");
            mInfoLabel.Text += string.Format("Points: {0:### ### ### ###}", triangles.Count * 3);

            // 4. Camera Setup
            // ===============
            // We assume the maximum size of the model in Z direction
            double aEstimatedModelSize = lastCT.UpperLeft_Z - firstCT.UpperLeft_Z;

            // Setup the camera position. A reasonable value for the model/camera distance is choosen.
            // In order to rotate the 3D model via the mouse, the implementation from the Code Project 'WPF 3D Primer' is taken.
            // However, in order to use this proposal out of the box, the camera must be positioned on the Z axis only.
            mViewPortCamera.Position = new Point3D(0, 0, -(aEstimatedModelSize * 3));

            // The 3D model is centered in the origin of the coordinate system.
            // Hence, camera and light direction should point to the origin.
            mViewPortCamera.LookDirection = new Point3D(0, 0, 0) - mViewPortCamera.Position;
            mViewPortLight.Direction      = mViewPortCamera.LookDirection;
        }
Example #11
0
        private void CreateMaskedVolumeView(short theIsoValueInHounsfield, byte[][,] mask, CTSliceInfo[] slices)
        {
            // shall be used only once, because original slice data will be altered
            // require data reloading
            for (int k = 0; k != slices.Length; ++k)
            {
                CTSliceInfo ct = slices[k];
                short[,] buffer = ct.HounsfieldPixelBuffer;
                byte[,] maska   = mask[k];

                for (int r = 0; r != ct.RowCount; ++r)
                {
                    for (int c = 0; c != ct.ColumnCount; ++c)
                    {
                        if (maska[r, c] > 0)
                        {
                            buffer[r, c] = theIsoValueInHounsfield;
                        }
                    }
                }
            }

            if (slices.Length > 2)
            {
                VolumeView aVolumeViewWindow = new VolumeView();

                Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;

                // Create the Volume for the specified IOD list / IsoValue.
                aVolumeViewWindow.CreateVolume(slices, theIsoValueInHounsfield);
                aVolumeViewWindow.Title = string.Format("DICOM Viewer - Volume View (IsoValue = {0} in Hounsfield Units)", theIsoValueInHounsfield.ToString());

                Mouse.OverrideCursor = null;

                aVolumeViewWindow.ShowDialog();
            }
            else
            {
                System.Windows.MessageBox.Show("The series does not have suffcient CT Slices in order to generate a Volume View!");
            }
        }
Example #12
0
        public static Tuple <float, float> AverageThreshold(CTSliceInfo ct, int sr, short threshold,
                                                            short skipthr)
        {
            int nr = ct.RowCount;
            int nc = ct.ColumnCount;

            short[,] bm = ct.HounsfieldPixelBuffer;

            int av_high = 0;
            int av_low  = 0;

            int nof_high = 0;
            int nof_low  = 0;

            for (int r = 0; r != sr; ++r)
            {
                for (int c = 0; c != nc; ++c)
                {
                    short v = bm[r, c];

                    if (v < skipthr)
                    {
                        continue;
                    }

                    if (v > threshold)
                    {
                        av_high += v;
                        ++nof_high;
                        continue;
                    }

                    av_low += v;
                    ++nof_low;
                }
            }

            return(new Tuple <float, float>((float)av_low / (float)nof_low, (float)av_high / (float)nof_high));
        }
Example #13
0
        public static short[,] Apply(CTSliceInfo ct, GaussBlur gb)
        {
            short[,] hmap = ct.HounsfieldPixelBuffer;

            int nr = ct.RowCount;
            int nc = ct.ColumnCount;

            int br = gb.br;
            int bc = gb.bc;

            float[,] blur = gb.blur;

            short[,] bm = new short[nr, nc];
            int l = bm.Length;
            Array.Clear(bm, 0, bm.Length);

            for (int r = br; r != nr-br; ++r)
            {
                for (int c = bc; c != nc-bc; ++c)
                {
                    float s = 0.0f;

                    for (int ir = -br; ir <= br; ++ir)
                    {
                        int sr = r + ir;
                        for (int ic = -bc; ic <= bc; ++ic)
                        {
                            int sc = c + ic;

                            s += (float)hmap[sr, sc] * blur[ir + br, ic + bc];
                        }
                    }

                    bm[r, c] = (short)(s + 0.5f);
                }
            }

            return bm;
        }
Example #14
0
        public static WriteableBitmap BuildGrey8Bitmap(CTSliceInfo ct,
                                                       byte[,]     normalizedPixelBuffer)
        {
            byte[] imageDataArray = new byte[ct.RowCount * ct.ColumnCount * 1];

            int i = 0;
            for (int r = 0; r != ct.RowCount; ++r)
            {
                for (int c = 0; c != ct.ColumnCount; ++c)
                {
                    byte grayValue = normalizedPixelBuffer[r, c];

                    imageDataArray[i] = grayValue;

                    ++i;
                }
            }

            // Allocate the Bitmap
            WriteableBitmap bitmap = new WriteableBitmap(ct.ColumnCount, ct.RowCount, 96, 96, PixelFormats.Gray8, null);

            // Write the Pixels
            Int32Rect imageRectangle = new Int32Rect(0, 0, ct.ColumnCount, ct.RowCount);
            int imageStride = ct.ColumnCount * bitmap.Format.BitsPerPixel / 8;
            bitmap.WritePixels(imageRectangle, imageDataArray, imageStride, 0);

            return bitmap;
        }
Example #15
0
        private void ButtonLungs_Click(object sender, RoutedEventArgs e)
        {
            bool rc = _scol.BuildSortedSlicesArray();

            if (!rc)
            {
                System.Windows.MessageBox.Show("There are skips in CTs!");
                return;
            }

            CTSliceInfo[] slices = _scol.Slices;

            // Step 1: find the couch
            int sr = SelectStartSlice(slices);

            CTSliceInfo ct = slices[sr];

            int sc       = Couch.DetectCouchInOneSlice(ct.HounsfieldPixelBuffer, ct.RowCount, ct.ColumnCount);
            int scBefore = Couch.DetectCouchInOneSlice(slices[sr + 10].HounsfieldPixelBuffer, slices[sr + 10].RowCount, slices[sr + 10].ColumnCount);
            int scAfter  = Couch.DetectCouchInOneSlice(slices[sr - 10].HounsfieldPixelBuffer, slices[sr - 10].RowCount, slices[sr - 10].ColumnCount);

            sc = Math.Max(Math.Max(sc, scBefore), scAfter);

            // Step 2: Gaussian blur

            GaussBlur gb = new GaussBlur((float)ct.PixelSpacing_X, (float)ct.PixelSpacing_X, 5);

            for (int k = 0; k != slices.Length; ++k)
            {
                ct          = slices[k];
                short[,] bm = CTSliceHelpers.Apply(ct, gb);
                ct.HounsfieldPixelBuffer = bm;
            }

            // Step 3: clear below the couch
            for (int k = 0; k != slices.Length; ++k)
            {
                ct = slices[k];

                short[,] hb = ct.HounsfieldPixelBuffer;

                for (int r = ct.RowCount - 1; r > sc; --r)
                {
                    for (int c = 0; c != ct.ColumnCount; ++c)
                    {
                        hb[r, c] = -1024;
                    }
                }
            }

            // Step 4: gray level thresholding
            for (int k = 0; k != slices.Length; ++k)
            {
                ct = slices[k];

                Couch.GLThresholding(ct, sc, -499, 0, -499);
            }

            // Step 5: Flool fill
            for (int k = 0; k != slices.Length; ++k)
            {
                ct = slices[k];

                short[,] ret = Couch.FloodFill(ct.HounsfieldPixelBuffer, ct.RowCount, ct.ColumnCount,
                                               3, 3, -499, 0);
                ct.HounsfieldPixelBuffer = ret;
            }

            // Step 6: Contours via Moore Neighbour
            for (int k = slices.Length - 1; k >= slices.Length - 2; --k)
            {
                ct = slices[k];
                int nr = ct.RowCount;
                int nc = ct.ColumnCount;

                int    z  = ct.SliceLoc;
                double zz = ct.UpperLeft_Z;

                short[,] bm = ct.HounsfieldPixelBuffer;

                bool[,] image = new bool[nr, nc];

                for (int r = 0; r != nr; ++r)
                {
                    for (int c = 0; c != nc; ++c)
                    {
                        image[r, c] = false;
                        if (bm[r, c] < 0)
                        {
                            image[r, c] = true;
                        }
                    }
                }

                System.Drawing.Point[] contour = MooreContour.Trace(image, nr, nc);


                foreach (var pt in contour)
                {
                    int r = pt.Y;
                    int c = pt.X;

                    bm[r, c] = 500;
                }
            }
        }
Example #16
0
        // Helper method to handle the selection change event of the IOD Tree.
        // a) In case the selected tree node represents only group information (Patient, SOPClass, Study, Series), the detailed view is cleared.
        // b) In case the selected tree node represents an IOD, the DICOM Metainformation is displayed in the DICOM Tag Tree.
        // c) In case the selected tree node represents a CT Slice, in addition to the DICOM Metainformation,
        //    the ImageFlow button, the volume buttons and the bitmap is shown.
        private void mIODTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs <object> e)
        {
            TreeViewItem aSelectedNode = _IODTree.SelectedItem as TreeViewItem;

            if (aSelectedNode == null)
            {
                return;
            }

            // Clear old content
            _DICOMTagTree.Items.Clear();
            _Grid.RowDefinitions.First().Height = new GridLength(0);
            _Grid.RowDefinitions.Last().Height  = new GridLength(0);

            IOD anIOD = aSelectedNode.Tag as IOD;

            if (anIOD == null)
            {
                return;
            }

            // Set the FileName as root node

            string aFileName = Path.GetFileName(anIOD.FileName);

            TreeViewItem rootNode = new TreeViewItem()
            {
                Header = string.Format("File: {0}", aFileName)
            };

            _DICOMTagTree.Items.Add(rootNode);

            // Expand the root node
            rootNode.IsExpanded = true;

            // Add all DICOM attributes to the tree
            foreach (XElement xe in anIOD.XDocument.Descendants("DataSet").First().Elements("DataElement"))
            {
                AddDICOMAttributeToTree(rootNode, xe);
            }

            // In case the IOD does have a processable pixel data, the ImageFlow button, the volume buttons and the bitmap is shown.
            // Otherwise, only the DICOM attributes are shown and the first and last grid row is hided.
            if (anIOD.IsPixelDataProcessable())
            {
                CTSliceInfo ct = _scol.Retrieve(anIOD.FileName);
                if (ct == null)
                {
                    ct = new Helper.CTSliceInfo(anIOD.XDocument, anIOD.FileName);
                    _scol.Add(ct);
                }
                _Grid.RowDefinitions.First().Height = new GridLength(30);
                _Grid.RowDefinitions.Last().Height  = new GridLength(ct.RowCount + 16);

                _Image.Source = CTSliceHelpers.GetPixelBufferAsBitmap(ct);
                _curCT        = ct;
            }
            else
            {
                _Grid.RowDefinitions.First().Height = new GridLength(0);
                _Grid.RowDefinitions.Last().Height  = new GridLength(0);

                _curCT = null;
            }
        }
Example #17
0
        private static List<Triangle> ComputeTriangles(CTSliceInfo[] slices, int theIsoValue)
        {
            // 1. Calculate the Center Point
            // =============================
            // For moving the 3D model with the mouse, the implementation is taken from Code Project 'WPF 3D Primer'.
            // See also: 'http://www.codeproject.com/Articles/23332/WPF-3D-Primer#'
            // However, this implementation needs the 3D model to be centered in the origin of the coordinate system.
            // As a consequence, all CT Slices have to be shifted by the Center Point (method 'AdjustPatientPositionToCenterPoint()')
            CTSliceInfo firstCT = slices[0];
            CTSliceInfo lastCT  = slices[slices.Length - 1];

            double Center_X = firstCT.UpperLeft_X + (firstCT.PixelSpacing_X * firstCT.ColumnCount / 2);
            double Center_Y = firstCT.UpperLeft_Y + (firstCT.PixelSpacing_Y * firstCT.RowCount / 2);

            // CT Slices are already sorted ascending in Z direction
            double Center_Z = firstCT.UpperLeft_Z + ((lastCT.UpperLeft_Z - firstCT.UpperLeft_Z) / 2);

            // Create the Center Point
            Point3D aCenterPoint = new Point3D(Center_X, Center_Y, Center_Z);

            // 2. The Marching Cubes algorithm
            // ===============================
            // For each Voxel of the CT Slice, an own GridCell is created.
            // The IsoValue and the x/y/z information for each corner of the GridCell is taken from the direct neighbor voxels (of the same or adjacant CT Slice).
            // Looping has to be done over all CT Slices.

            List<Triangle> triangles = new List<Triangle>();

            CTSliceInfo slice1 = null;
            CTSliceInfo slice2 = slices[0];

            slice2.AdjustPatientPositionToCenterPoint(aCenterPoint);

            for (int sliceIdx = 1; sliceIdx != slices.Length; ++sliceIdx)
            {
                slice1 = slice2;
                slice2 = slices[sliceIdx];
                slice2.AdjustPatientPositionToCenterPoint(aCenterPoint);

                for (int r = 0; r != slice1.RowCount - 1; ++r)
                {
                    for (int c = 0; c != slice1.ColumnCount - 1; ++c)
                    {
                        GridCell aGridCell = new GridCell(sliceIdx, r, c, slice1, slice2);
                        MarchingCubes.Polygonise(aGridCell, theIsoValue, ref triangles);
                    }
                }
            }
            return triangles;
        }
Example #18
0
        // Creates the Volume View out of a given list of CT Slices for the specified Iso Value.
        public void CreateVolume(CTSliceInfo[] slices, int theIsoValue)
        {
            CTSliceInfo firstCT = slices[0];
            CTSliceInfo lastCT  = slices[slices.Length - 1];

            List<Triangle> triangles = ComputeTriangles(slices, theIsoValue);
            MeshGeometry3D mesh      = ComputeMesh(triangles);

            // Last step is to give the mesh to the WPF viewport in order to render it.
            mGeometryModel = new GeometryModel3D(mesh, new DiffuseMaterial(new SolidColorBrush(Colors.Red)));
            mGeometryModel.Transform = new Transform3DGroup();

            //            GeometryModel3D qqq = new GeometryModel3D(ComputeMesh(ComputeTriangles(slices, theIsoValue-100)),
            //                                                      new DiffuseMaterial(new SolidColorBrush(Colors.Blue)));
            //            qqq.Transform = mGeometryModel.Transform;

            Model3DGroup a3DGroup = new Model3DGroup();
            a3DGroup.Children.Add(mGeometryModel);
            //            a3DGroup.Children.Add(qqq);
            m3DModel.Content = a3DGroup;

            // Dump some useful size information.
            mInfoLabel.Text =  string.Format("CT Pixel Data\n");
            mInfoLabel.Text += string.Format("{0} x {1} x {2} = {3:### ### ### ###}\n\n", firstCT.RowCount, firstCT.ColumnCount, slices.Length, firstCT.RowCount * firstCT.ColumnCount * slices.Length);
            mInfoLabel.Text += string.Format("Marching Cubes\n");
            mInfoLabel.Text += string.Format("IsoValue: {0}, Triangle Count: {1:### ### ### ###}\n\n", theIsoValue, triangles.Count);
            mInfoLabel.Text += string.Format("Mesh Size\n");
            mInfoLabel.Text += string.Format("Points: {0:### ### ### ###}", triangles.Count * 3);

            // 4. Camera Setup
            // ===============
            // We assume the maximum size of the model in Z direction
            double aEstimatedModelSize = lastCT.UpperLeft_Z - firstCT.UpperLeft_Z;

            // Setup the camera position. A reasonable value for the model/camera distance is choosen.
            // In order to rotate the 3D model via the mouse, the implementation from the Code Project 'WPF 3D Primer' is taken.
            // However, in order to use this proposal out of the box, the camera must be positioned on the Z axis only.
            mViewPortCamera.Position = new Point3D(0, 0, -(aEstimatedModelSize * 3));

            // The 3D model is centered in the origin of the coordinate system.
            // Hence, camera and light direction should point to the origin.
            mViewPortCamera.LookDirection = new Point3D(0, 0, 0) - mViewPortCamera.Position;
            mViewPortLight.Direction = mViewPortCamera.LookDirection;
        }
Example #19
0
        // build bitmap directly from Hounsfield map, shifted by 1024
        public static WriteableBitmap BuildGrey16Bitmap(CTSliceInfo ct,
                                                        byte[,]     normalizedPixelBuffer)
        {
            byte[] imageDataArray = new byte[ct.RowCount * ct.ColumnCount * 2];

            int i = 0;
            for (int r = 0; r != ct.RowCount; ++r)
            {
                for (int c = 0; c != ct.ColumnCount; ++c)
                {
                    ushort grayValue = (ushort)(ct[r, c] + 1024);

                    imageDataArray[i * 2 + 0] = (byte)((grayValue >> 8) & 0x00FF);
                    imageDataArray[i * 2 + 1] = (byte)(grayValue & 0x00FF);

                    ++i;
                }
            }

            // Allocate the Bitmap
            WriteableBitmap bitmap = new WriteableBitmap(ct.ColumnCount, ct.RowCount, 96, 96, PixelFormats.Gray16, null);

            // Write the Pixels
            Int32Rect imageRectangle = new Int32Rect(0, 0, ct.ColumnCount, ct.RowCount);
            int imageStride = ct.ColumnCount * bitmap.Format.BitsPerPixel / 8;
            bitmap.WritePixels(imageRectangle, imageDataArray, imageStride, 0);

            return bitmap;
        }
Example #20
0
        // Helper method, which returns the pixel data of a CT slice as gray-scale bitmap.
        public static WriteableBitmap GetPixelBufferAsBitmap(CTSliceInfo ct)
        {
            int windowLeftBorder = ct.WindowCenter - (ct.WindowWidth / 2);

            /*
            GaussBlur gb = new GaussBlur(1.0f, (float)ct.PixelSpacing_X, 5);

            short[,] bm = Apply(ct, gb);

            ct.HounsfieldPixelBuffer = bm;
            */

            byte[,] normalizedPixelBuffer = BuildNormalizedPixelBuffer(ct, ct.WindowCenter, ct.WindowWidth, windowLeftBorder);

            WriteableBitmap bitmap = BuildGrey16Bitmap(ct, normalizedPixelBuffer);

            return bitmap;
        }
Example #21
0
        public static byte[,] BuildNormalizedPixelBuffer(CTSliceInfo ct,
                                                         int windowCenter,
                                                         int windowWidth,
                                                         int windowLeftBorder)
        {
            Debug.Assert(windowWidth > 0);

            byte[,] normalizedPixelBuffer = new byte[ct.RowCount, ct.ColumnCount];

            // Normalize the Pixel value to [0,255]
            for (int r = 0; r != ct.RowCount; ++r)
            {
                for (int c = 0; c != ct.ColumnCount; ++c)
                {
                    short aPixelValue = ct[r, c];
                    int aPixelValueNormalized = (255 * (aPixelValue - windowLeftBorder)) / windowWidth;

                    if (aPixelValueNormalized <= 0)
                        normalizedPixelBuffer[r, c] = 0;
                    else
                        if (aPixelValueNormalized >= 255)
                            normalizedPixelBuffer[r, c] = 255;
                    else
                        normalizedPixelBuffer[r, c] = Convert.ToByte(aPixelValueNormalized);
                }
            }
            return normalizedPixelBuffer;
        }