LAUREL BRIDGE

LaurelBridge.DCFExamples.CustomTagDictionary Namespace

DICOM Connectivity Framework V3.4
The CustomTagDictionary example demonstrates how to use DCF to customize the DICOM and DICONDE tag dictionary.
Classes

  ClassDescription
Public classProgram
This example demonstrates how to customize the default tag dictionary.

When DCF needs to look up a value representation (VR) in the implicit transfer syntax, it uses the TagLookup singleton. If no dictionary is configured for the application, the default dictionary is loaded from a resource in the library.

The default dictionary may also be extended via the use of the custom_tags file, where users may automatically load custom tag extensions upon construction of the default DataDictionary. This facility is used to support the tags and naming conventions for DICONDE.

Extending the data dictionary is useful in several scenarios:

  • Adding to tags in the standard dictionary for items newly added by the DICOM standard.
  • Adding definitions of private tags for vendor specific private tags.
  • Poorly encoded elements may be able to be better processed by changing the VR of a tag in the dictionary.
  • Overriding a tag allows you to change the name or description of an element in the log output.
Remarks

Supported OS Platforms:

  • Windows - .Net Framework 4.7.2 64-bit and 32-bit
  • Windows - .Net Core 2.1 64-bit and 32-bit
  • Linux - .Net Core 2.1 64-bit

Examples

CustomTagDictionary Sample Code
public class Program
{
    /// <summary>
    /// Main entry point for CustomTagDictionary example.
    /// </summary>
    public static void Main()
    {
        try
        {
            DemoLookupSomeTags();

            DemoOverrideTagNames();

            DemoCreatePrivateTags();

            DemoPrivateTagUsage();

            DemoLoadCustomTagsFromFile();

            DemoLoadCustomTagsFromLegacyExtDataDictionary();

            DemoSessionBasedTagExtensions();
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception caught during execution: {0}", e);
            Environment.ExitCode = 1;
        }
        finally
        {
            // Remove the custom tags and extended data dictionary files to prevent other examples from automatically loading them
            File.Delete(WorkingCopyCustomTagsFilename);
            File.Delete(WorkingCopyLegacyCustomTagsFilename);
        }

        if (System.Diagnostics.Debugger.IsAttached)
        {
            Console.WriteLine();
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey();
        }
    }

    #region Demos

    private static void DemoLookupSomeTags()
    {
        Console.WriteLine("\n===== DemoLookupSomeTags =====\n");

        // Lookup some common tags
        Console.WriteLine("Lookup some common tags");

        ITagEntry tagEntry = DataDictionary.GetTagEntry(Tags.PatientName);
        Console.WriteLine("({0}) Name: {1}", tagEntry.Tag, tagEntry.Name);

        tagEntry = DataDictionary.GetTagEntry(Tags.PatientID);
        Console.WriteLine("({0}) Name: {1}", tagEntry.Tag, tagEntry.Name);

        tagEntry = DataDictionary.GetTagEntry(Tags.ReferringPhysicianName);
        Console.WriteLine("({0}) Name: {1}", tagEntry.Tag, tagEntry.Name);
    }

    private static void DemoOverrideTagNames()
    {
        Console.WriteLine("\n===== DemoOverrideTagNames =====\n");

        // Set the TagLookup object to default
        DataDictionary.TagLookup = TagLookup.CreateDefault();

        // Lookup some common tags before dictionary extension
        LookupTagsAndDisplayThem("Lookup some common tags BEFORE dictionary extension", SomeCommonTags);
        LookupTagsAndDisplayThem("Lookup the same common tags by overridden names BEFORE dictionary extension", SameCommonTagsWithOverriddenNames);

        // Override and extend the default tag dictionary
        DataDictionary.TagLookup = CreateExtendedProvider();

        // Look up the same common tags after dictionary extension
        LookupTagsAndDisplayThem("Lookup the same common tags AFTER dictionary extension", SomeCommonTags);
        LookupTagsAndDisplayThem("Lookup the same common tags by overridden names AFTER dictionary extension", SameCommonTagsWithOverriddenNames);

        // Set the TagLookup object to default
        DataDictionary.TagLookup = TagLookup.CreateDefault();

        // Look up the same common tags after going back to default
        LookupTagsAndDisplayThem("Lookup the same common tags AFTER RESET to default", SomeCommonTags);
        LookupTagsAndDisplayThem("Lookup the same common tags by overridden names AFTER RESET to default", SameCommonTagsWithOverriddenNames);

    }

