Saturday, June 25, 2016

Reflection

Back in the old days, C++ templates were the highest level of code abstraction for me. I loved creating template classes. On the other hand, Reflection, when I first learned about it, it didn't raise such an emotional feeling in me. It seamed like a complicated way to do simple things. About a year ago I was lucky to work with John Volkar from Bayer Healthcare. His project used Reflection to transform between C# data members and DICOM data elements. Recently, while working on another project, this technique came in handy. It's a nice example of how to use the Attribute class to link between a data member and a DICOM tag. Here it is. Thanks John.

First, how to you use it. It's very simple. Use the Tag attribute above the class member like this:

[Tag(0x0010,0x0010)]
public string patient_name;


This just mapped the patient_name member to a DICOM Patient Name data element by using the tag (0010,0010).


Here's a test class for the DICOM Patient Module with patient name, patient id, patient birth date and patient sex. This test puts values into the module class, maps the class into a DCXOBJ and then creates a new instance of the module class and fills the data from the DCXOBJ. The test compares the values in the two instances.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using GenericDICOM;
using rzdcxLib;
using System.Globalization;

namespace UnitTests
{
    [TestClass]
    public class DicomModuleTest
    {
        class PatientModule : DicomModule
        {
            [Tag(0x0010,0x0010)]
            public string patient_name;

            [Tag(0x0010, 0x0020)]
            public string patient_id;

            [Tag(0x0010, 0x0030)]
            public string _patient_birth_date;

            public DateTime patient_birth_date
            {
                set
                {
                    _patient_birth_date = value.ToString("yyyyMMdd");
                }
                get
                {
                    return DateTime.ParseExact(_patient_birth_date, "yyyyMMdd", CultureInfo.InvariantCulture);
                }
            }
            [Tag(0x0010,0x0040)]
            public string pateint_sex;
        }

        [TestMethod]
        public void TestDICOMFromTo()
        {
            PatientModule m = new PatientModule();
            m.patient_name = "A^B";
            m.patient_id = "PID123";
            m.patient_birth_date = new DateTime(2001, 02, 03);
            m.pateint_sex = "M";

            DCXOBJ o = m.ToDicomObject();

            PatientModule n = new PatientModule();
            n.FromDicomObject(o);

            Assert.AreEqual(m.patient_name, n.patient_name);
            Assert.AreEqual(m.patient_id, n.patient_id);
            Assert.AreEqual(m.patient_birth_date, n.patient_birth_date);
            Assert.AreEqual(m.pateint_sex, n.pateint_sex);
        }
    }

}

The DateTime type for patient birth date does require a little bit of dancing around it in order to format it properly. Maybe someone wants to contribute a generic class for it?

Here's the implementation of DicomModule class and the Tag Attribute class:

using System;
using rzdcxLib;
using System.Reflection;

namespace GenericDICOM
{
    ///



    /// The Tag attribute maps a member to a specific DICOM Data Element
    ///
    public class Tag : Attribute
    {
        public int tag;
        public Tag(ushort group, ushort element)
        {
            tag = group << 16 | element;
        }
    }

    ///



    /// A DICOM Module represents a set of DICOM Data Elelments
    ///
    public class DicomModule
    {
        public DicomModule()
        {

        }

        public DicomModule(DCXOBJ inObj)
        {
            FromDicomObject(inObj);
        }

        public DCXOBJ ToDicomObject()
        {
            DCXOBJ dicomObject = new DCXOBJ();
            FieldInfo[] fields = this.GetType().GetFields();
            foreach (FieldInfo f in fields)
            {
                Attribute[] attrs = System.Attribute.GetCustomAttributes(f);
                foreach (Attribute attr in attrs)
                {
                    if (attr is Tag)
                    {
                        DCXELM e = new DCXELM();
                        e.Init((attr as Tag).tag);
                        e.Value = f.GetValue(this);
                        dicomObject.insertElement(e);
                    }
                }
            }
            return dicomObject;
        }

        public void FromDicomObject(DCXOBJ dicomObject)
        {
            FieldInfo[] fields = this.GetType().GetFields();
            foreach (FieldInfo f in fields)
            {
                Attribute[] attrs = System.Attribute.GetCustomAttributes(f);
                foreach (Attribute attr in attrs)
                {
                    if (attr is Tag)
                    {
                        DCXELM e = dicomObject.GetElement((attr as Tag).tag);
                        if (e != null)
                            f.SetValue(this, Convert.ChangeType(e.Value, f.FieldType));
                    }
                }
            }
        }
    }
}

In the next post I'm going to take C# Generic Programming techniques a bit further and use the DicomModule class for performing DICOM Queries and Move instances using the DICOM Query/Retrieve Service.


5 comments:

  1. As a current client of yours just wanted to say that I have really enjoyed your useful articles and SOOO glad you are back at it again and writing more useful articles. I have followed most of your chapters and continue to do the same with your articles. Keep it up Roni!

    ReplyDelete
    Replies
    1. Thank you!
      I enjoy writing these articles. I should do it more often. But they're so much other things that keeps me busy that I don't get to it.
      Now I'm curious. Reveal yourself "Unknown" :-)
      Thanks,
      Roni

      Delete
  2. Haha, I'm Matt Zadeh. Not going to say the company name for confidentiality purposes. I'm a library user of yours and in fact we are going to put some new orders in soon. It's nearly impossible to read 3000 pages of DICOM info. So it has been very nice being able to read the conclusions of different chapters in your "dicom is easy" work instead. Also the library makes integration so much easier into my other c# codes I'm writing.

    ReplyDelete
  3. Roni, I have a question regarding other standard names I have heard. I understand DICOM and PACS and what they stand for, however what are HL7, VNA, and XDS in relation to those. Is HL7 a replacement for DICOM in some medical facilities and VNA, and XDS for PACS? And if some of those can be used for others, doesn't that defeat the whole purpose of having a "unified standard" for medical info to get shared/accessed/etc??!! Unless, communicating with VNA and XDS uses same IP addressing and protocols as PACS. Please shine some light on this when you got a chance :)

    ReplyDelete
    Replies
    1. Great question. I noted to write about that but, in two (dozens) words:
      HL7 is widely used for many things that are not imaging. Patient registration, transfer and discharge, test results, scheduling, orders, reports and also bed side monitors, infusion pumps, and other devices.
      VNA - carries the idea of 'let's put all documents together, not just radiology, in one big digital archive. Originally coined by players in the archiving world that didn't have DICOM support yet, this is now a driving force to levarge the investment in PACS to store other digital documents, like video ( e.g procedure recordings) visible light, scanned document and much more and also to add Other means for accessing the data in the PACS using web services (http/XML) RESTful API (http/json) and so on.
      XDS - comes from IHE and address document sharing between organizations using mostly web Techoligy.
      DICOM later versions address many of these issues with the introduction of WADO and imapping from DICOM to XML and json.
      This is really a long story that is beyond technology. It has marketing, politics, culture and business aspects. Interesting.
      The rise of EMR's changes the rules. There are some very big players that dominate the market and pose a threat to interoperability.
      DICOM still dominate the imaging production floor but once the images are in the PACS it played a lesser role as Web Techoligy takes over.

      Delete