private static GrayscaleImageGraphic CreateOverlayImageGraphic(OverlayPlane overlayPlaneIod, byte[] overlayData) { Point origin = (overlayPlaneIod.OverlayOrigin ?? new Point(1, 1)) - new Size(1, 1); int rows = overlayPlaneIod.OverlayRows; int cols = overlayPlaneIod.OverlayColumns; if (overlayData == null || overlayData.Length == 0) { return(null); } GrayscaleImageGraphic imageGraphic = new GrayscaleImageGraphic( rows, cols, // the reported overlay dimensions 8, // bits allocated is always 8 8, // overlays always have bit depth of 1, but we upconverted the data 7, // the high bit is now 7 after upconverting false, false, // overlays aren't signed and don't get inverted 1, 0, // overlays have no rescale overlayData); // the upconverted overlay data imageGraphic.SpatialTransform.TranslationX = origin.X; imageGraphic.SpatialTransform.TranslationY = origin.Y; return(imageGraphic); }
public void ChangeSyntax(TransferSyntax syntax) { try { if (!_dicomFile.TransferSyntax.Encapsulated) { // Check if Overlay is embedded in pixels OverlayPlaneModuleIod overlayIod = new OverlayPlaneModuleIod(_dicomFile.DataSet); for (int i = 0; i < 16; i++) { if (overlayIod.HasOverlayPlane(i)) { OverlayPlane overlay = overlayIod[i]; if (overlay.OverlayData == null) { DicomUncompressedPixelData pd = new DicomUncompressedPixelData(_dicomFile); overlay.ConvertEmbeddedOverlay(pd); } } } } else if (syntax.Encapsulated) { // Must decompress first. _dicomFile.ChangeTransferSyntax(TransferSyntax.ExplicitVrLittleEndian); } _dicomFile.ChangeTransferSyntax(syntax); } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception compressing/decompressing DICOM file"); } }
public void SetupMRWithOverlay(DicomAttributeCollection theSet) { SetupMR(theSet); OverlayPlaneModuleIod overlayIod = new OverlayPlaneModuleIod(theSet); DicomUncompressedPixelData pd = new DicomUncompressedPixelData(theSet); OverlayPlane overlay = overlayIod[0]; // Embedded overlays are retired in dicom, just doing it for testing purposes theSet[DicomTags.OverlayBitPosition].SetInt32(0, pd.HighBit + 1); overlay.OverlayBitsAllocated = 1; overlay.OverlayColumns = pd.ImageWidth; overlay.OverlayRows = pd.ImageHeight; overlay.OverlayOrigin = new Point(0, 0); overlay.OverlayType = OverlayType.R; }
public void RleOverlayTest() { DicomFile file = new DicomFile("RleCodecOverlayTest.dcm"); SetupMRWithOverlay(file.DataSet); SetupMetaInfo(file); // Save a copy file.Save(); // Load the file into new DicomFile object DicomFile newFile = new DicomFile(file.Filename); newFile.Load(); OverlayPlaneModuleIod overlayIod = new OverlayPlaneModuleIod(newFile.DataSet); OverlayPlane overlay = overlayIod[0]; // SHould be no OverlayData tag Assert.IsNull(overlay.OverlayData, "Overlay should be in pixel data"); // Overlay should be extracted out of pixel data here newFile.ChangeTransferSyntax(TransferSyntax.RleLossless); Assert.IsNotNull(overlay.OverlayData, "Overlay Data is not null"); newFile.Save(); // Load a new copy newFile = new DicomFile(file.Filename); newFile.Load(); newFile.ChangeTransferSyntax(TransferSyntax.ExplicitVrLittleEndian); newFile.Filename = "Output" + file.Filename; newFile.Save(); List <DicomAttributeComparisonResult> results = new List <DicomAttributeComparisonResult>(); bool compare = file.DataSet.Equals(newFile.DataSet, ref results); // Shouldn't be the same, OverlayData tag should have been added Assert.IsFalse(compare, results.Count > 0 ? CollectionUtils.FirstElement(results).Details : string.Empty); }
/// <summary> /// Determines if the overlay data for this plane is embedded in the pixel data. /// </summary> /// <remarks> /// We cannot use <see cref="OverlayPlane.IsEmbedded"/> because the PixelData attribute is not in the dataset since we stream it separately. /// </remarks> private static bool IsOverlayEmbedded(OverlayPlane overlayPlane) { IDicomAttributeProvider provider = overlayPlane.DicomAttributeProvider; // OverlayData exists => not embedded DicomAttribute overlayData = provider[overlayPlane.TagOffset + DicomTags.OverlayData]; if (overlayData.IsEmpty || overlayData.IsNull) { // embedded => BitsAllocated={8|16}, OverlayBitPosition in [0, BitsAllocated) int overlayBitPosition = provider[overlayPlane.TagOffset + DicomTags.OverlayBitPosition].GetInt32(0, -1); int bitsAllocated = provider[DicomTags.BitsAllocated].GetInt32(0, 0); if (overlayBitPosition >= 0 && overlayBitPosition < bitsAllocated && (bitsAllocated == 8 || bitsAllocated == 16)) { // embedded => OverlayBitPosition in (HighBit, BitsAllocated) or [0, HighBit - BitsStored + 1) int highBit = provider[DicomTags.HighBit].GetInt32(0, 0); int bitsStored = provider[DicomTags.BitsStored].GetInt32(0, 0); return(overlayBitPosition > highBit || overlayBitPosition < highBit - bitsStored + 1); } } return(false); }
protected override byte[] CreateNormalizedOverlayData(int overlayGroupNumber, int overlayFrameNumber) { int frameIndex = overlayFrameNumber - 1; int overlayIndex = overlayGroupNumber - 1; byte[] overlayData = null; OverlayPlaneModuleIod opmi = new OverlayPlaneModuleIod(this.Parent); if (opmi.HasOverlayPlane(overlayIndex)) { OverlayPlane overlayPlane = opmi[overlayIndex]; if (_overlayData[overlayIndex] == null) { if (IsOverlayEmbedded(overlayPlane)) { this.GetNormalizedPixelData(); } else { int bitOffset; overlayPlane.TryComputeOverlayDataBitOffset(frameIndex, out bitOffset); OverlayData od = new OverlayData(bitOffset, overlayPlane.OverlayRows, overlayPlane.OverlayColumns, overlayPlane.IsBigEndianOW, overlayPlane.OverlayData); _overlayData[overlayIndex] = od.Unpack(); } } overlayData = _overlayData[overlayIndex]; } return(overlayData); }
/// <summary> /// Constructs an <see cref="OverlayPlaneGraphic"/> for a single or multi-frame overlay plane using a pre-processed overlay pixel data buffer. /// </summary> /// <remarks> /// <para> /// The <paramref name="overlayPixelData"/> parameter allows for the specification of an alternate source of overlay pixel data, such /// as the unpacked contents of <see cref="DicomTags.OverlayData"/> or the extracted, inflated overlay pixels of <see cref="DicomTags.PixelData"/>. /// Although the format should be 8-bits per pixel, every pixel should either be 0 or 255. This will allow pixel interpolation algorithms /// sufficient range to produce a pleasant image. (If the data was either 0 or 1, regardless of the bit-depth, most interpolation algorithms /// will interpolate 0s for everything in between!) /// </para> /// </remarks> /// <param name="overlayPlaneIod">The IOD object containing properties of the overlay plane.</param> /// <param name="overlayPixelData">The overlay pixel data in 8-bits per pixel format, with each pixel being either 0 or 255.</param> /// <param name="frameIndex">The overlay frame index (0-based). Single-frame overlays should specify 0.</param> /// <param name="source">A value identifying the source of the overlay plane.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="overlayPixelData"/> is NULL or 0-length.</exception> public OverlayPlaneGraphic(OverlayPlane overlayPlaneIod, byte[] overlayPixelData, int frameIndex, OverlayPlaneSource source) { Platform.CheckNonNegative(frameIndex, "frameIndex"); _frameIndex = frameIndex; _index = overlayPlaneIod.Index; _label = overlayPlaneIod.OverlayLabel; _description = overlayPlaneIod.OverlayDescription; _type = overlayPlaneIod.OverlayType; _subtype = (OverlayPlaneSubtype)overlayPlaneIod.OverlaySubtype; _source = source; GrayscaleImageGraphic overlayImageGraphic = CreateOverlayImageGraphic(overlayPlaneIod, overlayPixelData); if (overlayImageGraphic != null) { _overlayGraphic = overlayImageGraphic; this.Color = System.Drawing.Color.PeachPuff; base.Graphics.Add(overlayImageGraphic); } if (string.IsNullOrEmpty(overlayPlaneIod.OverlayLabel)) { if (overlayPlaneIod.IsMultiFrame) { base.Name = string.Format(SR.FormatDefaultMultiFrameOverlayGraphicName, _source, _index, frameIndex); } else { base.Name = string.Format(SR.FormatDefaultSingleFrameOverlayGraphicName, _source, _index, frameIndex); } } else { base.Name = overlayPlaneIod.OverlayLabel; } }
/// <summary> /// Constructs an <see cref="OverlayPlaneGraphic"/> for a single or multi-frame overlay plane using a pre-processed overlay pixel data buffer. /// </summary> /// <remarks> /// <para> /// The <paramref name="overlayPixelData"/> parameter allows for the specification of an alternate source of overlay pixel data, such /// as the unpacked contents of <see cref="DicomTags.OverlayData"/> or the extracted, inflated overlay pixels of <see cref="DicomTags.PixelData"/>. /// Although the format should be 8-bits per pixel, every pixel should either be 0 or 255. This will allow pixel interpolation algorithms /// sufficient range to produce a pleasant image. (If the data was either 0 or 1, regardless of the bit-depth, most interpolation algorithms /// will interpolate 0s for everything in between!) /// </para> /// </remarks> /// <param name="overlayPlaneIod">The IOD object containing properties of the overlay plane.</param> /// <param name="overlayPixelData">The overlay pixel data in 8-bits per pixel format, with each pixel being either 0 or 255.</param> /// <param name="frameIndex">The overlay frame index (0-based). Single-frame overlays should specify 0.</param> /// <param name="source">A value identifying the source of the overlay plane.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="overlayPixelData"/> is NULL or 0-length.</exception> public OverlayPlaneGraphic(OverlayPlane overlayPlaneIod, byte[] overlayPixelData, int frameIndex, OverlayPlaneSource source) { Platform.CheckNonNegative(frameIndex, "frameIndex"); _frameIndex = frameIndex; _index = overlayPlaneIod.Index; _label = overlayPlaneIod.OverlayLabel; _description = overlayPlaneIod.OverlayDescription; _type = overlayPlaneIod.OverlayType; _subtype = (OverlayPlaneSubtype) overlayPlaneIod.OverlaySubtype; _source = source; GrayscaleImageGraphic overlayImageGraphic = CreateOverlayImageGraphic(overlayPlaneIod, overlayPixelData); if (overlayImageGraphic != null) { _overlayGraphic = overlayImageGraphic; this.Color = System.Drawing.Color.PeachPuff; base.Graphics.Add(overlayImageGraphic); } if (string.IsNullOrEmpty(overlayPlaneIod.OverlayLabel)) { if (overlayPlaneIod.IsMultiFrame) base.Name = string.Format(SR.FormatDefaultMultiFrameOverlayGraphicName, _source, _index, frameIndex); else base.Name = string.Format(SR.FormatDefaultSingleFrameOverlayGraphicName, _source, _index, frameIndex); } else { base.Name = overlayPlaneIod.OverlayLabel; } }
/// <summary> /// Constructs an <see cref="OverlayPlaneGraphic"/> for a single-frame overlay plane using a pre-processed overlay pixel data buffer. /// </summary> /// <remarks> /// <para> /// This overload should only be used for single-frame overlay planes. Multi-frame overlay planes should process the overlay data /// into separate buffers and then construct individual graphics using <see cref="OverlayPlaneGraphic(OverlayPlane, byte[], int, OverlayPlaneSource)"/>. /// </para> /// <para> /// The <paramref name="overlayPixelData"/> parameter allows for the specification of an alternate source of overlay pixel data, such /// as the unpacked contents of <see cref="DicomTags.OverlayData"/> or the extracted, inflated overlay pixels of <see cref="DicomTags.PixelData"/>. /// Although the format should be 8-bits per pixel, every pixel should either be 0 or 255. This will allow pixel interpolation algorithms /// sufficient range to produce a pleasant image. (If the data was either 0 or 1, regardless of the bit-depth, most interpolation algorithms /// will interpolate 0s for everything in between!) /// </para> /// </remarks> /// <param name="overlayPlaneIod">The IOD object containing properties of the overlay plane.</param> /// <param name="overlayPixelData">The overlay pixel data in 8-bits per pixel format, with each pixel being either 0 or 255.</param> /// <param name="source">A value identifying the source of the overlay plane.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="overlayPixelData"/> is NULL or 0-length.</exception> public OverlayPlaneGraphic(OverlayPlane overlayPlaneIod, byte[] overlayPixelData, OverlayPlaneSource source) : this(overlayPlaneIod, overlayPixelData, 0, source) {}
private static GrayscaleImageGraphic CreateOverlayImageGraphic(OverlayPlane overlayPlaneIod, byte[] overlayData) { Point origin = (overlayPlaneIod.OverlayOrigin ?? new Point(1, 1)) - new Size(1, 1); int rows = overlayPlaneIod.OverlayRows; int cols = overlayPlaneIod.OverlayColumns; if (overlayData == null || overlayData.Length == 0) return null; GrayscaleImageGraphic imageGraphic = new GrayscaleImageGraphic( rows, cols, // the reported overlay dimensions 8, // bits allocated is always 8 8, // overlays always have bit depth of 1, but we upconverted the data 7, // the high bit is now 7 after upconverting false, false, // overlays aren't signed and don't get inverted 1, 0, // overlays have no rescale overlayData); // the upconverted overlay data imageGraphic.SpatialTransform.TranslationX = origin.X; imageGraphic.SpatialTransform.TranslationY = origin.Y; return imageGraphic; }
public void TestConversion_MultiframeEmbedded() { const bool bigEndian = false; // these parameters should be kept prime numbers so that we can exercise the overlay handling for rows/frames that cross byte boundaries const int rows = 97; const int columns = 101; const int frames = 7; const int overlayIndex = 0; var overlayData = new bool[rows*columns*frames]; for (int i = 0; i < frames; i++) { overlayData[(i * rows*columns) + 1] = true; overlayData[(i * rows * columns) + 10] = true; overlayData[(i * rows * columns) + 100] = true; overlayData[(i * rows * columns) + 225] = true; overlayData[(i * rows * columns) + 1000] = true; overlayData[(i * rows * columns) + 1001] = true; overlayData[(i * rows * columns) + (rows * columns) - 1] = true; } var dataset = new DicomAttributeCollection(); SetImage(dataset, new byte[rows * columns * 2 * frames], rows, columns, frames, 16, 12, 11, false); SetOverlay(dataset, overlayIndex, overlayData, OverlayType.G, new Point(1, 1), 12, bigEndian); DicomUncompressedPixelData pd = new DicomUncompressedPixelData(dataset); OverlayPlane overlayPlane = new OverlayPlane(overlayIndex, dataset); Assert.IsTrue(overlayPlane.ExtractEmbeddedOverlay(pd), "Failed to convert to Non-Embedded overlay plane."); int actualOverlayFrame; Assert.IsFalse(overlayPlane.TryGetRelevantOverlayFrame(-1, frames, out actualOverlayFrame), "Should not any matching overlay frame for image frame #-1"); Assert.IsFalse(overlayPlane.TryGetRelevantOverlayFrame(0, frames, out actualOverlayFrame), "Should not any matching overlay frame for image frame #0"); Assert.IsFalse(overlayPlane.TryGetRelevantOverlayFrame(8, frames, out actualOverlayFrame), "Should not any matching overlay frame for image frame #8"); // all valid image frame inputs should map 1-to-1 with the same numbered overlay frame Assert.IsTrue(overlayPlane.TryGetRelevantOverlayFrame(1, frames, out actualOverlayFrame), "Should be able to match an overlay frame to image frame #1"); Assert.AreEqual(1, actualOverlayFrame, "Wrong overlay frame matched to image frame #1"); Assert.IsTrue(overlayPlane.TryGetRelevantOverlayFrame(2, frames, out actualOverlayFrame), "Should be able to match an overlay frame to image frame #2"); Assert.AreEqual(2, actualOverlayFrame, "Wrong overlay frame matched to image frame #2"); Assert.IsTrue(overlayPlane.TryGetRelevantOverlayFrame(3, frames, out actualOverlayFrame), "Should be able to match an overlay frame to image frame #3"); Assert.AreEqual(3, actualOverlayFrame, "Wrong overlay frame matched to image frame #3"); Assert.IsTrue(overlayPlane.TryGetRelevantOverlayFrame(4, frames, out actualOverlayFrame), "Should be able to match an overlay frame to image frame #4"); Assert.AreEqual(4, actualOverlayFrame, "Wrong overlay frame matched to image frame #4"); Assert.IsTrue(overlayPlane.TryGetRelevantOverlayFrame(5, frames, out actualOverlayFrame), "Should be able to match an overlay frame to image frame #5"); Assert.AreEqual(5, actualOverlayFrame, "Wrong overlay frame matched to image frame #5"); Assert.IsTrue(overlayPlane.TryGetRelevantOverlayFrame(6, frames, out actualOverlayFrame), "Should be able to match an overlay frame to image frame #6"); Assert.AreEqual(6, actualOverlayFrame, "Wrong overlay frame matched to image frame #6"); Assert.IsTrue(overlayPlane.TryGetRelevantOverlayFrame(7, frames, out actualOverlayFrame), "Should be able to match an overlay frame to image frame #7"); Assert.AreEqual(7, actualOverlayFrame, "Wrong overlay frame matched to image frame #7"); }
/// <summary> /// Constructs an <see cref="OverlayPlaneGraphic"/> for a single-frame overlay plane using a pre-processed overlay pixel data buffer. /// </summary> /// <remarks> /// <para> /// This overload should only be used for single-frame overlay planes. Multi-frame overlay planes should process the overlay data /// into separate buffers and then construct individual graphics using <see cref="OverlayPlaneGraphic(OverlayPlane, byte[], int, OverlayPlaneSource)"/>. /// </para> /// <para> /// The <paramref name="overlayPixelData"/> parameter allows for the specification of an alternate source of overlay pixel data, such /// as the unpacked contents of <see cref="DicomTags.OverlayData"/> or the extracted, inflated overlay pixels of <see cref="DicomTags.PixelData"/>. /// Although the format should be 8-bits per pixel, every pixel should either be 0 or 255. This will allow pixel interpolation algorithms /// sufficient range to produce a pleasant image. (If the data was either 0 or 1, regardless of the bit-depth, most interpolation algorithms /// will interpolate 0s for everything in between!) /// </para> /// </remarks> /// <param name="overlayPlaneIod">The IOD object containing properties of the overlay plane.</param> /// <param name="overlayPixelData">The overlay pixel data in 8-bits per pixel format, with each pixel being either 0 or 255.</param> /// <param name="source">A value identifying the source of the overlay plane.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="overlayPixelData"/> is NULL or 0-length.</exception> public OverlayPlaneGraphic(OverlayPlane overlayPlaneIod, byte[] overlayPixelData, OverlayPlaneSource source) : this(overlayPlaneIod, overlayPixelData, 0, source) { }
protected void SerializeOverlayPlane(OverlayPlaneModuleIod overlayPlaneModule, out IOverlayMapping overlayMapping, DicomPresentationImageCollection <T> images) { // Doesn't support multiframe or whatever case it is when we get more than one image serialized to one state List <OverlayPlaneGraphic> visibleOverlays = new List <OverlayPlaneGraphic>(); foreach (T image in images) { DicomGraphicsPlane dicomGraphics = DicomGraphicsPlane.GetDicomGraphicsPlane(image, false); if (dicomGraphics != null) { // identify visible bitmap shutter if exists OverlayPlaneGraphic bitmapShutter = dicomGraphics.Shutters.ActiveShutter as OverlayPlaneGraphic; if (bitmapShutter != null) { visibleOverlays.Add(bitmapShutter); } // identify any visible overlays visibleOverlays.AddRange(((IEnumerable <ILayer>)dicomGraphics.Layers).Where(l => l.Visible).SelectMany(l => l.Graphics).OfType <OverlayPlaneGraphic>().Where(g => g.Visible)); } } OverlayMapping overlayMap = new OverlayMapping(); Queue <OverlayPlaneGraphic> overlaysToRemap = new Queue <OverlayPlaneGraphic>(); // user and presentation state overlays are high priority items to remap foreach (OverlayPlaneGraphic overlay in CollectionUtils.Select(visibleOverlays, delegate(OverlayPlaneGraphic t) { return(t.Source != OverlayPlaneSource.Image); })) { overlaysToRemap.Enqueue(overlay); } foreach (OverlayPlaneGraphic overlay in CollectionUtils.Select(visibleOverlays, delegate(OverlayPlaneGraphic t) { return(t.Source == OverlayPlaneSource.Image); })) { if (overlayMap[overlay.Index] == null) { overlayMap[overlay.Index] = overlay; } else { overlaysToRemap.Enqueue(overlay); // image overlays are lower priority items to remap, since they will be included in the header anyway } } // seed the overlays to remap into the remaining available overlay groups for (int n = 0; n < 16 && overlaysToRemap.Count > 0; n++) { if (overlayMap[n] == null) { overlayMap[n] = overlaysToRemap.Dequeue(); } } // serialize the overlays for (int n = 0; n < 16; n++) { OverlayPlaneGraphic overlay = overlayMap[n]; if (overlay != null) { if (overlay.Source != OverlayPlaneSource.Image || overlay.Index != n) { // only record this overlay in the presentation state if it is being remapped to another group or is not already in the image. OverlayPlane overlayIod = overlayPlaneModule[n]; overlayIod.OverlayData = overlay.CreateOverlayData(overlayIod.IsBigEndianOW).Raw; overlayIod.OverlayBitPosition = 0; overlayIod.OverlayBitsAllocated = 1; overlayIod.OverlayColumns = overlay.Columns; overlayIod.OverlayDescription = overlay.Description; overlayIod.OverlayLabel = overlay.Label; overlayIod.OverlayOrigin = Point.Round(overlay.Origin); overlayIod.OverlayRows = overlay.Rows; overlayIod.OverlaySubtype = overlay.Subtype; overlayIod.OverlayType = overlay.Type; overlayIod.RoiArea = null; overlayIod.RoiMean = null; overlayIod.RoiStandardDeviation = null; } else { overlayPlaneModule.Delete(n); } } } if (overlaysToRemap.Count > 0) { Platform.Log(LogLevel.Warn, "Attempt to serialize presentation state with more than 16 visible overlays - some information may be lost."); } overlayMapping = overlayMap; }