context: #region License
  
  // Copyright (c) 2013, ClearCanvas Inc.
  // All rights reserved.
  // http://www.clearcanvas.ca
  //
  // This file is part of the ClearCanvas RIS/PACS open source project.
  //
  // The ClearCanvas RIS/PACS open source project is free software: you can
  // redistribute it and/or modify it under the terms of the GNU General Public
  // License as published by the Free Software Foundation, either version 3 of the
  // License, or (at your option) any later version.
  //
  // The ClearCanvas RIS/PACS open source project is distributed in the hope that it
  // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
  // Public License for more details.
  //
  // You should have received a copy of the GNU General Public License along with
  // the ClearCanvas RIS/PACS open source project.  If not, see
  // <http://www.gnu.org/licenses/>.
  
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Drawing;
  using System.IO;
  using ClearCanvas.Common;
  using ClearCanvas.Dicom.Codec;
  using ClearCanvas.Dicom.IO;
  using ClearCanvas.Dicom.Iod;
  
  namespace ClearCanvas.Dicom
  {
  	/// <summary>
  	/// Class representing uncompressed pixel data.
  	/// </summary>
  	public class DicomUncompressedPixelData : DicomPixelData
  	{
  		#region Private Members
  
  		private readonly List<FrameData> _fd = new List<FrameData>();
  		private DicomAttribute _pd;
  
  		#endregion
  
  		#region Constructors
  
  		/// <summary>
  		/// Initializes a <see cref="DicomUncompressedPixelData"/> from the attributes in a DICOM file/message.
  		/// </summary>
  		/// <param name="dicomMessage">A DICOM file/message from which to initialize the properties of the <see cref="DicomUncompressedPixelData"/>.</param>
  		public DicomUncompressedPixelData(DicomMessageBase dicomMessage)
  			: base(dicomMessage)
  		{
  			_pd = dicomMessage.DataSet[DicomTags.PixelData];
  			InitializeFrameData(this, _pd);
  		}
  
  		/// <summary>
  		/// Constructor from an <see cref="DicomAttributeCollection"/> instance.
  		/// </summary>
  		/// <param name="collection"></param>
  		public DicomUncompressedPixelData(DicomAttributeCollection collection)
  			: base(collection)
  		{
  			_pd = collection[DicomTags.PixelData];
  			InitializeFrameData(this, _pd);
  		}
  
  		/// <summary>
  		/// Contructor from a <see cref="DicomCompressedPixelData"/> instance.
  		/// </summary>
  		/// <param name="compressedPixelData"></param>
  		public DicomUncompressedPixelData(DicomCompressedPixelData compressedPixelData)
  			: base(compressedPixelData)
  		{
  			if (BitsAllocated > 8)
  			{
  				_pd = new DicomAttributeOW(DicomTags.PixelData);
  			}
  			else
  			{
  				var pdTag = DicomTagDictionary.GetDicomTag(DicomTags.PixelData);
  				var obTag = new DicomTag(DicomTags.PixelData, pdTag.Name, pdTag.VariableName, DicomVr.OBvr, pdTag.MultiVR, pdTag.VMLow, pdTag.VMHigh, pdTag.Retired);
  				_pd = new DicomAttributeOB(obTag);
  			}
  			InitializeFrameData(this, _pd);
  		}
  
  		private void InitializeFrameData(DicomUncompressedPixelData owner, DicomAttribute pixelDataAttribute)
  		{
  			var obAttrib = pixelDataAttribute as DicomAttributeOB;
  			if (obAttrib != null && obAttrib.StreamLength > 0)
  			{
  				for (var n = 0; n < NumberOfFrames; ++n)
  					_fd.Add(new FrameDataOB(owner, obAttrib, n));
  			}
  
  			var owAttrib = pixelDataAttribute as DicomAttributeOW;
  			if (owAttrib != null && owAttrib.StreamLength > 0)
  			{
  				for (var n = 0; n < NumberOfFrames; ++n)
  					_fd.Add(new FrameDataOW(owner, owAttrib, n));
  			}
  		}
  
  		#endregion
  
  		#region Internal Methods
  
  		internal void UpdatePixelDataAttribute()
  		{
  			var frameSize = UncompressedFrameSize;
  			var frameCount = NumberOfFrames;
  			var padding = ((frameCount*frameSize) & 1) == 1 ? 1 : 0;
  
  			using (var stream = ((DicomAttributeBinary) _pd).AsStream())
  			{
  				stream.Position = 0;
  				foreach (var frameData in _fd)
  				{
  					stream.Write(frameData.GetFrame(), 0, frameSize);
  				}
  				stream.SetLength(frameCount*frameSize + padding);
  			}
  		}
  
  		internal byte[] GetData()
  		{
  			if (_fd.Count == 0) return null;
  			return _fd[0].GetFrame();
  		}
  
  		#endregion
  
  		#region Public Methods
  
  		/// <summary>
  		/// Update a <see cref="DicomAttributeCollection"/> with the pixel data contained
  		/// within this object and also update pixel data related tags.
  		/// </summary>
  		/// <remarks>
  		/// This method will replace the pixel data attribute in <paramref name="dataset"/>
  		/// and update other pixel data related tags within the collection.
  		/// </remarks>
  		/// <param name="dataset">The collection to update.</param>
  		public override void UpdateAttributeCollection(DicomAttributeCollection dataset)
  		{
  			dataset.SaveDicomFields(this);
  
  			if (dataset.Contains(DicomTags.NumberOfFrames) || NumberOfFrames > 1)
  				dataset[DicomTags.NumberOfFrames].SetInt32(0, NumberOfFrames);
  			if (dataset.Contains(DicomTags.PlanarConfiguration))
  				dataset[DicomTags.PlanarConfiguration].SetInt32(0, PlanarConfiguration);
  			if (dataset.Contains(DicomTags.LossyImageCompressionRatio))
  				dataset[DicomTags.LossyImageCompressionRatio].SetFloat32(0, LossyImageCompressionRatio);
  			if (dataset.Contains(DicomTags.LossyImageCompressionMethod))
  				dataset[DicomTags.LossyImageCompressionMethod].SetString(0, LossyImageCompressionMethod);
  			if (dataset.Contains(DicomTags.RescaleSlope) || DecimalRescaleSlope != 1.0M || DecimalRescaleIntercept != 0.0M)
  				dataset[DicomTags.RescaleSlope].SetString(0, RescaleSlope);
  			if (dataset.Contains(DicomTags.RescaleIntercept) || DecimalRescaleSlope != 1.0M || DecimalRescaleIntercept != 0.0M)
  				dataset[DicomTags.RescaleIntercept].SetString(0, RescaleIntercept);
  
  			if (dataset.Contains(DicomTags.WindowCenter) || LinearVoiLuts.Count > 0)
  				Window.SetWindowCenterAndWidth(dataset, LinearVoiLuts);
  
  			dataset[DicomTags.PixelData] = _pd;
  
  			//Remove the palette color lut, if the pixels were translated to RGB
  			if (dataset.Contains(DicomTags.RedPaletteColorLookupTableData)
  			    && dataset.Contains(DicomTags.BluePaletteColorLookupTableData)
  			    && dataset.Contains(DicomTags.GreenPaletteColorLookupTableData)
  			    && !HasPaletteColorLut)
  			{
  				dataset.RemoveAttribute(DicomTags.BluePaletteColorLookupTableDescriptor);
  				dataset.RemoveAttribute(DicomTags.BluePaletteColorLookupTableData);
  				dataset.RemoveAttribute(DicomTags.RedPaletteColorLookupTableDescriptor);
  				dataset.RemoveAttribute(DicomTags.RedPaletteColorLookupTableData);
  				dataset.RemoveAttribute(DicomTags.GreenPaletteColorLookupTableDescriptor);
  				dataset.RemoveAttribute(DicomTags.GreenPaletteColorLookupTableData);
  			}
  
  			UpdatePixelDataAttribute();
  		}
  
  		/// <summary>
  		/// Update a <see cref="DicomMessageBase"/> with the pixel data contained
  		/// within this object and also update pixel data related tags.
  		/// </summary>
  		/// <param name="message"></param>
  		public override void UpdateMessage(DicomMessageBase message)
  		{
  			UpdateAttributeCollection(message.DataSet);
  			DicomFile file = message as DicomFile;
  			if (file != null)
  				file.TransferSyntax = TransferSyntax;
  		}
  
  		/// <summary>
  		/// Appends a frame to the pixel data.
  		/// </summary>
  		/// <param name="frameData">The frame data to append.</param>
  		public void AppendFrame(byte[] frameData)
  		{
  			_fd.Add(new FrameDataBytes(this, frameData));
  		}
  
  		public void InsertFrame(int frame, byte[] frameData)
  		{
  			_fd.Insert(frame, new FrameDataBytes(this, frameData));
  		}
  
  		public void RemoveFrame(int frame)
  		{
  			_fd.RemoveAt(frame);
  		}
  
  		/// <summary>
  		/// Gets a specific uncompressed frame.
  		/// </summary>
  		/// <param name="frame">The zero-offset index of the frame to be retrieved.</param>
  		/// <param name="photometricInterpretation">The photometric interpretation of the pixel data.</param>
  		/// <returns>A byte array containing the frame data.</returns>
  		/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="frame"/> specifies an invalid frame index.</exception>
  		/// <exception cref="DicomDataException">Thrown if there was a problem extracting the specified frame from the pixel data buffer.</exception>
  		public override byte[] GetFrame(int frame, out string photometricInterpretation)
  		{
  			if (frame >= NumberOfFrames || frame < 0)
  				throw new ArgumentOutOfRangeException("frame");
  
  			photometricInterpretation = PhotometricInterpretation;
  
  			if (frame >= _fd.Count)
  				throw new DicomDataException("Requested frame exceeds available pixel data in buffer.");
  
  			return _fd[frame].GetFrame();
  		}
  
  		/// <summary>
  		/// Replace a specific uncompressed frame.
  		/// </summary>
  		/// <param name="frame">The zero-offset index of the frame to be replaced.</param>
  		/// <param name="frameData">Frame data to replace the current data</param>
  		/// <returns>A byte array containing the frame data.</returns>
  		/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="frame"/> specifies an invalid frame index.</exception>
  		/// <exception cref="DicomDataException">Thrown if there was a problem extracting the specified frame from the pixel data buffer.</exception>
  		public void SetFrame(int frame, byte[] frameData)
  		{
  			if (frame >= NumberOfFrames || frame < 0)
  				throw new ArgumentOutOfRangeException("frame");
  
  			if (frame >= _fd.Count)
  				throw new DicomDataException("Requested frame exceeds available pixel data in buffer.");
  
  			_fd[frame] = new FrameDataBytes(this, frameData);
  		}
  
  		[Obsolete("Renamed to DicomUncompressedPixelData.SetFrame(int,byte[]).")]
  		public void ReplaceFrame(int frame, byte[] frameData)
  		{
  			SetFrame(frame, frameData);
  		}
  
  		public void ConvertPaletteColorToRgb()
  		{
  			Platform.CheckTrue(PhotometricInterpretation.Equals("PALETTE COLOR"), "Photometric Interpretation Palette Color Check");
  
  			List<FrameData> frames = new List<FrameData>();
  
  			for (int i = 0; i < NumberOfFrames; i++)
  			{
  				byte[] currentFrame = GetFrame(i);
  				byte[] newFrame = new byte[UncompressedFrameSize*3];
  
  				PaletteColorToRgb(BitsAllocated, IsSigned, currentFrame, newFrame, PaletteColorLut);
  				frames.Add(new FrameDataBytes(this, newFrame, false));
  			}
  
  			// change the Pixel Data attribute so we don't affect the original
  			_pd = _pd.Tag.VR.CreateDicomAttribute(_pd.Tag);
  
  			_fd.Clear();
  			_fd.AddRange(frames);
  
  			SamplesPerPixel = 3;
  			PhotometricInterpretation = "RGB";
  			PlanarConfiguration = 0;
  			HasPaletteColorLut = false;
  		}
  
  		#endregion
  
  		#region Static Methods
  
  		#region Toggle Methods
  
  		/// <summary>
  		/// Toggle the planar configuration of a pixel data array
  		/// </summary>
  		/// <param name="pixelData"></param>
  		/// <param name="numValues"></param>
  		/// <param name="bitsAllocated"></param>
  		/// <param name="samplesPerPixel"></param>
  		/// <param name="oldPlanerConfiguration"></param>
  		public static void TogglePlanarConfiguration(byte[] pixelData, int numValues, int bitsAllocated, int samplesPerPixel, int oldPlanerConfiguration)
  		{
  			int bytesAllocated = bitsAllocated/8;
  			int numPixels = numValues/samplesPerPixel;
  			if (bytesAllocated == 1)
  			{
  				byte[] buffer = new byte[pixelData.Length];
  				if (oldPlanerConfiguration == 1)
  				{
  					for (int n = 0; n < numPixels; n++)
  					{
  						for (int s = 0; s < samplesPerPixel; s++)
  						{
  							buffer[n*samplesPerPixel + s] = pixelData[n + numPixels*s];
  						}
  					}
  				}
  				else
  				{
  					for (int n = 0; n < numPixels; n++)
  					{
  						for (int s = 0; s < samplesPerPixel; s++)
  						{
  							buffer[n + numPixels*s] = pixelData[n*samplesPerPixel + s];
  						}
  					}
  				}
  				Array.Copy(buffer, 0, pixelData, 0, numValues);
  			}
  			else if (bytesAllocated == 2)
  			{
  				throw new DicomCodecUnsupportedSopException(String.Format("BitsAllocated={0} is not supported!", bitsAllocated));
  			}
  			else
  				throw new DicomCodecUnsupportedSopException(String.Format("BitsAllocated={0} is not supported!", bitsAllocated));
  		}
  
  		/// <summary>
  		/// Toggle the pixel representation of a frame
  		/// </summary>
  		public static unsafe void TogglePixelRepresentation(byte[] frameData, int highBit, int bitsStored, int bitsAllocated, out int rescale)
  		{
  			if ((bitsAllocated != 8 && bitsAllocated != 16) || bitsStored > bitsAllocated)
  				throw new DicomCodecUnsupportedSopException("Invalid bits allocated/stored value(s).");
  
  			int highestBit = bitsAllocated - 1;
  			const int lowestBit = 0;
  			if (highBit > highestBit || highBit < lowestBit)
  				throw new ArgumentException(String.Format("Invalid high bit for {0}-bit pixel data ({1}).", bitsAllocated, highBit));
  
  			int bytesAllocated = bitsAllocated/8;
  			int numValues = frameData.Length/bytesAllocated;
  
  			int unusedHighBitsCount = highestBit - highBit;
  			int unusedLowBitsCount = GetLowBit(bitsStored, highBit);
  
  			int signShift = unusedHighBitsCount;
  			int rightAlignShift = unusedLowBitsCount + unusedHighBitsCount;
  
  			rescale = -GetMinPixelValue(bitsStored, true);
  
  			if (bitsStored < 8 && bitsAllocated <= 8)
  			{
  				fixed (byte* pFrameData = frameData)
  				{
  					var pixelData = pFrameData;
  					for (int p = 0; p < numValues; p++, pixelData++)
  					{
  						int pixel = ((sbyte) (*pixelData << signShift)) >> rightAlignShift;
  						*pixelData = (byte) (pixel + rescale);
  					}
  				}
  			}
  			else if (bitsStored == 8 && bitsAllocated <= 8)
  			{
  				fixed (byte* pFrameData = frameData)
  				{
  					var pixelData = pFrameData;
  					for (int p = 0; p < numValues; p++, pixelData++)
  					{
  						int pixel = (sbyte) (*pixelData);
  						*pixelData = (byte) (pixel + rescale);
  					}
  				}
  			}
  			else if (bitsStored < 16)
  			{
  				fixed (byte* pFrameData = frameData)
  				{
  					var pixelData = (ushort*) pFrameData;
  					for (int p = 0; p < numValues; p++, pixelData++)
  					{
  						int pixel = ((short) (*pixelData << signShift)) >> rightAlignShift;
  						*pixelData = (ushort) (pixel + rescale);
  					}
  				}
  			}
  			else
  			{
  				fixed (byte* pFrameData = frameData)
  				{
  					var pixelData = (ushort*) pFrameData;
  					for (int p = 0; p < numValues; p++, pixelData++)
  					{
  						int pixel = (short) (*pixelData);
  						*pixelData = (ushort) (pixel + rescale);
  					}
  				}
  			}
  		}
  
  		/// <summary>
  		/// Change a 8-bit pixel data to a 16-bit buffer
  		/// </summary>
  		public static unsafe byte[] ToggleBitDepth(byte[] frameData, int length, int uncompressedFrameSize, int bitsStored, int bitsAllocated)
  		{
  			if ((bitsStored != 8 || bitsAllocated != 16))
  				throw new DicomCodecUnsupportedSopException("Invalid bits allocated/stored value(s).");
  
  			if (length == uncompressedFrameSize)
  			{
  				var b = new byte[length/2];
  
  				fixed (byte* destFrameData = b)
  				fixed (byte* pFrameData = frameData)
  				{
  					var pixelData = (ushort*) pFrameData;
  					var destData = destFrameData;
  
  					for (int p = 0; p < length/2; p++, pixelData++, destData++)
  					{
  						int pixel = (byte) (*pixelData);
  						*destData = (byte) (pixel);
  					}
  				}
  
  				return b;
  			}
  			else
  			{
  				var b = new byte[length*2];
  
  				fixed (byte* destFrameData = b)
  				fixed (byte* pFrameData = frameData)
  				{
  					var pixelData = pFrameData;
  					var destData = (ushort*) destFrameData;
  
  					for (int p = 0; p < length; p++, pixelData++, destData++)
  					{
  						int pixel = (sbyte) (*pixelData);
  						*destData = (byte) (pixel);
  					}
  				}
  
  				return b;
  			}
  		}
  
  		#endregion
  
  		#region Unused Bit Masking
  
  		internal static bool ZeroUnusedBits(Stream stream, int bitsAllocated, int bitsStored, int highBit)
  		{
  			return ZeroUnusedBits(stream, bitsAllocated, bitsStored, highBit, ByteBuffer.LocalMachineEndian);
  		}
  
  		internal static unsafe bool ZeroUnusedBits(Stream stream, int bitsAllocated, int bitsStored, int highBit, Endian endian)
  		{
  			if (bitsAllocated != 8 && bitsAllocated != 16)
  				throw new ArgumentException(String.Format("Invalid value for Bits Allocated ({0})", bitsAllocated));
  
  			stream.Position = 0;
  
  			var buffer = new byte[4096];
  			var anyChanged = false;
  			int bytesRead;
  
  			if (bitsAllocated == 8)
  			{
  				while ((bytesRead = stream.Read(buffer, 0, 4096)) > 0)
  				{
  					var result = ZeroUnusedBits(buffer, bitsStored, highBit);
  					stream.Seek(-bytesRead, SeekOrigin.Current);
  					stream.Write(buffer, 0, bytesRead);
  					if (!anyChanged && result)
  						anyChanged = true;
  				}
  			}
  			else
  			{
  				while ((bytesRead = stream.Read(buffer, 0, 4096)) > 0)
  				{
  					bool result;
  					fixed (byte* p = buffer)
  						result = ZeroUnusedBits((ushort*) p, bitsStored, highBit, buffer.Length/2, endian);
  					stream.Seek(-bytesRead, SeekOrigin.Current);
  					stream.Write(buffer, 0, bytesRead);
  					if (!anyChanged && result)
  						anyChanged = true;
  				}
  			}
  			return anyChanged;
  		}
  
  		/// <summary>
  		/// Masks
  		/// </summary>
  		/// <param name="pixelData"></param>
  		/// <param name="bitsAllocated"></param>
  		/// <param name="bitsStored"></param>
  		/// <param name="highBit"></param>
  		/// <returns></returns>
  		public static bool ZeroUnusedBits(byte[] pixelData, int bitsAllocated, int bitsStored, int highBit)
  		{
  			return ZeroUnusedBits(pixelData, bitsAllocated, bitsStored, highBit, ByteBuffer.LocalMachineEndian);
  		}
  
  		public static unsafe bool ZeroUnusedBits(byte[] pixelData, int bitsAllocated, int bitsStored, int highBit, Endian endian)
  		{
  			if (bitsAllocated != 8 && bitsAllocated != 16)
  				throw new ArgumentException(String.Format("Invalid value for Bits Allocated ({0})", bitsAllocated));
  
  			if (bitsAllocated == 8)
  				return ZeroUnusedBits(pixelData, bitsStored, highBit);
  
  
gt: 			fixed (byte* p = pixelData)