Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

DicomTagSeeker - A DiCOM repository tag searcher

5.00/5 (13 votes)
18 Apr 2013CPOL4 min read 31.2K   3.1K  
A tool for Seeking, Sorting and Reporting in a morderately large DICOM repository

Introduction 

DICOM (Digital Imaging and Communications in Medicine) is a standard for handling, storing, printing, and transmitting information in medical imaging. It includes a file format definition and a network communications protocol. The communication protocol is an application protocol that uses TCP/IP to communicate between systems. DICOM files can be exchanged between two entities that are capable of receiving image and patient data in DICOM format. The National Electrical Manufacturers Association (NEMA) holds the copyright to this standard. It was developed by the DICOM Standards Committee, whose members[2] are also partly members of NEMA.

DICOM data format 

DICOM differs from some, but not all, data formats in that it groups information into data sets. That means that a file of a chest x-ray image, for example, actually contains the patient ID within the file, so that the image can never be separated from this information by mistake. This is similar to the way that image formats such as JPEG can also have embedded tags to identify and otherwise describe the image.

A DICOM data object consists of a number of attributes, including items such as name, ID, etc., and also one special attribute containing the image pixel data (i.e. logically, the main object has no "header" as such: merely a list of attributes, including the pixel data). A single DICOM object can have only one attribute containing pixel data. For many modalities, this corresponds to a single image. But note that the attribute may contain multiple "frames", allowing storage of cine loops or other multi-frame data. Another example is NM data, where an NM image, by definition, is a multi-dimensional multi-frame image. In these cases, three- or four-dimensional data can be encapsulated in a single DICOM object. Pixel data can be compressed using a variety of standards, including JPEG, JPEG Lossless, JPEG 2000, and Run-length encoding (RLE). LZW (zip) compression can be used for the whole data set (not just the pixel data), but this has rarely been implemented.

DICOM uses three different Data Element encoding schemes. With Explicit Value Representation (VR) Data Elements, for VRs that are not OB, OW, OF, SQ, UT, or UN, the format for each Data Element is: GROUP (2 bytes) ELEMENT (2 bytes) VR (2 bytes) LengthInByte (2 bytes) Data (variable length). For the other Explicit Data Elements or Implicit Data Elements, see section 7.1 of Part 5 of the DICOM Standard.

The same basic format is used for all applications, including network and file usage, but when written to a file, usually a true "header" (containing copies of a few key attributes and details of the application which wrote it) is added. 

Source: http://en.wikipedia.org/wiki/DICOM 

Background 

In medical imaging platform testing there are often requirements to hunt for images with specific DICOM attributes (group-element TAGs) in local imagepools (be it the network folder, a version control system or some other data source). The maximum size of these paths are in terrabytes (owing to the enormous file sizes of DICOM images) and the number of directories are in tens of thousands if not more. 

Seemingly it was difficult, immensely time-consuming and error prone to look into all files and directories just to find for the ones we need. Hours and sometimes days went in such tasks. Sometimes the search would end un-successfully as there were no images or we would simply give-up because of other mission critical tasks or monotony. For a human it is not only cumbersome and monotonous but also unproductive for the team or project. 

The tool cuts across the barriers to maintain huge DICOM databases. It’s a multithreaded solution which peeps into all/selected DCM files and picks up information for reporting, tabulation or simply finding files with specific attributes (group-element tags) 

This tool uses OpenDicom.NET as a dicom file parsing library.  

Using the code 

Where the tool scores over other dicom searchers 

a) Most available tool is an indexed search which works on a pre-prepared index of images in the paths above. No wonder, it cannot be generalized to look for images anywhere.

b) No search mode (like FirstFile/All Files). e.g For a specific search we would get all the files listed as a result. If a path contains 2000 files, we would get all of them as results when we would require just one to get the path. And this would obscure other required results behind say 100 pages.

c) It's 

  • Small in size 
  • Portable
  • Efficient
  • User Friendly 
  • Configurable 

Usage: 

Step 1: Enter the path where to search images

Step 2: Enter a text filename where the result will be dumped

Step 3: Select the list of attributes to be searched for

Step 4: Click BeginFind (to asynchronously search for images in the path)

Image 1

Step 5 (Optional): While the tool is searching there is an option to EndFind which terminates the process in between; if the user is satisfied with the results listed so far 

Here is how the results are shown (pretty primitive but good enough to find your files) 

Image 2 

The main class in this scope is the Finder class. Here is a peek at the code: 