    private static void DemoCreatePrivateTags()
    {
        Console.WriteLine("\n===== DemoCreatePrivateTags =====\n");

        // Set the TagLookup object to default
        DataDictionary.TagLookup = TagLookup.CreateDefault();

        // Lookup some private tags before dictionary extension
        LookupTagsAndDisplayThem("Lookup some common tags BEFORE dictionary extension", SomeCommonTags);
        LookupTagsAndDisplayThem("Lookup some private tags BEFORE dictionary extension", MyPrivateCreatorId, MyPrivateTags);

        // Override and extend the default tag dictionary
        DataDictionary.TagLookup = CreateExtendedProvider();

        // Upon inspection of the dictionaries used in the CreateExtendedProvider method, note:
        // The ordering of dictionaries in the TagEntryProvider constructor is meaningful.
        // The initial boolean "true" in the constructor indicates the default dictionary will be used.
        // The override dictionary comes first in in the list of dictionaries, followed by the private dictionary.
        // Thus, the lookup search order for a given tag in this TagEntryProvider is override first, then private, and finally default.
        // If a tag is defined in all three, the name defined for that tag in the override dictionary would be returned on a lookup.
        // If a tag is defined in the private and default dictionaries only, the name from the next dictionary in search order,
        // private, would be returned. If a tag is only defined in a single dictionary, the name defined there is returned.
        // As a specific example, consider the private tag (0019,1003) that defines the name 'My Person Name' in the private dictionary 
        // and the name 'My Overridden Person Name' in the override dict. The expected value returned from the lookup will be the overriden value
        // because the override dictionary is first in search order.

        // Look up the same private tags after dictionary extension
        LookupTagsAndDisplayThem("Lookup the same common tags AFTER dictionary extension", SomeCommonTags);
        LookupTagsAndDisplayThem("Lookup the same private tags AFTER dictionary extension", MyPrivateCreatorId, MyPrivateTags);
    }

