Wednesday, May 29, 2013

Using the DICOM File Meta Information to Identify a DICOM file

[update 24 March 2023: Latest releases of HRZ software can be found on HRZ website - www.hrzkit.com]

I know I shouldn't do this but I'll do it anyway and apologize for not posting as frequent as before. This is for very good reason that I'm very busy with everyday work. One little example is the new DICOMIZER 2.0 released just few days ago and this is just a tiny project that my involvement in was really limited to giving some guidelines to the team. Naturally, the new DICOMIZER uses RZDCX. It is written in C# and uses the new features of version 2.0.3.0 of the DICOM Toolkit that all of them were requested by readers of this blog and of course our loved customers. I hope to release some code snippets from the new release soon but in this post, just before getting to the main subject (identifying DICOM files), I want to describe the new GetBitmap call of DCXIMG that returns a bitmap in memory and is used in the new DICOMIZER when you drag a DICOM file on top of the application. Here's a screenshot.

The little code that do the trick follows.


            img = new DCXIMG();
            img.LoadFile(dcmfile.Text);

            Size imageSize = new Size(img.width, img.Height); // Known size.

            // Set bitmap known image's metadata.
            bitmap = new Bitmap(imageSize.Width, imageSize.Height, imagePixelFormat);

            // Prepare working rectangle.
            wholeBitmap = new Rectangle(0, 0, bitmap.Width, bitmap.Height);

            // Lock all bitmap's pixels.
            bitmapData = bitmap.LockBits(wholeBitmap, ImageLockMode.WriteOnly, imagePixelFormat);

            img.GetBitmap(0, bitmapData.Stride * bitmapData.Height, (uint)bitmapData.Scan0);

            bitmap.UnlockBits(bitmapData);

            pictureBox1.Image = bitmap;

bitmap, bitmapData and pictureBox1 are members of the form class of types Bitmap, BitmapData and PictureBox reap. Easy!

Now you can play with it a bit more, for example by adding window center and window width handlers to the mouse move events. Before, you should keep somewhere the initial values, as here we have two members called center and width and we also need to keep a boolean for mouseDown and two more integers for lastX and lastY:


            img.GetWindow(out center, out width);


 And then the mouse handlers can be something like this:


       private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseDown = true;
            lastX = e.X;
            lastY = e.Y;
        }

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            mouseDown = false;
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (mouseDown && img != null)
            {
                int diffX = e.X - lastX;
                int diffY = e.Y - lastY;

                width += diffX;
                center += diffY;

                if (width < 0)
                    width = 0;
                if (width > 65536)
                    width = 65536;

                if (center < 0)
                    center = 0;
                if (center > 65536)
                    center = 65536;

                img.SetWindow(center, width);
                
                bitmapData = bitmap.LockBits(wholeBitmap, ImageLockMode.WriteOnly, imagePixelFormat);
                img.GetBitmap(0, bitmapData.Stride * bitmapData.Height, (uint)bitmapData.Scan0); 
                bitmap.UnlockBits(bitmapData);

                pictureBox1.Image = bitmap;

                lastX = e.X;
                lastY = e.Y;
            }
        }
    }


That's a simplified version, of course, just to get the idea. One last advise is to call ScaleImage just before  if you are dealing with mammograms because they are some big to fit into the screen anyway and then your response times will be much much faster.

And now for the subject. Looking back into this blog I see that the very first post that looked a bit like DICOM Tutorial dealt exactly with this subject when RZDCX 1.0.1.4 was released so long ago but this is a good subject and questions keeps coming so it's worth dealing with it again.

You have a bunch of files and you want to know if they are DICOM or not? One way is to call DCXOBJ.openFile and see if it passes but that may be too much for your needs as you don't really want to read the files, just know if they are DICOM or not. If your'e on Windows you probably think of checking the suffix, ha? So it's not ".dcm" and even if it is, so what? the dcm or DCM or whatever suffix is all but standard. If you're unix i.e. everything else that is not windows you'll be looking for magic number and it's in bytes 128-131. A DICOM file should start with a preamble: 128 0x0 bytes and then the 4 bytes with characters 'DICM' (ASCII) and there you go. Of course by accident you may fall on a file that has "DICM"there that is not a DICOM file so if you want to add extra security to that you can go the extra mile and check the next two bytes that should be the group 0002 of the file meta info and check that bytes 128-133 of the file are 0x44 0x49 0x43 0x4D 0x02 0x00. I would fill quite confident that it is a DICOM file if there's a match. If you're still in doubt, you can go all the way up to the UL characters of the group 0002 length and even two bytes beyond that that should always be 0x04 0x00 but not further then this as the header length value is the first thing to change from file to file. Matching the 12 bytes from 128-139 is defiantly good though it will perform slower then just matching just the 4 bytes of 128-131 using a single call to compare an int with the constant 0x4D434944.
Identifying a DICOM file using bytes 128-131

There's also the case of DICOM files that does not have the preamble and file meta information though this becomes very rare and it's not standard. In this case you have no better way then trying to parse it. Most likely it will start with 0x08 0x00 for group 0008 element but that's only a heuristic.

3 comments:

  1. I came here hoping to learn what MATLAB meant by FileMetaInformationVersion = [0;1].

    ReplyDelete
    Replies
    1. Its very simple. This is the value that should be set to this tag. It has never changed. If at some point the standard specifications for the DICOM File Meta Header are going to change, then I guess this will be used but for now the first tag in every DICOM file (after the group length of group 0002) is (0002,0001) OB 00\01

      Delete
  2. On the example of the simplified version it is much easier to understand the meaning of the program and figure out how it works in practice.

    ReplyDelete