C#
namespace ImageFinder
{
    public delegate void FindEventHandler();

    enum SearchMethod
    {
        FirstFile,
        AllFiles
    }

    enum OtherInfo
    {
        None = 0,
        FileSize = 1,
        FileCount = 2
    }

    class Finder
    {
        // Live tested data sample where the tool works
        //\\network1\path1 383 GB
        //\\network2\path2 67.4 GB 
        //\\network3\path3 14.2 GB


        private Tag GetGroupElement(string tag)
        {
            string[] str = tag.Split(new char[] { ',' });
            return new Tag(int.Parse(str[0], NumberStyles.HexNumber),
                int.Parse(str[1], NumberStyles.HexNumber));
        }

        private string GetValueString(Value value)
        {
            string substr = "";
            switch (value.VR.Name)
            {
                default:
                    for (int k = 0; k < value.Count; k++)
                    {
                        substr += value[k].ToString();
                        if (k < value.Count - 1)
                        {
                            substr += "/";
                        }
                    }
                    break;
            }
            return substr;
        }

        private string ReadFile(FileInfo fileInfo)
        {
            if (!myString.Contains(fileInfo.DirectoryName) || mySearchMethod == SearchMethod.AllFiles)
            {
                string aValueString = "";

                AcrNemaFile file = null;
                try
                {
                    if (DicomFile.IsDicomFile(fileInfo.FullName))
                    {
                        file = new DicomFile(fileInfo.FullName, false);
                    }
                    else if (AcrNemaFile.IsAcrNemaFile(fileInfo.FullName))
                    {
                        file = new AcrNemaFile(fileInfo.FullName, false);
                    }
                    else
                    {
                        //Console.WriteLine("Selected file is wether a DICOM nor an ACR-NEMA file.");
                        return "Unknown File Format Exception";
                    }
                }
                catch (Exception dicomFileException)
                {
                    //Console.WriteLine("Problems processing DICOM file" + dicomFileException);
                    return "Unknown File Format Exception";
                }

                for (int j = 0; j < myItems.Count; j++)
                {
                    if (myItems[j] == "7FE0,0010")
                    {
                        Tag tag = GetGroupElement(myItems[j]);
                        try
                        {
                            DataElement elem = file.DataSet[tag];
                            aValueString += "Length = " + elem.ValueLength.ToString() + mySeparator;
                        }
                        catch
                        {
                            aValueString += "--" + mySeparator;
                        }
                    }
                    else if (myItems[j] == "0002,0010")
                    {
                        aValueString += file.GetJointDataSets().TransferSyntax + mySeparator;
                    }
                    else
                    {
                        try
                        {
                            Tag tag = GetGroupElement(myItems[j]);
                            if (tag != null)
                            {
                                DataElement elem = file.DataSet[tag];
                                aValueString += GetValueString(elem.Value) + mySeparator;
                            }
                            else
                            {
                                aValueString += "??" + mySeparator;
                            }

                        }
                        catch
                        {
                            aValueString += "--" + mySeparator;
                        }
                    }
                }

                myString.Add(fileInfo.DirectoryName);
                if (mySearchMethod == SearchMethod.FirstFile)
                {
                    // WARNING : Cross Thread Operation
                    if (myProgressIndicator.Value < myProgressIndicator.Maximum)
                    myProgressIndicator.Value++;
                    // WARNING : Cross Thread Operation
                    return aValueString;
                }
                else
                {
                    if (myShowAllCondition == true || IsFound(aValueString))
                    {
                        string writeString = fileInfo.FullName;

                        if ((myOtherInfoRequired &= OtherInfo.FileSize) != 0)
                        {
                            writeString = writeString + mySeparator + fileInfo.Length;
                        }
                        if ((myOtherInfoRequired & OtherInfo.FileCount) != 0)
                        {
                            writeString = writeString + mySeparator + "1";
                        }
                        // WARNING : Cross Thread Operation
                        if (myProgressIndicator.Value < myProgressIndicator.Maximum)
                        myProgressIndicator.Value++;
                        // WARNING : Cross Thread Operation
                        myOutStream.WriteLine(writeString + mySeparator + aValueString);
                        myOutStream.Flush();
                    }
                }
            }

            return string.Empty;
        }      
    }
}

Points of Interest

The idea of tag based search can well be extended to image search techniques of other image files than DICOM. Most imaging formats hold some tags which become relevant for particular tag-based search requirements. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)