    private static void DemoPrivateTagUsage()
    {
        Console.WriteLine("\n===== DemoPrivateTagUsage =====\n");

        // Create a TagLookup object using new DCF34 approach
        TagLookup tagLookup = TagLookup.CreateFromStream("mystream",
            new MemoryStream(Encoding.UTF8.GetBytes(
                "(0019,xx00,MY PRIVATE TAG)|My Count|MyCount|US|1|\n" +
                "(0019,xx01,MY PRIVATE TAG)|My Other ID|MyOtherID|SH|1|\n" +
                "(0019,xx02,MY PRIVATE TAG)|My Date and Time|MyDate|TM|1|\n" +
                "(0019,xx03,MY PRIVATE TAG)|My Person Name|MyPersonName|PN|1|\n" +
                "(0019,xx04,MY PRIVATE TAG2)|My LO Tag|MyLOTag|LO|1|\n")), true);

        // Create a data set
        DicomDataSet dds = new DicomDataSet();

        // Insert another private element
        DicomElement creator1 = ElementFactory.Create(0x00190010, "MY PRIVATE TAG");
        dds.Insert(creator1);

        Console.WriteLine("Lookup elements after inserting MY PRIVATE TAG creator");

        // Lookup the My Count element
        ITagEntry tagEntry = tagLookup.Find(0x00191000, dds);
        DisplayTagEntryFull("My Count", tagEntry);

        // Lookup the My Other ID element
        tagEntry = tagLookup.Find(0x00191001, dds);
        DisplayTagEntryFull("My Other ID", tagEntry);

        // Lookup the My Data and Time element
        tagEntry = tagLookup.Find(0x00191002, dds);
        DisplayTagEntryFull("My Data and Time", tagEntry);

        // Lookup the My Person Name element
        tagEntry = tagLookup.Find(0x00191003, dds);
        DisplayTagEntryFull("My Person Name", tagEntry);

        // Lookup an element that doesn't exist
        tagEntry = tagLookup.Find(0x00191005, dds);
        DisplayTagEntryFull("No such tag", tagEntry);

        // Make sure we can still lookup My Other ID (with creator MY PRIVATE TAG)
        tagEntry = tagLookup.Find(0x00191001, dds);
        DisplayTagEntryFull("My Other ID still found", tagEntry);

        // Try looking up the My LO Tag element (not found, its creator is MY PRIVATE TAG2)
        tagEntry = tagLookup.Find(0x00191004, dds);
        DisplayTagEntryFull("My LO Tag", tagEntry);

        // Insert the MY PRIVATE TAG2 creator
        DicomElement creator2 = ElementFactory.Create(0x00190010, "MY PRIVATE TAG2");
        dds.Insert(creator2);

        Console.WriteLine("{0}Lookup elements after inserting MY PRIVATE TAG2 creator", Environment.NewLine);

        // This time MY PRIVATE TAG2 hides My Other ID (because its creator is MY PRIVATE TAG)
        tagEntry = tagLookup.Find(0x00191001, dds);
        DisplayTagEntryFull("My Other ID not found", tagEntry);

        // Lookup the My LO Tag element, works this time
        tagEntry = tagLookup.Find(0x00191004, dds);
        DisplayTagEntryFull("My LO Tag", tagEntry);

        // Insert the MY PRIVATE TAG3 creator
        DicomElement creator3 = ElementFactory.Create(0x00190012, "MY OTHER PRIVATE TAG");
        dds.Insert(creator3);

        Console.WriteLine("{0}Lookup elements after inserting MY OTHER PRIVATE TAG creator", Environment.NewLine);

        // My Data and Time no longer found (MY OTHER PRIVATE TAG hides it, both end with 2)
        tagEntry = tagLookup.Find(0x00191002, dds);
        DisplayTagEntryFull("My Data and Time not found", tagEntry);

        // My LO Tag element still found (ends with 4)
        tagEntry = tagLookup.Find(0x00191004, dds);
        DisplayTagEntryFull("My LO Tag still found", tagEntry);
    }

    private static void DemoLoadCustomTagsFromFile()
    {
        Console.WriteLine("\n===== DemoLoadCustomTagsFromFile =====\n");

        // Reset to default tag dictionary
        DataDictionary.TagLookup = null;

        // Example private vendor tag info
        const string PrivateVendorCreator1 = "PRIVATE VENDOR TAG1";
        const string PrivateVendorCreator2 = "PRIVATE VENDOR TAG2";
        AttributeTag[] privateVendorTags = new AttributeTag[] { 0x00100010, 0x00990000, 0x00990001, 0x00990002, 0x00990003 };

        // Display tags before loading the dictionary
        LookupTagsAndDisplayThem("Lookup some common tags BEFORE loading custom_tags overrides", SomeCommonTags);
        LookupTagsAndDisplayThem("Lookup Private Vendor 1 tags BEFORE loading custom_tags overrides", PrivateVendorCreator1, privateVendorTags);
        LookupTagsAndDisplayThem("Lookup Private Vendor 2 tags BEFORE loading custom_tags overrides", PrivateVendorCreator2, privateVendorTags);
        LookupTagsAndDisplayThem("Lookup No Creator priv. tags BEFORE loading custom_tags overrides", privateVendorTags);

        // Make temporary working copy of the example custom_tags file
        File.Copy(SampleCustomTagsFilename, WorkingCopyCustomTagsFilename);

        // Setting TagLookup to null causes the tags dictionary to be reset to default values
        // and causes the overrides in the "custom_tags" to be read from disk
        DataDictionary.TagLookup = null;

        // Show the custom tag dictionary file just read in
        Console.WriteLine("----- Custom tags dictionary extention read from disk -----");
        Console.WriteLine("{0}", ReadCustomTagsDictFromFile());
        Console.WriteLine("-----------------------------------------------------------");
        Console.WriteLine();

        // Display tags after loading the dictionary
        LookupTagsAndDisplayThem("Lookup some common tags AFTER loading custom_tags overrides", SomeCommonTags);
        LookupTagsAndDisplayThem("Lookup Private Vendor 1 tags AFTER loading custom_tags overrides", PrivateVendorCreator1, privateVendorTags);
        LookupTagsAndDisplayThem("Lookup Private Vendor 2 tags AFTER loading custom_tags overrides", PrivateVendorCreator2, privateVendorTags);
        LookupTagsAndDisplayThem("Lookup No Creator priv. tags AFTER loading custom_tags overrides", privateVendorTags);

        // Delete working copy to prevent other examples from loading them
        File.Delete(WorkingCopyCustomTagsFilename);
    }

