// Copyright (c) 2012-2021 fo-dicom contributors. // Licensed under the Microsoft Public License (MS-PL). using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using FellowOakDicom.IO.Buffer; namespace FellowOakDicom.Samples.Compare { public partial class MainForm : Form { private static readonly Color _none = Color.Transparent; private static readonly Color _green = Color.FromArgb(190, 240, 190); private static readonly Color _yellow = Color.FromArgb(255, 255, 217); private static readonly Color _red = Color.FromArgb(255, 200, 200); private static readonly Color _gray = Color.FromArgb(200, 200, 200); private DicomFile _file1; private DicomFile _file2; private int _level = 0; private string _indent = string.Empty; public MainForm() { InitializeComponent(); } private void MainForm_Load(object sender, EventArgs e) { } public int Level { get => _level; set { _level = value; _indent = "".PadRight(_level * 4); } } private void OnClickSelect(object sender, EventArgs e) { DicomFile file1; while (true) { var ofd = new OpenFileDialog { Title = "Choose first DICOM file", Filter = "DICOM Files (*.dcm;*.dic)|*.dcm;*.dic|All Files (*.*)|*.*" }; if (ofd.ShowDialog(this) == DialogResult.Cancel) { return; } try { file1 = DicomFile.Open(ofd.FileName); break; } catch (Exception ex) { MessageBox.Show( this, ex.Message, "Error opening DICOM file", MessageBoxButtons.OK, MessageBoxIcon.Error); } } DicomFile file2; while (true) { var ofd = new OpenFileDialog { Title = "Choose second DICOM file", Filter = "DICOM Files (*.dcm;*.dic)|*.dcm;*.dic|All Files (*.*)|*.*" }; if (ofd.ShowDialog(this) == DialogResult.Cancel) { return; } try { file2 = DicomFile.Open(ofd.FileName); break; } catch (Exception ex) { MessageBox.Show( this, ex.Message, "Error opening DICOM file", MessageBoxButtons.OK, MessageBoxIcon.Error); } } _file1 = file1; _file2 = file2; lblFile1.Text = _file1.File.Name; lblFile2.Text = _file2.File.Name; CompareFiles(); } private void CompareFiles() { Level = 0; try { lvFile1.BeginUpdate(); lvFile2.BeginUpdate(); lvFile1.Items.Clear(); lvFile2.Items.Clear(); CompareDatasets(_file1.FileMetaInfo, _file2.FileMetaInfo); CompareDatasets(_file1.Dataset, _file2.Dataset); OnSizeChanged(lvFile1, EventArgs.Empty); OnSizeChanged(lvFile2, EventArgs.Empty); } catch (Exception ex) { MessageBox.Show( this, ex.Message, "Error comparing DICOM files", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { lvFile1.EndUpdate(); lvFile2.EndUpdate(); } } private void CompareDatasets(DicomDataset d1, DicomDataset d2) { var e1 = new Queue(d1 ?? new DicomDataset()); var e2 = new Queue(d2 ?? new DicomDataset()); while (e1.Count > 0 || e2.Count > 0) { DicomItem i1 = null; if (e1.Count > 0) { i1 = e1.Peek(); } DicomItem i2 = null; if (e2.Count > 0) { i2 = e2.Peek(); } if (i1 != null && i2 != null) { if (i1.Tag.Group < i2.Tag.Group) { AddItem(i1, null); e1.Dequeue(); continue; } if (i1.Tag.Group == i2.Tag.Group && i1.Tag.Element < i2.Tag.Element) { AddItem(i1, null); e1.Dequeue(); continue; } } if (i2 != null && i1 != null) { if (i2.Tag.Group < i1.Tag.Group) { AddItem(null, i2); e2.Dequeue(); continue; } if (i2.Tag.Group == i1.Tag.Group && i2.Tag.Element < i1.Tag.Element) { AddItem(null, i2); e2.Dequeue(); continue; } } AddItem(i1, i2); if (i1 != null) { e1.Dequeue(); } if (i2 != null) { e2.Dequeue(); } } } private void CompareSequences(DicomSequence s1, DicomSequence s2) { if (s1 == null) { AddItem(s1, lvFile1, _gray); AddItem(s2, lvFile2, _green); } else if (s2 == null) { AddItem(s1, lvFile1, _green); AddItem(s2, lvFile2, _gray); } else { AddItem(s1, lvFile1, _none); AddItem(s2, lvFile2, _none); } Level++; int count = 0; if (s1 != null) { count = s1.Items.Count; } if (s2 != null && s2.Items.Count > count) { count = s2.Items.Count; } for (int i = 0; i < count; i++) { DicomDataset d1 = null; if (s1 != null && i < s1.Items.Count) { d1 = s1.Items[i]; } DicomDataset d2 = null; if (s2 != null && i < s2.Items.Count) { d2 = s2.Items[i]; } if (d1 == null) { AddItem(string.Empty, uint.MaxValue, string.Empty, lvFile1, _gray); AddItem(GetTagName(DicomTag.Item), uint.MaxValue, string.Empty, lvFile2, _green); } else if (d2 == null) { AddItem(GetTagName(DicomTag.Item), uint.MaxValue, string.Empty, lvFile1, _green); AddItem(string.Empty, uint.MaxValue, string.Empty, lvFile2, _gray); } else { AddItem(GetTagName(DicomTag.Item), uint.MaxValue, string.Empty, lvFile1, _none); AddItem(GetTagName(DicomTag.Item), uint.MaxValue, string.Empty, lvFile2, _none); } Level++; CompareDatasets(d1, d2); Level--; } Level--; } private void CompareFragments(DicomItem i1, DicomItem i2) { DicomFragmentSequence s1 = null; DicomFragmentSequence s2 = null; bool pixel = cbIgnorePixelData.Checked && i1.Tag == DicomTag.PixelData; if (i1 == null) { AddItem(i1, lvFile1, _gray); AddItem(i2, lvFile2, _green); s2 = i2 as DicomFragmentSequence; } else if (i2 == null) { AddItem(i1, lvFile1, _green); AddItem(i2, lvFile2, _gray); s1 = i1 as DicomFragmentSequence; } else if (!(i1 is DicomFragmentSequence)) { AddItem(i1, lvFile1, pixel ? _yellow : _red); AddItem(i2, lvFile2, pixel ? _yellow : _red); s2 = i2 as DicomFragmentSequence; } else if (!(i2 is DicomFragmentSequence)) { AddItem(i1, lvFile1, pixel ? _yellow : _red); AddItem(i2, lvFile2, pixel ? _yellow : _red); s1 = i1 as DicomFragmentSequence; } else { AddItem(i1, lvFile1, pixel ? _yellow : _none); AddItem(i2, lvFile2, pixel ? _yellow : _none); s1 = i1 as DicomFragmentSequence; s2 = i2 as DicomFragmentSequence; } Level++; if (s1 == null) { AddItem(string.Empty, uint.MaxValue, string.Empty, lvFile1, _gray); AddItem( _indent + "Offset Table", (uint)s2.OffsetTable.Count * 4, string.Format("@entries={0}", s2.OffsetTable.Count), lvFile2, pixel ? _yellow : _red); } else if (s2 == null) { AddItem( _indent + "Offset Table", (uint)s1.OffsetTable.Count * 4, string.Format("@entries={0}", s1.OffsetTable.Count), lvFile1, pixel ? _yellow : _red); AddItem(string.Empty, uint.MaxValue, string.Empty, lvFile2, _gray); } else { Color c = _none; if (s1.OffsetTable.Count != s2.OffsetTable.Count) { c = _red; } else { for (int i = 0; i < s1.OffsetTable.Count; i++) { if (s1.OffsetTable[i] != s2.OffsetTable[i]) { c = _red; break; } } } AddItem( _indent + "Offset Table", (uint)s2.OffsetTable.Count * 4, string.Format("@entries={0}", s1.OffsetTable.Count), lvFile1, pixel ? _yellow : c); AddItem( _indent + "Offset Table", (uint)s2.OffsetTable.Count * 4, string.Format("@entries={0}", s2.OffsetTable.Count), lvFile2, pixel ? _yellow : c); } int count = 0; if (s1 != null) { count = s1.Fragments.Count; } if (s2 != null && s2.Fragments.Count > count) { count = s2.Fragments.Count; } string name = _indent + "Fragment"; for (int i = 0; i < count; i++) { IByteBuffer b1 = null; if (s1 != null && i < s1.Fragments.Count) { b1 = s1.Fragments[i]; } IByteBuffer b2 = null; if (s2 != null && i < s2.Fragments.Count) { b2 = s2.Fragments[i]; } if (b1 == null) { AddItem(string.Empty, uint.MaxValue, string.Empty, lvFile1, _gray); AddItem(name, (uint)b2.Size, string.Empty, lvFile2, pixel ? _yellow : _red); continue; } else if (b2 == null) { AddItem(name, (uint)b1.Size, string.Empty, lvFile1, pixel ? _yellow : _red); AddItem(string.Empty, uint.MaxValue, string.Empty, lvFile2, _gray); continue; } Color c = _none; if (pixel) { c = _yellow; } else if (!Compare(b1.Data, b2.Data)) { c = _red; } AddItem(name, (uint)b1.Size, string.Empty, lvFile1, c); AddItem(name, (uint)b2.Size, string.Empty, lvFile2, c); } Level--; } private string GetTagName(DicomTag t) { return string.Format("{0}{1} {2}", _indent, t.ToString().ToUpper(), t.DictionaryEntry.Name); } private void AddItem(string t, uint l, string v, ListView lv, Color c) { var lvi = lv.Items.Add(t); lvi.SubItems.Add(!string.IsNullOrEmpty(t) ? "--" : string.Empty); if (l == uint.MaxValue) { lvi.SubItems.Add(!string.IsNullOrEmpty(t) ? "-" : string.Empty); } else { lvi.SubItems.Add(l.ToString()); } lvi.SubItems.Add(v); lvi.UseItemStyleForSubItems = true; lvi.BackColor = c; } private void AddItem(DicomItem i, ListView lv, Color c) { ListViewItem lvi = null; if (i != null) { var tag = GetTagName(i.Tag); lvi = lv.Items.Add(tag); lvi.SubItems.Add(i.ValueRepresentation.Code); if (i is DicomElement) { var e = i as DicomElement; lvi.SubItems.Add(e.Length.ToString()); string value = ""; if (e.Length <= 2048) { value = string.Join("\\", e.Get()); } lvi.SubItems.Add(value); } else { lvi.SubItems.Add("-"); lvi.SubItems.Add(string.Empty); } lvi.Tag = i; } else { lvi = lv.Items.Add(string.Empty); lvi.SubItems.Add(string.Empty); lvi.SubItems.Add(string.Empty); lvi.SubItems.Add(string.Empty); } lvi.UseItemStyleForSubItems = true; lvi.BackColor = c; } private void AddItem(DicomItem i1, DicomItem i2) { if (i1 is DicomSequence || i2 is DicomSequence) { CompareSequences(i1 as DicomSequence, i2 as DicomSequence); return; } if (i2 == null) { AddItem(i1, lvFile1, _green); AddItem(i2, lvFile2, _gray); return; } if (i1 == null) { AddItem(i1, lvFile1, _gray); AddItem(i2, lvFile2, _green); return; } if (i1 is DicomElement && i2 is DicomElement) { var e1 = i1 as DicomElement; var e2 = i2 as DicomElement; var c = _none; if (!cbIgnoreVR.Checked && e1.ValueRepresentation != e2.ValueRepresentation) { c = _red; } else if (!Compare(e1.Buffer.Data, e2.Buffer.Data)) { c = _red; } if (cbIgnoreGroupLengths.Checked && e1.Tag.Element == 0x0000) { c = _yellow; } if (cbIgnoreUIDs.Checked && e1.ValueRepresentation == DicomVR.UI) { var uid = (i1 as DicomElement).Get(0); if (uid != null && (uid.Type == DicomUidType.SOPInstance || uid.Type == DicomUidType.Unknown)) { c = _yellow; } } if (cbIgnorePixelData.Checked && i1.Tag == DicomTag.PixelData) { c = _yellow; } AddItem(i1, lvFile1, c); AddItem(i2, lvFile2, c); return; } if (i1 is DicomFragmentSequence || i2 is DicomFragmentSequence) { CompareFragments(i1, i2); return; } if (i1 is DicomElement || i2 is DicomElement) { AddItem(i1, lvFile1, _red); AddItem(i2, lvFile2, _red); return; } AddItem(i1, lvFile1, _yellow); AddItem(i2, lvFile2, _yellow); } private static bool Compare(byte[] b1, byte[] b2) { if (b1.Length != b2.Length) { return false; } for (int i = 0; i < b1.Length; i++) { if (b1[i] != b2[i]) { return false; } } return true; } private void OnScroll(object sender, ScrollEventArgs e) { if (sender == lvFile1) { int index = lvFile1.TopItem.Index; lvFile2.TopItem = lvFile2.Items[index]; } else { int index = lvFile2.TopItem.Index; lvFile1.TopItem = lvFile1.Items[index]; } } private void OnSelect(object sender, EventArgs e) { if (sender == lvFile1) { if (lvFile1.SelectedIndices.Count > 0) { int index = lvFile1.SelectedIndices[0]; lvFile2.Items[index].Selected = true; } } else { if (lvFile2.SelectedIndices.Count > 0) { int index = lvFile2.SelectedIndices[0]; lvFile1.Items[index].Selected = true; } } } private void OnMouseEnter(object sender, EventArgs e) { ((Control)sender).Focus(); } private void OnSizeChanged(object sender, EventArgs e) { var lv = (ListViewEx)sender; var width = lv.Columns[0].Width + lv.Columns[1].Width + lv.Columns[2].Width; lv.Columns[3].Width = Math.Max(lv.ClientSize.Width - width, 440); } private void OnOptionChanged(object sender, EventArgs e) { int index = -1; if (lvFile1.TopItem != null) { index = lvFile1.TopItem.Index; } CompareFiles(); if (index != -1 && index < lvFile1.Items.Count) { lvFile1.TopItem = lvFile1.Items[index]; lvFile2.TopItem = lvFile2.Items[index]; } } } }