LAUREL BRIDGE

LaurelBridge.DCFExamples.ImageViewer Namespace

DICOM Connectivity Framework V3.4
The ImageViewer example is a non-diagnostic windows forms application that demonstrates use of the DicomImage class for image rendering, window/leveling, thumbnail generation, multiframe image rendering, and various other dicom image related functions.
Classes

  ClassDescription
Public classDicomHeaderDataForm
Dicom dump dialog
Public classDoubleBufferedPanel
Double buffered scrollable control with added support for zooming and panning.
Public classProgram
Windows Form application to provide a basic DICOM image viewer. This viewer makes use of several facilities in the LaurelBridge.DCF.Imaging namespace. See the Main(String) method for more information on optional command line arguments.
Public classThumbnailImagePanel
Thumbnail image panel with added support to draw view rectangle
Public classWindowLevelCalculator
Class to assist in calculating the window level values for a monochrome DICOM image.
Delegates

  DelegateDescription
Public delegateProgramInvokeDelegate
A delegate for Invoke.
Remarks

Not all aspects of DICOM image viewing have been implemented in this viewer.

Supported OS Platforms:

  • Windows - .Net Framework 4.7.2 64-bit and 32-bit

Examples

WindowLevelCalculator Sample Code
public class WindowLevelCalculator
{
    #region Fields
    private readonly ImageInfo _imageInfo;
    private readonly long _pixelRange;
    private readonly bool _inverted;
    private readonly long _minPixel;
    private readonly long _maxPixel;
    private readonly int _bitShift;
    private readonly float _signShift;
    private readonly float _intercept;
    private readonly float _slope;
    private readonly long[] _histogram;
    private long _count;
    private long _startCount;
    private long _endCount;
    private int _startIndex;
    private int _endIndex;
    private float _width;
    private float _center;

    private const double IgnoreStartPercent = 0.05;
    private const double IgnoreEndPercent = 0.01;
    #endregion

    #region Constructor
    /// <summary>
    /// Construct a window level calculator for the given image info.
    /// </summary>
    /// <param name="imageInfo">The ImageInfo for the image.</param>
    public WindowLevelCalculator(ImageInfo imageInfo)
    {
        _imageInfo = imageInfo;
        if (_imageInfo == null)
            throw new ArgumentException();
        if (!_imageInfo.IsGrayscalePixels)
            throw new NotSupportedException("window leveling color images not supported");
        Debug.Assert(_imageInfo.IsValid());
        _pixelRange = (1L << _imageInfo.BitsStored);
        _inverted = _imageInfo.PixelRepresentation != 0;
        int buckets = _imageInfo.BitsStored > 16 ? (1 << 16) : (1 << _imageInfo.BitsStored);
        _minPixel = _imageInfo.PixelRepresentation == 0 ? 0 : 0 - (_pixelRange >> 1);
        _maxPixel = _imageInfo.PixelRepresentation == 0 ? _pixelRange - 1 : ((_pixelRange >> 1) - 1);
        _intercept = _imageInfo.RescaleIntercept;
        _slope = _imageInfo.RescaleSlope;
        _signShift = (_imageInfo.PixelRepresentation == 0) ? 0 : (1L << (_imageInfo.BitsStored - 1));
        _bitShift = _imageInfo.BitsStored > 16 ? _imageInfo.BitsStored - 16 : 0;
        _histogram = new long[buckets];
        _count = 0;
    }
    #endregion