    private static void DemoLoadCustomTagsFromLegacyExtDataDictionary()
    {
        Console.WriteLine("\n===== DemoLoadCustomTagsFromLegacyExtDataDictionary =====\n");

        // Reset tag dictionary
        DataDictionary.TagLookup = null;

        // Example tags that are defined in the legacy dictionary (ext_data_dictionary.txt)
        AttributeTag tag1 = new AttributeTag(0x00291234);
        AttributeTag tag2 = new AttributeTag(0x00391234);

        // Show tags before loading legacy dictionary
        LookupTagsAndDisplayThem("Lookup some common tags BEFORE loading legacy data dictionary", SomeCommonTags);
        Console.WriteLine("Lookup custom tags BEFORE loading legacy data dictionary");
        LookupTagAndDisplayIt(null, tag1);
        LookupTagAndDisplayIt(null, tag2);

        // Make temporary working copy of the example custom_tags and ext_data_dictionary files
        File.Copy(SampleLegacyCustomTagsFilename, WorkingCopyLegacyCustomTagsFilename);

        // Load a new dictionary from disk that uses the legacy CFGGroup format
        DataDictionary.TagLookup = LegacyExtendTagDictionary();

        // Show the legacy tag dictionary file just read in
        Console.WriteLine();
        Console.WriteLine("----- Legacy tags dictionary extention read from disk -----");
        Console.WriteLine("{0}", ReadLegacyCustomTagsDictFromFile());
        Console.WriteLine("------------------------------------------------------------");
        Console.WriteLine();

        // Show tags after loading legacy dictionary
        LookupTagsAndDisplayThem("Lookup some common tags AFTER loading legacy data dictionary", SomeCommonTags);
        Console.WriteLine("Lookup custom tags AFTER loading legacy data dictionary");
        LookupTagAndDisplayIt(null, tag1);
        LookupTagAndDisplayIt(null, tag2);

        // Delete working copy to prevent other examples from loading them
        File.Delete(WorkingCopyLegacyCustomTagsFilename);
    }

    private static void DemoSessionBasedTagExtensions()
    {
        Console.WriteLine("\n===== DemoSessionBasedTagExtensions =====\n");

        // Turn off INFO message logging
        Framework.DefaultSessionSettings.IsInfoEnabled = false;

        // Demonstrate session based tag dictionary extensions
        DicomSessionSettings sessionSettings = new DicomSessionSettings()
        {
            // The explicit transfer syntax codecs will perform a lookup for standard (non-private) tags.
            // This allows overriding the VR for explicit transfer syntaxes and should be used with caution.
            LookupStandardVrInExplicit = true
        };

        // Turn INFO message logging back on
        Framework.DefaultSessionSettings.IsInfoEnabled = true;

        // Define an override to use for the session
        string sessionSettingsPNOverride = "(0010,0010)|Patient's Name|PatientName|CS|1|";
        Console.WriteLine("Override SessionSettings: {0}", sessionSettingsPNOverride);

        // Define a TagLookup for the session
        sessionSettings.TagLookup = TagLookup.CreateFromStream("CSPatientName", new MemoryStream(Encoding.UTF8.GetBytes(
            sessionSettingsPNOverride)), true);

        // Read a dataset from an example file
        string datasetPath = "mr-knee.dcm";
        DicomDataSet ds = ReadDatasetFromFile(datasetPath, sessionSettings);

        // Lookup the Patient Name element extension, which uses explicit VR of CS
        DicomElement element = ds.FindElement(Tags.PatientName);
        Console.WriteLine("PN element (extended ss):{0}", element);

        // Lookup the Patient Name element, which uses default VR of PN
        DicomDataSet dds = ReadDatasetFromFile(datasetPath, null);
        DicomElement element2 = dds.FindElement(Tags.PatientName);
        Console.WriteLine("PN element  (default ss):{0}", element2);
    }

