Exemple #1
0
        static void Main(string[] args)
        {
            try {
                if (args.Length < 2)
                {
                    Console.WriteLine("Usage: DicomSeriesReader <input_directory> <output_file>");
                    return;
                }

                Console.WriteLine("Reading Dicom directory: " + args[0]);
                ImageSeriesReader reader = new ImageSeriesReader();

                VectorString dicom_names = ImageSeriesReader.GetGDCMSeriesFileNames(args[0]);
                reader.SetFileNames(dicom_names);

                Image image = reader.Execute();

                VectorUInt32 size = image.GetSize();
                Console.WriteLine("Image size: " + size[0] + " " + size[1] + " " + size[2]);

                Console.WriteLine("Writing image: " + args[1]);
                ImageFileWriter writer = new ImageFileWriter();
                writer.SetFileName(args[1]);
                writer.Execute(image);

                if (Environment.GetEnvironmentVariable("SITK_NOSHOW") == null)
                {
                    SimpleITK.Show(image, "Dicom Series");
                }
            } catch (Exception ex) {
                Console.WriteLine("Usage: DicomSeriesReader <input_directory> <output_file>");
                Console.WriteLine(ex);
            }
        }
Exemple #2
0
    /*! Load the entire series (i.e. the entire volume).
     * Unlike loadImageData(), this function does not create a colors array or texture. To
     * access volume data, simply get the image (DICOM.image) and read pixel values from it. */
    private void loadVolumeData()
    {
        // Get all file names for the series:
        VectorString fileNames = seriesInfo.filenames;

        // Create a reader which will read the whole series:
        ImageSeriesReader reader = new ImageSeriesReader();

        reader.SetFileNames(fileNames);
        // Load the entire image into a series:
        Image image = reader.Execute();

        // Make the loaded image accessable from elsewhere:
        this.image = image;
    }
        /// <inheritdoc/>
        public ImageReaderBase CreateImageReader(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentNullException(nameof(path));
            }

            var fs = fileSystemStrategy.Create(path);

            if (fs == null)
            {
                throw new UnexpectedNullException("Filesystem could not be created based on the provided path.");
            }

            if (fs.IsDirectory(path))
            {
                var entryPath = DiscoverEntryPath(fs.Directory.GetFiles(path));
                if (string.IsNullOrWhiteSpace(entryPath))
                {
                    // By default try to read DICOM image series
                    // https://simpleitk.readthedocs.io/en/master/Examples/DicomSeriesReader/Documentation.html
                    ImageSeriesReader seriesReader = new ImageSeriesReader();
                    seriesReader.SetFileNames(ImageSeriesReader.GetGDCMSeriesFileNames(path));
                    return(seriesReader);
                }
                else
                {
                    // Try to read the image based on the entry path
                    ImageFileReader reader = new ImageFileReader();
                    reader.SetFileName(entryPath);
                    return(reader);
                }
            }
            else
            {
                ImageFileReader reader = new ImageFileReader();
                reader.SetFileName(path);
                return(reader);
            }
        }
    /*! Searches the directoryToLoad for DICOMs. Called in thread!*/
    public void parseDirectory(object sender, DoWorkEventArgs e)
    {
        // Parse the directory and return the seriesUIDs of all the DICOM series:
        try {
            availableSeries.Clear();

            Debug.Log("[DICOM] Searching directory: " + directoryToLoad);
            VectorString series = ImageSeriesReader.GetGDCMSeriesIDs(directoryToLoad);
            if (series.Count > 0)
            {
                foreach (string s in series)
                {
                    DICOMSeries info = new DICOMSeries(directoryToLoad, s);
                    availableSeries.Add(info);
                }
            }

            // Debug log:
            string str = "[DICOM] Found " + series.Count + " series.";
            Debug.Log(str);
        } catch (System.Exception err) {
            Debug.LogError("Error while trying to parse DICOM directory: " + err.Message);
        }
    }