    #region Compute
    /// <summary>
    /// Compute a window level center and width for the given pixel array.
    /// </summary>
    /// <param name="pixelArray">The raw pixel array.</param>
    /// <param name="center">Out parameter that gets the window center.</param>
    /// <param name="width">Out parameter that gets the window width.</param>
    public void Compute(Array pixelArray, out float center, out float width)
    {
        center = _imageInfo.WindowCenter;
        width = _imageInfo.WindowWidth;
        _count = pixelArray.Length;
        if (pixelArray.GetType() == typeof(byte[]))
        {
            Count((byte[])pixelArray);
        }
        else if (pixelArray.GetType() == typeof(sbyte[]))
        {
            Count((sbyte[])pixelArray);
        }
        else if (pixelArray.GetType() == typeof(ushort[]))
        {
            Count((ushort[])pixelArray);
        }
        else if (pixelArray.GetType() == typeof(short[]))
        {
            Count((short[])pixelArray);
        }
        else if (pixelArray.GetType() == typeof(uint[]))
        {
            Count((uint[])pixelArray);
        }
        else if (pixelArray.GetType() == typeof(int[]))
        {
            Count((int[])pixelArray);
        }
        else
            throw new NotSupportedException("unsupported pixel type(" + pixelArray.GetType().GetElementType() + ")");

        // Some images could have saturated noise at either extreme that could be avoided.
        // Artificial intelligence or human interaction is needed to determine which images 
        // need the percentages is beyond the scope of this example.
        //_startCount = 1; // (long)(_count * IgnoreStartPercent); // ignore first 5%
        //_endCount = 1; // (long)(_count * IgnoreEndPercent); // ignore last 1%

        // find total width
        _startCount = 1;
        _endCount = 1;
        long numSeen = 0;
        for (_startIndex = 0; _startIndex < _histogram.Length; _startIndex++)
        {
            if (_histogram[_startIndex] + numSeen >= _startCount)
                break;
            numSeen += _histogram[_startIndex];
        }
        numSeen = 0;
        for (_endIndex = _histogram.Length - 1; _endIndex > _startIndex; _endIndex--)
        {
            if (numSeen + _histogram[_endIndex] >= _endCount)
                break;
            numSeen += _histogram[_endIndex];
        }
        float range = BucketToPixel(_endIndex) - BucketToPixel(_startIndex);
        // minimum window width is 1
        width = _width = Math.Max(1.0f, _slope * range);
        center = _center = _slope * (BucketToPixel(_startIndex)) + _intercept + _width / 2;
    }
    #endregion

    #region ToString
    /// <summary>
    /// Dump this as a string.
    /// </summary>
    /// <returns>Me as a string.</returns>
    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();
        int incr = _histogram.Length / 32;
        for (int i = 0; i < _histogram.Length; i += incr)
        {
            long count = 0;
            for (int j = i; j < i + incr; j++)
            {
                if (j == _histogram.Length)
                    break;
                count += _histogram[j];
            }
            sb.AppendFormat("{0,6} : {1,-8}", i, count);
            sb.AppendLine();
        }
        sb.AppendLine();
        sb.AppendFormat(
            "count({0}), pixelRange({1}), signShift({2}), slope({3}), intercept({4}), bitShift({5}))",
            _count, _pixelRange, _signShift, _slope, _intercept, _bitShift);
        sb.AppendLine();
        sb.AppendFormat("startIndex({0}), endIndex({1}), width({2}), center({3}), inverted({4})",
            _startIndex, _endIndex, _width, _center, _inverted);
        return sb.ToString();
    }
    #endregion

    #region Helpers
    private void Count<T>(IEnumerable<T> pixels) where T : IConvertible
    {
        foreach (T p in pixels)
        {
            long rawPixel = Convert.ToInt64(p);
            _histogram[PixelToBucket(rawPixel)]++;
        }
    }

    /// <summary>
    /// Convert to histogram bucket value.
    /// </summary>
    /// <param name="pixelValue">Raw pixel value from DICOM</param>
    /// <returns>the bucket index that contains the pixel value</returns>
    private int PixelToBucket(long pixelValue)
    {
        long bucket = (long)(pixelValue + _signShift) >> _bitShift;
        // silently clamp out-of-range pixels
        if (bucket < 0)
            bucket = 0;
        else if (bucket >= _histogram.Length)
            bucket = _histogram.Length - 1;
        return (int)bucket;
    }

    /// <summary>
    /// Convert back to pixel value.
    /// </summary>
    /// <param name="bucket">A bucket is a pixel normalized to positive, and fit into a 64k histogram</param>
    /// <returns>the pixel value for the bucket</returns>
    private float BucketToPixel(int bucket)
    {
        float pixel = ((long)bucket << _bitShift) - _signShift;
        Debug.Assert(_minPixel <= pixel && pixel <= _maxPixel);
        return pixel;
    }
    #endregion
}