    #endregion

    #region CreateExtendedProvider helper

    /// <summary>
    /// Create an ITagEntryProvider with 3 dictionaries, the override, the private and the standard dictionary.
    /// </summary>
    /// <returns>the ITagEntryProvider</returns>
    private static ITagEntryProvider CreateExtendedProvider()
    {
        // typically, these dictionaries will be in a file you can load using CreateFromFile.  We use CreateFromStream here:

        // the private dictionary defines elements for any private group with creator id 'PRIVATE CREATOR'
        TagLookup privateDict = TagLookup.CreateFromStream("private dictionary", new MemoryStream(Encoding.UTF8.GetBytes(
            "(0019,xx00,PRIVATE CREATOR)|My Count|MyCount|US|1|\n" +
            "(0019,xx01,PRIVATE CREATOR)|My Other ID|MyOtherID|SH|1|\n" +
            "(0019,xx02,PRIVATE CREATOR)|My Date and Time|MyDate|TM|1|\n" +
            "(0019,xx03,PRIVATE CREATOR)|My Person Name|MyPersonName|PN|1|\n")), true);

        // this override dictionary overrides some tags that are used by DICONDE, as well as adding another private creator tag
        TagLookup overrideDict = TagLookup.CreateFromStream("override dictionary", new MemoryStream(Encoding.UTF8.GetBytes(
            "(0010,0010)|Component Name|ComponentName|PN|1|\n" +
            "(0010,0020)|Component ID Number|ComponentIDNumber|LO|1|\n" +
            "(0008,0090)|Component Owner Name|ComponentOwnerName|PN|1|\n" +
            "(0019,xx03,PRIVATE CREATOR)|My Overridden Person Name|MyPersonName|PN|1|\n")), true);

        // create the TagEntryProvider with the override, private and standard dictionaries, in that search order.
        return new TagEntryProvider(true, overrideDict, privateDict);
    }

    #endregion

    #region Platform independent file paths

    // Filenames of an example and working copy of a DCF34 custom tags file
    private static readonly string SampleCustomTagsFilename = Path.Combine("dicom", "custom_tags.txt");
    private static readonly string WorkingCopyCustomTagsFilename = Path.Combine("dicom", "custom_tags");

    // Filenames of an example and working copy of a DCF33 legacy format custom tags file
    private static readonly string SampleLegacyCustomTagsFilename = Path.Combine("dicom", "ext_data_dictionary.txt");
    private static readonly string WorkingCopyLegacyCustomTagsFilename = Path.Combine("dicom", "ext_data_dictionary");

    #endregion

    #region Tag Contants

    /// <summary>
    /// Helper class to manage DICONDE override tag constants.
    /// </summary>
    static class NDETags
    {
        public const int ComponentName = Tags.PatientName;
        public const int ComponentIDNumber = Tags.PatientID;
        public const int ComponentOwnerName = Tags.ReferringPhysicianName;
    }

    /// <summary>
    /// Helper class to manage private tag constants.
    /// </summary>
    static class PrivateCreatorTags
    {
        public const int MyCount = 0x00191000;
        public const int MyOtherID = 0x00191001;
        public const int MyDate = 0x00191002;
        public const int MyPersonName = 0x00191003;
    }

    // Common tags to lookup
    private static readonly AttributeTag[] SomeCommonTags = { Tags.PatientName, Tags.PatientID, Tags.ReferringPhysicianName };
    private static readonly AttributeTag[] SameCommonTagsWithOverriddenNames = { NDETags.ComponentName, NDETags.ComponentIDNumber, NDETags.ComponentOwnerName };

    // Private tags to lookup
    private static readonly string MyPrivateCreatorId = "PRIVATE CREATOR";
    private static readonly AttributeTag[] MyPrivateTags = { PrivateCreatorTags.MyCount, PrivateCreatorTags.MyOtherID, PrivateCreatorTags.MyDate, PrivateCreatorTags.MyPersonName };

    #endregion

    #region Tag Lookup and Display Helpers