Exemple #5
0
    /*! Load the entire series (i.e. the entire volume).
     * Unlike loadImageData(), this function does not create a colors array or texture. To
     * access volume data, simply get the image (DICOM.image) and read pixel values from it. */
    private void loadVolumeData()
    {
        // Get all file names for the series:
        VectorString fileNames = seriesInfo.filenames;

        // Create a reader which will read the whole series:
        ImageSeriesReader reader = new ImageSeriesReader();

        reader.SetFileNames(fileNames);
        // Load the entire image into a series:
        Image image = reader.Execute();

        origTexWidth     = (int)image.GetWidth();
        origTexHeight    = (int)image.GetHeight();
        origTexDepth     = (int)image.GetDepth();
        texWidth         = Mathf.NextPowerOfTwo((int)image.GetWidth());
        texHeight        = Mathf.NextPowerOfTwo((int)image.GetHeight());
        texDepth         = Mathf.NextPowerOfTwo((int)image.GetDepth());
        texPaddingWidth  = texWidth - origTexWidth;
        texPaddingHeight = texHeight - origTexHeight;
        texPaddingDepth  = texDepth - origTexDepth;
        Debug.Log("Original texture dimensions: " + origTexWidth + " " + origTexHeight + " " + origTexDepth);
        Debug.Log("Texture dimensions: " + texWidth + " " + texHeight + " " + texDepth);
        colors = new Color32[texWidth * texHeight * texDepth];

        int intercept = 0;
        int slope     = 1;

        try {
            intercept = Int32.Parse(image.GetMetaData("0028|1052"));
            slope     = Int32.Parse(image.GetMetaData("0028|1053"));
        } catch {
        }
        Debug.Log("Slope: " + slope + " Intercept: " + intercept);

        if (image.GetDimension() != 3)
        {
            throw(new System.Exception("Cannot load volume: Image needs to be 3D. Dimensions of image: " + image.GetDimension()));
        }

        histogram = new Histogram();

        UInt32 min = UInt32.MaxValue;
        UInt32 max = UInt32.MinValue;

        Debug.Log("Pixel format: " + image.GetPixelID());

        // Copy the image into a colors array:
        if (image.GetPixelID() == PixelIDValueEnum.sitkUInt16)
        {
            IntPtr bufferPtr = image.GetBufferAsUInt16();

            unsafe {
                UInt16 *ptr = (UInt16 *)bufferPtr.ToPointer();

                int consecutiveIndex = 0;
                for (UInt32 z = 0; z < texDepth; z++)
                {
                    for (UInt32 y = 0; y < texHeight; y++)
                    {
                        for (UInt32 x = 0; x < texWidth; x++)
                        {
                            if (x < origTexWidth && y < origTexHeight && z < origTexDepth)
                            {
                                long jumpingIndex = x + y * texWidth + z * texWidth * texHeight;

                                UInt32 pixelValue = (UInt32)((UInt16)ptr [consecutiveIndex]);
                                colors [jumpingIndex] = F2C(pixelValue);

                                if (pixelValue > max)
                                {
                                    max = pixelValue;
                                }
                                if (pixelValue < min)
                                {
                                    min = pixelValue;
                                }

                                histogram.addValue(pixelValue);

                                consecutiveIndex++;
                            }
                        }
                    }
                }
            }
        }
        else if (image.GetPixelID() == PixelIDValueEnum.sitkInt16)
        {
            IntPtr bufferPtr = image.GetBufferAsInt16();

            unsafe {
                Int16 *ptr = (Int16 *)bufferPtr.ToPointer();

                int consecutiveIndex = 0;
                for (UInt32 z = 0; z < texDepth; z++)
                {
                    for (UInt32 y = 0; y < texHeight; y++)
                    {
                        for (UInt32 x = 0; x < texWidth; x++)
                        {
                            if (x < origTexWidth && y < origTexHeight && z < origTexDepth)
                            {
                                long jumpingIndex = x + y * texWidth + z * texWidth * texHeight;

                                UInt32 pixelValue = (UInt32)((Int16)ptr[consecutiveIndex] + Int16.MaxValue);
                                colors [jumpingIndex] = F2C(pixelValue);

                                if (pixelValue > max)
                                {
                                    max = pixelValue;
                                }
                                if (pixelValue < min)
                                {
                                    min = pixelValue;
                                }

                                histogram.addValue(pixelValue);

                                consecutiveIndex++;
                            }
                        }
                    }
                }
            }
        }
        else if (image.GetPixelID() == PixelIDValueEnum.sitkInt32)
        {
            IntPtr bufferPtr = image.GetBufferAsInt32();

            unsafe {
                Int32 *ptr = (Int32 *)bufferPtr.ToPointer();

                int consecutiveIndex = 0;

                for (UInt32 z = 0; z < texDepth; z++)
                {
                    for (UInt32 y = 0; y < texHeight; y++)
                    {
                        for (UInt32 x = 0; x < texWidth; x++)
                        {
                            if (x < origTexWidth && y < origTexHeight && z < origTexDepth)
                            {
                                long jumpingIndex = x + y * texWidth + z * texWidth * texHeight;

                                // TODO: To move from Int32 to UInt32 range, we should add Int32.MaxValue?!
                                // However, when we do this,
                                UInt32 pixelValue = (UInt32)((Int32)ptr[consecutiveIndex]) + (UInt32)Int16.MaxValue;
                                colors [jumpingIndex] = F2C(pixelValue);

                                if (pixelValue > max)
                                {
                                    max = pixelValue;
                                }
                                if (pixelValue < min)
                                {
                                    min = pixelValue;
                                }

                                histogram.addValue(pixelValue);

                                consecutiveIndex++;
                            }
                        }
                    }
                }
            }
        }
        else
        {
            throw(new System.Exception("Unsupported pixel format: " + image.GetPixelID()));
        }

        /*IntPtr bufferPtr;
         * UInt32 numberOfPixels = image.GetWidth () * image.GetHeight () * image.GetDepth();
         * if (image.GetPixelID () == PixelIDValueEnum.sitkUInt16) {
         *      bufferPtr = image.GetBufferAsUInt16 ();
         *
         *      UInt16[] colorsTmp = new UInt16[ numberOfPixels ];
         *      Int16[] tmp = new Int16[ numberOfPixels ];
         *      Marshal.Copy( bufferPtr, tmp, 0, (int)numberOfPixels );
         *      System.Buffer.BlockCopy (tmp, 0, colorsTmp, 0, (int)numberOfPixels);
         *
         *      int index = 0;
         *      //for (UInt32 z = 0; z < texDepth; z++) {
         *      for (UInt32 z = 0; z < texDepth; z++) {
         *              for (UInt32 y = 0; y < texHeight; y++) {
         *                      for (UInt32 x = 0; x < texWidth; x++) {
         *                              //long consecutiveIndex = (texWidth-1-x) + y*texWidth + z*texWidth*texHeight;
         *                              long consecutiveIndex =  x + y * texWidth + z*texWidth*texHeight;
         *                              if (x < origTexWidth && y < origTexHeight && z < origTexDepth) {
         *                                      UInt16 pixelValue = (UInt16)((colorsTmp [index] - intercept) / slope);
         *                                      colors [consecutiveIndex] = F2C (pixelValue);
         *
         *                                      if (pixelValue > max)
         *                                              max = pixelValue;
         *                                      if (pixelValue < min)
         *                                              min = pixelValue;
         *
         *                                      histogram.addValue (pixelValue);
         *
         *                                      index++;
         *                              }
         *                      }
         *              }
         *      }
         * } else if ( image.GetPixelID() == PixelIDValueEnum.sitkInt16 ) {
         *      bufferPtr = image.GetBufferAsInt16 ();
         *
         *      Int16[] colorsTmp = new Int16[ numberOfPixels ];
         *      Marshal.Copy( bufferPtr, colorsTmp, 0, (int)numberOfPixels );
         *
         *      int index = 0;
         *      //for (UInt32 z = 0; z < texDepth; z++) {
         *      for (UInt32 z = 0; z < texDepth; z++) {
         *              for (UInt32 y = 0; y < texHeight; y++) {
         *                      for (UInt32 x = 0; x < texWidth; x++) {
         *                              //long consecutiveIndex = (texWidth-1-x) + y*texWidth + z*texWidth*texHeight;
         *                              long consecutiveIndex =  x + y * texWidth + z*texWidth*texHeight;
         *                              if (x < origTexWidth && y < origTexHeight && z < origTexDepth )
         *                              {
         *                                      //Int16 pixelValueInt16 = (Int16)((colorsTmp [index] - intercept) / slope);
         *                                      //UInt32 pixelValue = (UInt32)((int)pixelValueInt16 + 32768);
         *
         *                                      UInt16 pixelValue = (UInt16)((colorsTmp [index] - intercept) / slope);
         *                                      colors [ consecutiveIndex] = F2C(pixelValue);
         *
         *                                      if (pixelValue > max)
         *                                              max = pixelValue;
         *                                      if (pixelValue < min)
         *                                              min = pixelValue;
         *
         *                                      histogram.addValue (pixelValue);
         *
         *                                      index++;
         *                              }
         *                      }
         *              }
         *      }
         * } else if ( image.GetPixelID() == PixelIDValueEnum.sitkInt32 ) {
         *      bufferPtr = image.GetBufferAsInt32 ();
         *
         *      Int32[] colorsTmp = new Int32[ numberOfPixels ];
         *      Marshal.Copy( bufferPtr, colorsTmp, 0, (int)numberOfPixels );
         *
         *      int index = 0;
         *      //for (UInt32 z = 0; z < texDepth; z++) {
         *      for (UInt32 z = 0; z < texDepth; z++) {
         *              for (UInt32 y = 0; y < texHeight; y++) {
         *                      for (UInt32 x = 0; x < texWidth; x++) {
         *                              //long consecutiveIndex = (texWidth-1-x) + y*texWidth + z*texWidth*texHeight;
         *                              long consecutiveIndex =  x + y * texWidth + z*texWidth*texHeight;
         *                              if (x < origTexWidth && y < origTexHeight && z < origTexDepth) {
         *                                      Int32 pixelValueInt32 = (Int32)((colorsTmp [index] - intercept) / slope);
         *
         *                                      UInt32 pixelValue = (UInt32)((Int64)pixelValueInt32 + Int32.MaxValue);
         *                                      colors [ consecutiveIndex ] = F2C(pixelValue);
         *
         *                                      if (pixelValue > max)
         *                                              max = pixelValue;
         *                                      if (pixelValue < min)
         *                                              min = pixelValue;
         *
         *                                      histogram.addValue (pixelValue);
         *
         *                                      index++;
         *                              }
         *                      }
         *              }
         *      }
         * } else {
         *      throw(new System.Exception ("Unsupported pixel format: " + image.GetPixelID()));
         * }*/

        // Manually set the min and max values, because we just caculated them for the whole volume and
        // can thus be sure that we have the correct values:
        seriesInfo.setMinMaxPixelValues(min, max);


        histogram.setMinMaxPixelValues(min, max);

        // Make the loaded image accessable from elsewhere:
        this.image = image;


        Debug.Log("Loaded.");
    }
