public override void Decode(DicomPixelData oldPixelData, DicomPixelData newPixelData, DicomCodecParams parameters) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { throw new InvalidOperationException("Unsupported OS Platform"); } for (int frame = 0; frame < oldPixelData.NumberOfFrames; frame++) { IByteBuffer jpegData = oldPixelData.GetFrame(frame); //Converting photmetricinterpretation YbrFull or YbrFull422 to RGB if (oldPixelData.PhotometricInterpretation == PhotometricInterpretation.YbrFull) { jpegData = PixelDataConverter.YbrFullToRgb(jpegData); oldPixelData.PhotometricInterpretation = PhotometricInterpretation.Rgb; } else if (oldPixelData.PhotometricInterpretation == PhotometricInterpretation.YbrFull422) { jpegData = PixelDataConverter.YbrFull422ToRgb(jpegData, oldPixelData.Width); oldPixelData.PhotometricInterpretation = PhotometricInterpretation.Rgb; } PinnedByteArray jpegArray = new PinnedByteArray(jpegData.Data); byte[] frameData = new byte[newPixelData.UncompressedFrameSize]; PinnedByteArray frameArray = new PinnedByteArray(frameData); JlsParameters jls = new JlsParameters(); char[] errorMessage = new char[256]; // IMPORT JpegLsDecode unsafe { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { CharlsApiResultType err = JpegLSDecode_Linux64((void *)frameArray.Pointer, frameData.Length, (void *)jpegArray.Pointer, Convert.ToUInt32(jpegData.Size), ref jls, errorMessage); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { CharlsApiResultType err = JpegLSDecode_Windows64((void *)frameArray.Pointer, frameData.Length, (void *)jpegArray.Pointer, Convert.ToUInt32(jpegData.Size), ref jls, errorMessage); } IByteBuffer buffer; if (frameData.Length >= (1 * 1024 * 1024) || oldPixelData.NumberOfFrames > 1) { buffer = new TempFileBuffer(frameData); } else { buffer = new MemoryByteBuffer(frameData); } buffer = EvenLengthBuffer.Create(buffer); newPixelData.AddFrame(buffer); } } }
public void Data_CompareWithInitializer_ExactMatch() { // Arrange var bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7 }; var expected = new byte[] { 1, 2, 3, 4, 5, 6, 7, 0 }; var buffer = EvenLengthBuffer.Create(new MemoryByteBuffer(bytes)); // Act var actual = buffer.Data; // Assert Assert.Equal(expected.Length, actual.Length); Assert.Equal(expected, actual); }
/// <summary> /// Creates a DICOM overlay from a GDI+ Bitmap. /// </summary> /// <param name="ds">Dataset</param> /// <param name="bitmap">Bitmap</param> /// <param name="mask">Color mask for overlay</param> /// <returns>DICOM overlay</returns> public static DicomOverlayData FromBitmap(DicomDataset ds, Bitmap bitmap, Color mask) { ushort group = 0x6000; while (ds.Contains(new DicomTag(group, DicomTag.OverlayBitPosition.Element))) { group += 2; } var overlay = new DicomOverlayData(ds, group); overlay.Type = DicomOverlayType.Graphics; overlay.Rows = bitmap.Height; overlay.Columns = bitmap.Width; overlay.OriginX = 1; overlay.OriginY = 1; overlay.BitsAllocated = 1; overlay.BitPosition = 1; var count = overlay.Rows * overlay.Columns / 8; if ((overlay.Rows * overlay.Columns) % 8 > 0) { count++; } var array = new BitList(); array.Capacity = overlay.Rows * overlay.Columns; int p = 0; for (int y = 0; y < bitmap.Height; y++) { for (int x = 0; x < bitmap.Width; x++, p++) { if (bitmap.GetPixel(x, y).ToArgb() == mask.ToArgb()) { array[p] = true; } } } overlay.Data = EvenLengthBuffer.Create( new MemoryByteBuffer(array.Array)); return(overlay); }
public void CopyToStream_ShouldWorkCorrectly() { // Arrange var bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7 }; var expected = new byte[] { 1, 2, 3, 4, 5, 6, 7, 0 }; var evenLengthBuffer = EvenLengthBuffer.Create(new MemoryByteBuffer(bytes)); using var ms = new MemoryStream(new byte[8]); // Act evenLengthBuffer.CopyToStream(ms); var actual = ms.ToArray(); // Assert Assert.Equal(expected, actual); }
public void GetByteRange_WithOffset_ToEnd_ShouldReturnValidArray(int offset, int count) { // Arrange var bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7 }; var expected = new byte[] { 1, 2, 3, 4, 5, 6, 7, 0 }; var buffer = EvenLengthBuffer.Create(new MemoryByteBuffer(bytes)); // Act var actual = new byte[count]; buffer.GetByteRange(offset, count, actual); // Assert Assert.Equal(count, actual.Length); Assert.Equal(expected.Skip(offset).Take(count).ToArray(), actual); }
public async Task CopyToStreamAsync_ShouldWorkCorrectly() { // Arrange var bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7 }; var expected = new byte[] { 1, 2, 3, 4, 5, 6, 7, 0 }; var evenLengthBuffer = EvenLengthBuffer.Create(new MemoryByteBuffer(bytes)); using var ms = new MemoryStream(new byte[8]); // Act await evenLengthBuffer.CopyToStreamAsync(ms, CancellationToken.None).ConfigureAwait(false); var actual = ms.ToArray(); // Assert Assert.Equal(expected, actual); }
/// <summary> /// The JPEG-LS decoding function /// </summary> /// <param name="oldPixelData">The old pixel data</param> /// <param name="newPixelData">The new pixel data</param> /// <param name="parameters">The compression parameters</param> public override void Decode(DicomPixelData oldPixelData, DicomPixelData newPixelData, DicomCodecParams parameters) { for (var frame = 0; frame < oldPixelData.NumberOfFrames; frame++) { var jpegLsData = oldPixelData.GetFrame(frame); var message = String.Empty; var jpegLsParams = new JlsParameters(); var frameData = new byte[newPixelData.UncompressedFrameSize]; var err = JpegLs.Decode(frameData, jpegLsData.Data, jpegLsParams, out message); var buffer = frameData.Length >= 1 * 1024 * 1024 || oldPixelData.NumberOfFrames > 1 ? (IByteBuffer) new TempFileBuffer(frameData) : new MemoryByteBuffer(frameData); newPixelData.AddFrame(EvenLengthBuffer.Create(buffer)); } }
/// <summary> /// Creates a DICOM overlay from a GDI+ Bitmap. /// </summary> /// <param name="ds">Dataset</param> /// <param name="bitmap">Bitmap</param> /// <param name="mask">Color mask for overlay</param> /// <returns>DICOM overlay</returns> public static DicomOverlayData FromBitmap(DicomDataset ds, IImage bitmap, Color32 mask) { ushort group = 0x6000; while (ds.Contains(new DicomTag(group, DicomTag.OverlayBitPosition.Element))) { group += 2; } var overlay = new DicomOverlayData(ds, group) { Type = DicomOverlayType.Graphics, Rows = bitmap.Height, Columns = bitmap.Width, OriginX = 1, OriginY = 1, BitsAllocated = 1, BitPosition = 1 }; var array = new BitList { Capacity = overlay.Rows * overlay.Columns }; int p = 0; for (var y = 0; y < bitmap.Height; y++) { for (var x = 0; x < bitmap.Width; x++, p++) { if (bitmap.GetPixel(x, y).Value == mask.Value) { array[p] = true; } } } overlay.Data = EvenLengthBuffer.Create(new MemoryByteBuffer(array.Array)); return(overlay); }
/// <summary> /// Performs the actual decoding /// </summary> /// <param name="inputFile">The Hologic SCO input file</param> /// <param name="outputFile">The DICOM BTO output file</param> static void DecodeScoToBto(String inputFile, String outputFile) { // Check for a valid DICOM header if (!DicomFile.HasValidHeader(inputFile)) { throw new Exception("Input file doesn't seem to have a valid DICOM header"); } // Open the input file and get the DICOM dataset var dicomFile = DicomFile.Open(inputFile); var originalDataset = dicomFile.Dataset; // Get the private sequence tag that contains the full resolution pixels var privateTagFullRes = originalDataset.GetPrivateTag(new DicomTag(0x7E01, 0x1010, "HOLOGIC, Inc.")); var privateTagFullResSequence = originalDataset.GetDicomItem <DicomSequence>(privateTagFullRes); if (privateTagFullResSequence == null || privateTagFullResSequence.Items == null || privateTagFullResSequence.Items.Count == 0) { throw new Exception("Input file doesn't seem to contain the required private tags (0x7E01, 0x1010)"); } var memoryStream = new MemoryStream(); var binaryWriter = new BinaryWriter(memoryStream); // Concatenate the pixel tags from the private sequence items foreach (var privateTagFullResSequenceItem in privateTagFullResSequence.Items) { var privateTagFullResDataTag = privateTagFullResSequenceItem.GetPrivateTag(new DicomTag(0x7E01, 0x1012, "HOLOGIC, Inc.")); var privateTagFullResData = privateTagFullResSequenceItem.GetDicomItem <DicomOtherByte>(privateTagFullResDataTag); if (privateTagFullResData == null) { throw new Exception( "Input file doesn't seem to contain the required private tags (0x7E01, 0x1012)"); } binaryWriter.Write(privateTagFullResData.Buffer.Data); } binaryWriter.Flush(); var binaryReader = new BinaryReader(memoryStream); // Read the frame count from the private data (Offset: 20) binaryReader.BaseStream.Seek(20, SeekOrigin.Begin); var frameCount = binaryReader.ReadUInt16(); // Read the columns from the private data (Offset: 24) binaryReader.BaseStream.Seek(24, SeekOrigin.Begin); var columns = binaryReader.ReadUInt16(); // Read the rows from the private data (Offset: 28) binaryReader.BaseStream.Seek(28, SeekOrigin.Begin); var rows = binaryReader.ReadUInt16(); // Read the bits stored from the private data (Offset: 32) binaryReader.BaseStream.Seek(32, SeekOrigin.Begin); var bitsStored = binaryReader.ReadByte(); // Read the allowed lossy error from the private data (Offset: 36) binaryReader.BaseStream.Seek(36, SeekOrigin.Begin); var near = binaryReader.ReadByte(); // Read the frame data indexes from the private data // The indexes are starting at 1024 bytes before the end of the private data var frameIndexes = new List <int>(); binaryReader.BaseStream.Seek(binaryReader.BaseStream.Length - 1024, SeekOrigin.Begin); for (var i = 0; i < frameCount; i++) { frameIndexes.Add(binaryReader.ReadInt32()); } frameIndexes.Add(binaryReader.ReadInt32()); // Extract the frame data using the read indexes var frames = new List <byte[]>(); for (var i = 0; i < frameCount; i++) { binaryReader.BaseStream.Seek(frameIndexes[i], SeekOrigin.Begin); var bytesToRead = frameIndexes[i + 1] - frameIndexes[i] - 1; var frameBytes = binaryReader.ReadBytes(bytesToRead); frames.Add(frameBytes); } // Dispose the readers/writers binaryReader.Dispose(); binaryWriter.Dispose(); memoryStream.Dispose(); // Create a new dataset // In the case the allowed lossy error is zero create a lossless dataset var newDataset = new DicomDataset( near == 0 ? DicomTransferSyntax.JPEGLSLossless : DicomTransferSyntax.JPEGLSNearLossless); // Copy all items excluding private tags and pixel data (if they exist) foreach (var dicomItem in originalDataset) { if (!dicomItem.Tag.IsPrivate && dicomItem.Tag != DicomTag.PixelData) { newDataset.Add(dicomItem); } } // New SOP newDataset.AddOrUpdate(DicomTag.SOPClassUID, DicomUID.BreastTomosynthesisImageStorage); newDataset.AddOrUpdate(DicomTag.SOPInstanceUID, DicomUID.Generate()); // New rendering params newDataset.AddOrUpdate <ushort>(DicomTag.Columns, columns); newDataset.AddOrUpdate <ushort>(DicomTag.Rows, rows); newDataset.AddOrUpdate <ushort>(DicomTag.BitsAllocated, 16); newDataset.AddOrUpdate <ushort>(DicomTag.BitsStored, bitsStored); newDataset.AddOrUpdate <ushort>(DicomTag.HighBit, (ushort)(bitsStored - 1)); newDataset.AddOrUpdate(DicomTag.PhotometricInterpretation, PhotometricInterpretation.Monochrome2.Value); // New pixel data var pixelData = DicomPixelData.Create(newDataset, true); // Iterate through all frames, construct a new JPEG-LS frame // and append it as a pixel fragment in the new dataset for (var i = 0; i < frameCount; i++) { using (var frameMemoryStream = new MemoryStream()) { using (var frameBinaryWriter = new BinaryWriter(frameMemoryStream)) { // Create the JPEG-LS header // Start of image (SOI) marker frameBinaryWriter.Write(new byte[] { 0xFF, 0xD8 }); // Start of JPEG-LS frame (SOF55) marker – marker segment follows frameBinaryWriter.Write(new byte[] { 0xFF, 0xF7 }); // Length of marker segment = 11 bytes including the length field frameBinaryWriter.Write(new byte[] { 0x00, 0x0B }); // P = Precision frameBinaryWriter.Write(bitsStored); // Y = Number of lines (big endian) frameBinaryWriter.Write(SwapBytes(rows)); // X = Number of columns (big endian) frameBinaryWriter.Write(SwapBytes(columns)); // Nf = Number of components in the frame = 1 frameBinaryWriter.Write((byte)0x01); // C1 = Component ID = 1 (first and only component) frameBinaryWriter.Write((byte)0x01); // Sub-sampling: H1 = 1, V1 = 1 frameBinaryWriter.Write((byte)0x11); // Tq1 = 0 (this field is always 0) frameBinaryWriter.Write((byte)0x00); // Start of scan (SOS) marker frameBinaryWriter.Write(new byte[] { 0xFF, 0xDA }); // Length of marker segment = 8 bytes including the length field frameBinaryWriter.Write(new byte[] { 0x00, 0x08 }); // Ns = Number of components for this scan = 1 frameBinaryWriter.Write((byte)0x01); // Ci = Component ID = 1 frameBinaryWriter.Write((byte)0x01); // Tm1 = Mapping table index = 0 (no mapping table) frameBinaryWriter.Write((byte)0x00); // NEAR frameBinaryWriter.Write(near); // ILV = 0 (interleave mode = non-interleaved) frameBinaryWriter.Write((byte)0x00); // Al = 0, Ah = 0 (no point transform) frameBinaryWriter.Write((byte)0x00); // Append the extracted frame data // Frame data frameBinaryWriter.Write(frames[i]); // Close the JPEG-LS frame // End of image (EOI) marker frameBinaryWriter.Write(new byte[] { 0xFF, 0xD9 }); frameBinaryWriter.Flush(); var frameBytes = frameMemoryStream.ToArray(); // Add the fragment pixelData.AddFrame(EvenLengthBuffer.Create(new MemoryByteBuffer(frameBytes))); } } } // Decompress the new DICOM file to Explicit Little Endian // This step is performed because the resulting JPEG-LS codestream cannot be decoded // on several viewers (_validBits are equal to zero at the end of the decoding process, needs more investigation...) // For this reason, the application is performing the decompression, silencing the decoding errors // and producing valid multi-frame part10 DICOM files. var decompressedDataset = newDataset.Clone(DicomTransferSyntax.ExplicitVRLittleEndian); // Create a new DICOM file object from the decompress dataset var newDicomFile = new DicomFile(decompressedDataset); // Persist the decompressed DICOM file to disk newDicomFile.Save(outputFile); }
public override unsafe void Encode(DicomPixelData oldPixelData, DicomPixelData newPixelData, DicomCodecParams parameters) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { throw new InvalidOperationException("Unsupported OS Platform"); } if ((oldPixelData.PhotometricInterpretation == PhotometricInterpretation.YbrPartial422) || (oldPixelData.PhotometricInterpretation == PhotometricInterpretation.YbrPartial420)) { throw new DicomCodecException("Photometric Interpretation '{0}' not supported by JPEG-LS encoder", oldPixelData.PhotometricInterpretation); } DicomJpegLsParams jparams = (DicomJpegLsParams)parameters; if (jparams == null) { jparams = (DicomJpegLsParams)GetDefaultParameters(); } //IMPORT JLSPARAMETERS (DLLIMPORT) JlsParameters jls = new JlsParameters { width = oldPixelData.Width, height = oldPixelData.Height, bitsPerSample = oldPixelData.BitsStored, stride = oldPixelData.BytesAllocated * oldPixelData.Width * oldPixelData.SamplesPerPixel, components = oldPixelData.SamplesPerPixel, interleaveMode = oldPixelData.SamplesPerPixel == 1 ? CharlsInterleaveModeType.None : oldPixelData.PlanarConfiguration == PlanarConfiguration.Interleaved ? CharlsInterleaveModeType.Sample : CharlsInterleaveModeType.Line, colorTransformation = CharlsColorTransformationType.None }; if (TransferSyntax == DicomTransferSyntax.JPEGLSNearLossless) { jls.allowedLossyError = jparams.AllowedError; } for (int frame = 0; frame < oldPixelData.NumberOfFrames; frame++) { IByteBuffer frameData = oldPixelData.GetFrame(frame); //Converting photmetricinterpretation YbrFull or YbrFull422 to RGB if (oldPixelData.PhotometricInterpretation == PhotometricInterpretation.YbrFull) { frameData = PixelDataConverter.YbrFullToRgb(frameData); oldPixelData.PhotometricInterpretation = PhotometricInterpretation.Rgb; } else if (oldPixelData.PhotometricInterpretation == PhotometricInterpretation.YbrFull422) { frameData = PixelDataConverter.YbrFull422ToRgb(frameData, oldPixelData.Width); oldPixelData.PhotometricInterpretation = PhotometricInterpretation.Rgb; } PinnedByteArray frameArray = new PinnedByteArray(frameData.Data); byte[] jpegData = new byte[frameData.Size]; PinnedByteArray jpegArray = new PinnedByteArray(jpegData); uint jpegDataSize = 0; char[] errorMessage = new char[256]; // IMPORT JpegLsEncode unsafe { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { CharlsApiResultType err = JpegLSEncode_Linux64((void *)jpegArray.Pointer, checked ((uint)jpegArray.Count), &jpegDataSize, (void *)frameArray.Pointer, checked ((uint)frameArray.Count), ref jls, errorMessage); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { CharlsApiResultType err = JpegLSEncode_Windows64((void *)jpegArray.Pointer, checked ((uint)jpegArray.Count), &jpegDataSize, (void *)frameArray.Pointer, checked ((uint)frameArray.Count), ref jls, errorMessage); } Array.Resize(ref jpegData, (int)jpegDataSize); IByteBuffer buffer; if (jpegDataSize >= (1 * 1024 * 1024) || oldPixelData.NumberOfFrames > 1) { buffer = new TempFileBuffer(jpegData); } else { buffer = new MemoryByteBuffer(jpegData); } buffer = EvenLengthBuffer.Create(buffer); newPixelData.AddFrame(buffer); } } }