    private static void LookupTagsAndDisplayThem(string description, params AttributeTag[] tags)
    {
        LookupTagsAndDisplayThem(description, null, tags);
    }

    /// <summary>
    /// Display a description, then lookup and display some number of tags.
    /// </summary>
    /// <param name="description"></param>
    /// <param name="creatorId"></param>
    /// <param name="tags"></param>
    private static void LookupTagsAndDisplayThem(string description, string creatorId, params AttributeTag[] tags)
    {
        Console.WriteLine(description);
        foreach (AttributeTag tag in tags)
        {
            LookupTagAndDisplayIt(creatorId, tag);
        }
        Console.WriteLine();
    }

    /// <summary>
    /// Lookup and display a specific tag.
    /// </summary>
    /// <param name="creatorId"></param>
    /// <param name="tag"></param>
    private static void LookupTagAndDisplayIt(string creatorId, AttributeTag tag)
    {
        ITagEntry tagEntry = DataDictionary.GetTagEntry(tag, creatorId);
        Console.WriteLine("({0}) Name: {1}", tagEntry.Tag, tagEntry.Name);
    }

    /// <summary>
    /// Displays the various attributes of a TagEntry.
    /// </summary>
    /// <param name="description">The description of what we were doing, which should be non-null.</param>
    /// <param name="tagEntry">The ITagEntry to be displayed which may be null.</param>
    private static void DisplayTagEntryFull(string description, ITagEntry tagEntry)
    {
        string tagstr = tagEntry == null ? "not present" :
            string.Format("({0},{1})|{2}|{3}|{4}|{5}|", tagEntry.Tag, tagEntry.CreatorId, tagEntry.Name, tagEntry.Keyword, tagEntry.VRName, tagEntry.VMRange);
        Console.WriteLine("{0}: {1}", description, tagstr);
    }
    #endregion

    #region Helpers

    /// <summary>
    /// Reads the text from the custom_tags dictionary extensions from the working copy of custom_tags.
    /// </summary>
    /// <returns>The custom tags dictionary text read from file.</returns>
    private static string ReadCustomTagsDictFromFile()
    {
        string customTagsDictPath = Path.Combine(Framework.ConfigurationPath, WorkingCopyCustomTagsFilename);
        return File.ReadAllText(customTagsDictPath).Trim();
    }

    /// <summary>
    /// Reads the text from the ext_data_dictionary.txt legacy dictionary custom tag file.
    /// </summary>
    /// <returns>The legacy custom tags dictionary text read from file.</returns>
    private static string ReadLegacyCustomTagsDictFromFile()
    {
        string legacyTagsDictPath = Path.Combine(Framework.ConfigurationPath, WorkingCopyLegacyCustomTagsFilename);
        return File.ReadAllText(legacyTagsDictPath).Trim();
    }

    /// <summary>
    /// Extend the default tag dictionary using the legacy CFGGroup format.
    /// </summary>
    /// <returns>The default tag dictionary extended to include the private tag entries from the indicated CFGGroup.</returns>
    private static ITagEntryProvider LegacyExtendTagDictionary()
    {
        // load the legacy extended dictionary using the configuration file deployed by the visual studio project
        string cfgPath = WorkingCopyLegacyCustomTagsFilename;
        CFGGroup cfg = CFGDB.loadGroupFromFile(cfgPath);
        //Console.WriteLine("CFGGroup: {0}{1}", Environment.NewLine, cfg);
        TagLookup tagLookup = TagLookup.CreateFromCfgGroup(cfg);
        return new TagEntryProvider(true, tagLookup);
    }

    /// <summary>
    /// Reads the Dicom dataset header only from the given input file path.
    /// </summary>
    /// <param name="inputPath">The path of the Dicom dataset on disk.</param>
    /// <param name="sessionSettings">Session settings to use when reading the dataset.</param>
    /// <returns>The Dicom dataset read from file.</returns>
    private static DicomDataSet ReadDatasetFromFile(string inputPath, DicomSessionSettings sessionSettings)
    {
        using (DicomFileInput dfi = new DicomFileInput(inputPath, sessionSettings))
        {
            return dfi.ReadDataSetNoPixels();
        }
    }

    #endregion
}