Exemple #6
0
    /*! Constructor, fills most of the attributes of the DICOMSeries class.
     * \note This does some heavy file/directory parsing to determine the files which are part of
     *      this series and their order. This is why the DICOMSeries should be constructed in a
     *      background thread and then passed to the main thread. */
    public DICOMSeries(string directory, string seriesUID)
    {
        // Get the file names for the series:
        filenames = ImageSeriesReader.GetGDCMSeriesFileNames(directory, seriesUID);
        if (filenames.Count <= 0)
        {
            throw(new System.Exception("No files found for series " + seriesUID + "."));
        }
        this.seriesUID = seriesUID;

        // Load the first slice in volume to get meta information:
        firstSlice = SimpleITK.ReadImage(filenames[0]);
        VectorDouble o1 = firstSlice.GetOrigin();

        if (o1.Count < 3)
        {
            throw(new System.Exception("Invalid origins found in first image."));
        }
        origin = new Vector3((float)o1 [0], (float)o1 [1], (float)o1 [2]);

        numberOfSlices = filenames.Count;

        // Offset between two adjacent slices. If only one slice is present,
        // this defaults to zero.
        Vector3 sliceOffset = Vector3.zero;

        // If we have more than one slice, also load the last slice to be able to determine the slice spacing:
        if (filenames.Count > 1)
        {
            lastSlice = SimpleITK.ReadImage(filenames[filenames.Count - 1]);

            // Get the origins of the two images:
            VectorDouble o2 = lastSlice.GetOrigin();
            if (o2.Count < 3)
            {
                throw(new System.Exception("Invalid origins found in last image."));
            }
            Vector3 lastOrigin = new Vector3((float)o2 [0], (float)o2 [1], (float)o2 [2]);

            // Calculate offset between two adjacent slices (assuming all neighbours are the same distance apart):
            // Note: I expect sliceOffset.x and sliceOffset.y to be zero most of the time.
            //		Using a Vector just for completeness.
            sliceOffset = (lastOrigin - origin) / (filenames.Count - 1);
        }

        // Load the direction cosines:
        // ITK stores the direction cosines in a matrix with row-major-ordering. The weird indexing is because
        // we need the first and second column (0,3,6 for X and 1,4,7 for Y)
        VectorDouble direction = firstSlice.GetDirection();

        if (direction.Count < 6)
        {
            throw(new System.Exception("Invalid direction cosines found in images."));
        }
        directionCosineX = new Vector3((float)direction [0], (float)direction [3], (float)direction [6]);
        directionCosineY = new Vector3((float)direction [1], (float)direction [4], (float)direction [7]);

        sliceNormal = Vector3.Cross(directionCosineX, directionCosineY);

        // Calculate the which direction the normal is facing to determine the orienation (Transverse,
        // Coronal or Saggital).
        float absX = Mathf.Abs(sliceNormal.x);
        float absY = Mathf.Abs(sliceNormal.y);
        float absZ = Mathf.Abs(sliceNormal.z);

        if (absX > absY && absX > absZ)
        {
            sliceOrientation = SliceOrientation.Saggital;
        }
        else if (absY > absX && absY > absZ)
        {
            sliceOrientation = SliceOrientation.Coronal;
        }
        else if (absZ > absX && absZ > absY)
        {
            sliceOrientation = SliceOrientation.Transverse;
        }
        else
        {
            sliceOrientation = SliceOrientation.Unknown;
        }

        // Load the direction cosines:
        // NOTE: It seems that the the first value is the spacing between rows (i.e. y direction),
        //		the second value is the spacing between columns (i.e. x direction).
        //		I was not able to verify this so far, since all test dicoms we had have the same spacing in
        //		x and y direction...
        VectorDouble spacing = firstSlice.GetSpacing();

        if (spacing.Count < 2)
        {
            throw(new System.Exception("Invalid pixel spacing found in images."));
        }
        pixelSpacing = new Vector2((float)spacing [1], (float)spacing [0]);

        // Set up the transformation matrix:
        Matrix4x4 transformMatrix = new Matrix4x4();

        // Column 1:
        transformMatrix [0, 0] = directionCosineX.x * pixelSpacing.x;
        transformMatrix [1, 0] = directionCosineX.y * pixelSpacing.x;
        transformMatrix [2, 0] = directionCosineX.z * pixelSpacing.x;
        transformMatrix [3, 0] = 0f;
        // Column 2:
        transformMatrix [0, 1] = directionCosineY.x * pixelSpacing.y;
        transformMatrix [1, 1] = directionCosineY.y * pixelSpacing.y;
        transformMatrix [2, 1] = directionCosineY.z * pixelSpacing.y;
        transformMatrix [3, 1] = 0f;
        // Column 3:
        transformMatrix [0, 2] = sliceOffset.x;
        transformMatrix [1, 2] = sliceOffset.y;
        transformMatrix [2, 2] = sliceOffset.z;
        transformMatrix [3, 2] = 0f;
        // Column 4:
        transformMatrix [0, 3] = origin.x;
        transformMatrix [1, 3] = origin.y;
        transformMatrix [2, 3] = origin.z;
        transformMatrix [3, 3] = 1f;

        // Convert to a the left-hand-side coordinate system which Unity uses:
        Matrix4x4 rightHandToLeftHand = new Matrix4x4();

        rightHandToLeftHand [0, 0] = 1f;
        rightHandToLeftHand [1, 1] = 1f;
        rightHandToLeftHand [2, 2] = -1f;
        rightHandToLeftHand [3, 3] = 1f;

        pixelToPatient = rightHandToLeftHand * transformMatrix;

        // Inverse transformation:
        patientToPixel = pixelToPatient.inverse;

        // Read the minimum and maximum values which are stored in this image:
        minPixelValue          = UInt16.MinValue;
        maxPixelValue          = UInt16.MaxValue;
        foundMinMaxPixelValues = false;
        try {
            minPixelValue          = Int32.Parse(firstSlice.GetMetaData("0028|0106"));
            maxPixelValue          = Int32.Parse(firstSlice.GetMetaData("0028|0107"));
            foundMinMaxPixelValues = true;
        } catch {
        }
    }
