{"id":3881,"date":"2019-11-19T12:38:24","date_gmt":"2019-11-19T17:38:24","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=3881"},"modified":"2019-11-19T12:38:24","modified_gmt":"2019-11-19T17:38:24","slug":"how-to-read-and-visualize-a-dicom-volume","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2019\/11\/19\/how-to-read-and-visualize-a-dicom-volume\/","title":{"rendered":"How to Read and Visualize a DICOM Volume"},"content":{"rendered":"<div class=\"content\"><p>Earlier this year, I learned something about DICOM datasets that surprised me. I had downloaded a Head-Neck CT+PET study, and I wanted to create a volume array in MATLAB. I tried to do this the hard way at first, and of course I got it wrong. (Spoiler: there's an easy way.)<\/p><p>I naively assumed that there was some meaning to the order of filenames in the DICOM dataset. Here is the folder I was looking at:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/dir-output.png\" alt=\"\"> <\/p><p>You can see that I had files of the form 00000.dcm, 00001.dcm, ..., 000089.dcm. So, I read these files in filename order and concatenated the data in the third dimension. And, I got a mess. I had assumed that the filename order corresponded to physical slice order, but I was wrong.<\/p><p>You can see this by looking at the <tt>SliceLocation<\/tt> field returned by <tt>dicominfo<\/tt>. Let me read the files in filename order and plot the resulting slice locations.<\/p><pre class=\"codeinput\">folder = <span class=\"string\">\"\/Users\/eddins\/OneDrive - MathWorks\/General Reference\/D\/DICOM Head-Neck-PET-CT Dataset\/HN-CHUM-001\/08-27-1885-PANC. avec C.A. SPHRE ORL   tte et cou  -TP-74220\/3-StandardFull-07232\"<\/span>;\r\nd = dir(folder + <span class=\"string\">\"\/*.dcm\"<\/span>);\r\n<span class=\"keyword\">for<\/span> k = 1:length(d)\r\n    info = dicominfo(folder + <span class=\"string\">\"\/\"<\/span> + d(k).name);\r\n    slice_location(k) = info.SliceLocation;\r\n<span class=\"keyword\">end<\/span>\r\nplot(slice_location)\r\ntitle(<span class=\"string\">'Slice location'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/reading_dicom_volume_01.png\" alt=\"\"> <p>Uh oh. That's not good. Does that even make sense, I wondered? Are these all unique slice locations? Let me try sorting them, first, and then plotting them.<\/p><pre class=\"codeinput\">plot(sort(slice_location))\r\ntitle(<span class=\"string\">'Sorted slice locations'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/reading_dicom_volume_02.png\" alt=\"\"> <p>That's more like what I expected. But it means that my assumption about the filenames was completely off.<\/p><p>But wait! As I said, I was doing this the hard way. The easy way is to use the function <tt>dicomreadVolume<\/tt>, which uses the DICOM metadata to automatically figures out how to arrange the slices in the output volume.<\/p><pre class=\"codeinput\">[V,sp] = dicomreadVolume(folder);\r\n<\/pre><pre class=\"codeinput\">whos <span class=\"string\">V<\/span>\r\n<\/pre><pre class=\"codeoutput\">  Name        Size                     Bytes  Class    Attributes\r\n\r\n  V         512x512x1x90            47185920  int16              \r\n\r\n<\/pre><p>The size of the third dimension is 1 (we call that a <i>singleton<\/i> dimension) because the voxels do not have multiple color components. We can use <tt>squeeze<\/tt> to get rid of it.<\/p><pre class=\"codeinput\">V = squeeze(V);\r\n<\/pre><pre class=\"codeinput\">whos <span class=\"string\">V<\/span>\r\n<\/pre><pre class=\"codeoutput\">  Name        Size                   Bytes  Class    Attributes\r\n\r\n  V         512x512x90            47185920  int16              \r\n\r\n<\/pre><p>The second output of <tt>dicomreadVolume<\/tt> contains spatial information about the voxels.<\/p><pre class=\"codeinput\">sp\r\n<\/pre><pre class=\"codeoutput\">\r\nsp = \r\n\r\n  struct with fields:\r\n\r\n       PatientPositions: [90&times;3 double]\r\n          PixelSpacings: [90&times;2 double]\r\n    PatientOrientations: [2&times;3&times;90 double]\r\n\r\n<\/pre><p>I like to use <b>Volume Viewer<\/b> (<tt>volumeViewer<\/tt>) to figure out the best ways to visualize a volume.<\/p><pre>volumeViewer(V)<\/pre><p>It is helpful to use the spatial information returned by <tt>dicomreadVolume<\/tt> for <b>Volume Viewer<\/b>.<\/p><pre>slice_location = sort(slice_location);\r\nscale_factors = [sp.PixelSpacings(1,:) slice_location(2)-slice_location(1)];\r\nvolumeViewer(V,'ScaleFactors',scale_factors)<\/pre><p>Here's a <b>Volume Viewer<\/b> screen shot with this data:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/volume-viewer-screenshot-600w.png\" alt=\"\"> <\/p><p>From the <b>Volume Viewer<\/b>, you can save rendering and camera configuration information to a struct that you can pass to <tt>volshow<\/tt>, which displays the volume visualization in ordinary figure window. You still have to pass in the scale factors as a separate argument.<\/p><pre class=\"codeinput\">volshow(V,config,<span class=\"string\">'ScaleFactors'<\/span>,scale_factors);\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/reading_dicom_volume_03.png\" alt=\"\"> <p>Do you work with DICOM volume data in MATLAB? Let us know in the comments what you would like to see.<\/p><p><b>Data credits<\/b><\/p><div><ul><li>Cancer Imaging Archive: Clark K, Vendt B, Smith K, et al. The Cancer Imaging Archive (TCIA): Maintaining and Operating a Public Information Repository. Journal of Digital Imaging. 2013; 26(6): 1045-1057. doi: 10.1007\/s10278-013-9622-7.<\/li><li>Head-Neck-PET-CT collection: Valli&egrave;res, M. et al. Radiomics strategies for risk assessment of tumour failure in head-and-neck cancer. Sci Rep 7, 10117 (2017). doi: 10.1038\/s41598-017-10371-5<\/li><li>Subject ID: HN-CHUM-001<\/li><\/ul><\/div><script language=\"JavaScript\"> <!-- \r\n    function grabCode_fa754d585266430bb741967a7e484d89() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='fa754d585266430bb741967a7e484d89 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' fa754d585266430bb741967a7e484d89';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2019 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_fa754d585266430bb741967a7e484d89()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2019b<br><\/p><\/div><!--\r\nfa754d585266430bb741967a7e484d89 ##### SOURCE BEGIN #####\r\n%%\r\n% Earlier this year, I learned something about DICOM datasets that\r\n% surprised me. I had downloaded a Head-Neck CT+PET study, and I wanted to\r\n% create a volume array in MATLAB. I tried to do this the hard way at\r\n% first, and of course I got it wrong. (Spoiler: there's an easy way.)\r\n%\r\n% I naively assumed that there was some meaning to the order of filenames\r\n% in the DICOM dataset. Here is the folder I was looking at:\r\n%\r\n% <<https:\/\/blogs.mathworks.com\/steve\/files\/dir-output.png>>\r\n%\r\n% You can see that I had files of the form 00000.dcm, 00001.dcm, ...,\r\n% 000089.dcm. So, I read these files in filename order and concatenated\r\n% the data in the third dimension. And, I got a mess. I had assumed that\r\n% the filename order corresponded to physical slice order, but I was wrong.\r\n%\r\n% You can see this by looking at the |SliceLocation| field returned by\r\n% |dicominfo|. Let me read the files in filename order and plot the\r\n% resulting slice locations.\r\n\r\nfolder = \"\/Users\/eddins\/OneDrive - MathWorks\/General Reference\/D\/DICOM Head-Neck-PET-CT Dataset\/HN-CHUM-001\/08-27-1885-PANC. avec C.A. SPHRE ORL   tte et cou  -TP-74220\/3-StandardFull-07232\";\r\nd = dir(folder + \"\/*.dcm\");\r\nfor k = 1:length(d)\r\n    info = dicominfo(folder + \"\/\" + d(k).name);\r\n    slice_location(k) = info.SliceLocation;\r\nend\r\nplot(slice_location)\r\ntitle('Slice location')\r\n\r\n%%\r\n% Uh oh. That's not good. Does that even make sense, I wondered? Are these\r\n% all unique slice locations? Let me try sorting them, first, and then\r\n% plotting them.\r\n\r\nplot(sort(slice_location))\r\ntitle('Sorted slice locations')\r\n\r\n%%\r\n% That's more like what I expected. But it means that my assumption about\r\n% the filenames was completely off.\r\n%\r\n% But wait! As I said, I was doing this the hard way. The easy way is to\r\n% use the function |dicomreadVolume|, which uses the DICOM metadata to\r\n% automatically figures out how to arrange the slices in the output volume.\r\n\r\n[V,sp] = dicomreadVolume(folder);\r\n\r\n%%\r\nwhos V\r\n\r\n%%\r\n% The size of the third dimension is 1 (we call that a _singleton_\r\n% dimension) because the voxels do not have multiple color components. We\r\n% can use |squeeze| to get rid of it.\r\n\r\nV = squeeze(V);\r\n\r\n%%\r\nwhos V\r\n\r\n%%\r\n% The second output of |dicomreadVolume| contains spatial information about\r\n% the voxels.\r\n\r\nsp\r\n\r\n%%\r\n% I like to use *Volume Viewer* (|volumeViewer|) to figure out the best\r\n% ways to visualize a volume.\r\n% \r\n%  volumeViewer(V)\r\n%\r\n% It is helpful to use the spatial information returned by\r\n% |dicomreadVolume| for *Volume Viewer*.\r\n%\r\n%  slice_location = sort(slice_location);\r\n%  scale_factors = [sp.PixelSpacings(1,:) slice_location(2)-slice_location(1)];\r\n%  volumeViewer(V,'ScaleFactors',scale_factors)\r\n%\r\n% Here's a *Volume Viewer* screen shot with this data:\r\n%\r\n% <<https:\/\/blogs.mathworks.com\/steve\/files\/volume-viewer-screenshot-600w.png>>\r\n%\r\n% From the *Volume Viewer*, you can save rendering and camera\r\n% configuration information to a struct that you can pass to |volshow|,\r\n% which displays the volume visualization in ordinary figure window. You\r\n% still have to pass in the scale factors as a separate argument.\r\n\r\nvolshow(V,config,'ScaleFactors',scale_factors);\r\n\r\n%%\r\n% Do you work with DICOM volume data in MATLAB? Let us know in the comments\r\n% what you would like to see.\r\n%\r\n% *Data credits*\r\n% \r\n% * Cancer Imaging Archive: Clark K, Vendt B, Smith K, et al. The Cancer\r\n% Imaging Archive (TCIA): Maintaining and Operating a Public Information\r\n% Repository. Journal of Digital Imaging. 2013; 26(6): 1045-1057. doi:\r\n% 10.1007\/s10278-013-9622-7.\r\n% * Head-Neck-PET-CT collection: Valli\u00c3\u00a8res, M. et al. Radiomics strategies\r\n% for risk assessment of tumour failure in head-and-neck cancer. Sci Rep 7,\r\n% 10117 (2017). doi: 10.1038\/s41598-017-10371-5\r\n% * Subject ID: HN-CHUM-001\r\n##### SOURCE END ##### fa754d585266430bb741967a7e484d89\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/reading_dicom_volume_03.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>Earlier this year, I learned something about DICOM datasets that surprised me. I had downloaded a Head-Neck CT+PET study, and I wanted to create a volume array in MATLAB. I tried to do this the hard... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/11\/19\/how-to-read-and-visualize-a-dicom-volume\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":3885,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[1265,1267,154,705,68,484,1127,52,1271,1269,306],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3881"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/comments?post=3881"}],"version-history":[{"count":3,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3881\/revisions"}],"predecessor-version":[{"id":3893,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3881\/revisions\/3893"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/3885"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=3881"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=3881"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=3881"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}