Exemple #7
0
    /*! Constructor, fills most of the attributes of the DICOMSeries class.
     * \note This does some heavy file/directory parsing to determine the files which are part of
     *      this series and their order. This is why the DICOMSeries should be constructed in a
     *      background thread and then passed to the main thread. */
    public DICOMSeries(string directory, string seriesUID)
    {
        Debug.Log("Loading Meta Data for Series: " + seriesUID);
        // Get the file names for the series:
        filenames = ImageSeriesReader.GetGDCMSeriesFileNames(directory, seriesUID);
        if (filenames.Count <= 0)
        {
            throw(new System.Exception("No files found for series " + seriesUID + "."));
        }
        this.seriesUID = seriesUID;

        // Load the first slice in volume to get meta information:
        firstSlice = SimpleITK.ReadImage(filenames[0]);
        lastSlice  = SimpleITK.ReadImage(filenames[Math.Max(filenames.Count - 1, 0)]);

        numberOfSlices = filenames.Count;

        // Load the direction cosines:
        // ITK stores the direction cosines in a matrix with row-major-ordering. The weird indexing is because
        // we need the first and second column (0,3,6 for X and 1,4,7 for Y)
        VectorDouble direction = firstSlice.GetDirection();

        if (direction.Count < 6)
        {
            throw(new System.Exception("Invalid direction cosines found in images."));
        }
        directionCosineX = new Vector3((float)direction [0], (float)direction [3], (float)direction [6]);
        directionCosineY = new Vector3((float)direction [1], (float)direction [4], (float)direction [7]);

        sliceNormal = Vector3.Cross(directionCosineX, directionCosineY);

        if (lastSlice != null)
        {
            // Get the origins of the two images:
            VectorDouble o1 = firstSlice.GetOrigin();
            if (o1.Count < 3)
            {
                throw(new System.Exception("Invalid origins found in first image."));
            }
            Vector3      origin = new Vector3((float)o1 [0], (float)o1 [1], (float)o1 [2]);
            VectorDouble o2     = lastSlice.GetOrigin();
            if (o2.Count < 3)
            {
                throw(new System.Exception("Invalid origins found in last image."));
            }
            Vector3 lastOrigin = new Vector3((float)o2 [0], (float)o2 [1], (float)o2 [2]);

            // Calculate offset between two adjacent slices (assuming all neighbours are the same distance apart):
            // Note: I expect sliceOffset.x and sliceOffset.y to be zero most of the time.
            //              Using a Vector just for completeness.
            sliceOffset = (lastOrigin - origin) / (numberOfSlices - 1);
        }

        if (lastSlice != null && numberOfSlices > 1)
        {
            // Load the direction cosines:
            // ITK stores the direction cosines in a matrix with row-major-ordering. The weird indexing is because
            // we need the first and second column (0,3,6 for X and 1,4,7 for Y)
            VectorDouble directionLast = lastSlice.GetDirection();
            if (directionLast.Count < 6)
            {
                throw(new System.Exception("Invalid direction cosines found in images."));
            }
            Vector3 directionCosineXLast = new Vector3((float)directionLast [0], (float)directionLast [3], (float)directionLast [6]);
            Vector3 directionCosineYLast = new Vector3((float)directionLast [1], (float)directionLast [4], (float)directionLast [7]);

            Vector3 sliceNormalLast = Vector3.Cross(directionCosineXLast, directionCosineYLast);

            // If the first and last slice have the same orientation, then consider this series to be a volume.
            // TODO: Better check?
            if ((sliceNormal == sliceNormalLast))
            {
                isConsecutiveVolume = true;
            }
            else
            {
                Debug.LogWarning("First and last slice of the series do not have the same orientation. This will not be considered a volume.\n" +
                                 "\tNormal first slice, Normal last slice: " + sliceNormal + " " + sliceNormalLast);
            }
        }
        else
        {
            isConsecutiveVolume = false;
        }

        if (isConsecutiveVolume)
        {
            // Calculate which direction the normal is facing to determine the orienation (Transverse,
            // Coronal or Saggital).
            float absX = Mathf.Abs(sliceNormal.x);
            float absY = Mathf.Abs(sliceNormal.y);
            float absZ = Mathf.Abs(sliceNormal.z);
            if (absX > absY && absX > absZ)
            {
                sliceOrientation = SliceOrientation.Saggital;
            }
            else if (absY > absX && absY > absZ)
            {
                sliceOrientation = SliceOrientation.Coronal;
            }
            else if (absZ > absX && absZ > absY)
            {
                sliceOrientation = SliceOrientation.Transverse;
            }
            else
            {
                sliceOrientation = SliceOrientation.Unknown;
            }
        }
        else
        {
            sliceOrientation = SliceOrientation.Unknown;                // Can't know what the orientation is if the first and last slice have different normals
        }

        // Read the minimum and maximum values which are stored in this image:
        minPixelValue          = UInt16.MinValue;
        maxPixelValue          = UInt16.MaxValue;
        foundMinMaxPixelValues = false;
        try {
            minPixelValue          = UInt32.Parse(firstSlice.GetMetaData("0028|0106"));
            maxPixelValue          = UInt32.Parse(firstSlice.GetMetaData("0028|0107"));
            foundMinMaxPixelValues = true;
        } catch {
        }
    }