Beispiel #1
0
        private void TraverseDirectoryTree(string root)
        {
            StreamWriter MetadataTable = null;
            StreamWriter CountsTable = null;
            int          errorCount = 0, copyCount = 0, deleteCount = 0, extractCount = 0, tableCount = 0;

            lostTagCount = 0;
            Stack <string> dirs  = new Stack <string>(100);
            List <string>  files = new List <string>();

            //Acquisition
            if (!Directory.Exists(root))
            {
                throw new ArgumentException("Specified root directory doesn't exist: " + root);
            }
            dirs.Push(root);
            while (dirs.Count > 0)
            {
                string currentDir = dirs.Pop();

                string[] subDirs;
                try { subDirs = Directory.GetDirectories(currentDir); }
                catch (UnauthorizedAccessException) { continue; }
                catch (DirectoryNotFoundException) { continue; }
                foreach (string str in subDirs)
                {
                    dirs.Push(str);
                }                                                                               // Push subdirectories on stack for traversal.

                string[] DICOMFiles = null;
                try { DICOMFiles = Directory.GetFiles(currentDir, "*.dcm"); }
                catch (UnauthorizedAccessException) { continue; }
                catch (DirectoryNotFoundException) { continue; }
                foreach (string file in DICOMFiles)
                {
                    files.Add(file);
                }
            }             // while dirs.Count > 0

            //Processing; could technically be split off into a separate fuuuuunctionnnnnn
            //but i don't give a shit
            bool DoSimpleParse = !(checkBoxCreateMetadataTable.Checked || checkBoxExtractMetadata.Checked);

            List <StudyObject> StudyFiles = new List <StudyObject>();

            int maxFiles = files.Count;

            for (int i = 0; i < maxFiles; i++)
            {
                float progress = ((float)(i + 1) / maxFiles);
                labelProgressBar.Text       = string.Format("Processing file {0} of {1} ({2:p0})", i + 1, maxFiles, progress);
                progressBarDICOMFiles.Value = (int)Math.Floor(progress * 100);
                Application.DoEvents();                 //keeps GUI alive by returning control to OS thread

                string     DICOMFile = files[i];
                string     NewFolderName, NewFileName, StudyTime, PatientName, StudyDate, PatientID;
                int        nFrames;
                DICOM.File dcf = null;
                try
                {                 //Take and open file, extract metadata
                    dcf         = new DICOM.File(DICOMFile, DoSimpleParse);
                    PatientName = TagValueOrDefault(0x00100010, dcf.Tags);
                    PatientID   = TagValueOrDefault(0x00100020, dcf.Tags);
                    StudyDate   = TagValueOrDefault(0x00080023, dcf.Tags);
                    StudyTime   = TagValueOrDefault(0x00080033, dcf.Tags);
                    if (!DoSimpleParse)
                    {
                        nFrames = int.Parse(TagValueOrDefault(0x00280008, dcf.Tags, "1"));
                    }
                    else
                    {
                        nFrames = ((new FileInfo(DICOMFile).Length / 1024) > 811) ? 125 : 1;
                    }                                                                                                 //single-image files are always 811KB big, so 811 * 1024 bytes; 125 frames is max and an assumption...

                    switch (comboBoxCopyMode.SelectedIndex)
                    {
                    case 0:                             //Both
                        break;

                    case 1:                             //Images only
                        if (nFrames > 1)
                        {
                            continue;
                        }
                        break;

                    case 2:                             //Videos only
                        if (nFrames < 2)
                        {
                            continue;
                        }
                        break;
                    }

                    NewFolderName = string.Format("{0}_{1}_{2}", StudyDate, PatientID, PatientName);
                    foreach (char c in InvalidPathChars)
                    {
                        NewFolderName = NewFolderName.Replace(c.ToString(), "");
                    }                                                                                                                     //sanitize path input
                    foreach (char c in new char[] { '/', '\\', '?' })
                    {
                        NewFolderName = NewFolderName.Replace(c.ToString(), "-");
                    }                                                                                                                                   //these aren't in InvalidPathChars since they're allowed in (full) paths
                    if (comboBoxExportMode.SelectedIndex == 1)
                    {
                        NewFolderName = PatientID.ToLowerInvariant() + Path.DirectorySeparatorChar + NewFolderName;
                    }
                    if (comboBoxExportMode.SelectedIndex == 2)
                    {
                        NewFolderName = "";
                    }            //no subfolders
                }                // try
                catch (FileNotFoundException) { errorCount++; continue; }

                if (checkBoxExportPerStudyCounts.Checked || checkBoxCreateMetadataTable.Checked)
                {
                    StudyObject obj         = new StudyObject();
                    string[]    FolderSplit = DICOMFile.Split(Path.DirectorySeparatorChar);
                    obj.Folder      = string.Join(Path.DirectorySeparatorChar.ToString(), FolderSplit.Take(FolderSplit.Length - 1));
                    obj.File        = FolderSplit.Skip(FolderSplit.Length - 1).ToArray()[0];
                    obj.PatientID   = PatientID;
                    obj.PatientName = PatientName;
                    obj.StudyDate   = StudyDate;
                    obj.StudyTime   = StudyTime;
                    obj.nFrames     = (short)nFrames;
                    obj.NewFolder   = NewFolderName;
                    StudyFiles.Add(obj);
                }

                DirectoryInfo OutputFolder = new DirectoryInfo(Path.Combine(textBoxOutputDirectory.Text, NewFolderName));
                if (checkBoxCopyAndRename.Checked || checkBoxExtractMetadata.Checked)
                {
                    //prepare receiving folder
                    if (!Directory.Exists(OutputFolder.FullName))
                    {
                        try { Directory.CreateDirectory(OutputFolder.FullName); }
                        catch (UnauthorizedAccessException)
                        {
                            MessageBox.Show("Cannot gain access to " + textBoxOutputDirectory.Text + ". Please select another directory or run as administrator.");
                            return;
                        }
                    }                    //if FolderName doesn't exist
                }

                if (checkBoxCopyAndRename.Checked)
                {
                    //Ensure numbering is consistent and continuous
                    int nPictures = 1, nVideos = 1;
                    //HACK to ensure Image/Video numbers are study-specific even if no subfolders are being used - previous code searched *.dcm responding to ALL files from ALL studies
                    string searchStr = string.Format("{0}_*_{1}_*.dcm", StudyDate, PatientID, PatientName);
                    foreach (char c in new char[] { '/', '\\', '?' })
                    {
                        searchStr = searchStr.Replace(c.ToString(), "-");
                    }                                                                                                                           //these are invalid as f**k for files
                    foreach (char c in InvalidFileChars)
                    {
                        searchStr.Replace(c.ToString(), "");
                    }
                    FileInfo[] ExistingFiles = OutputFolder.GetFiles(searchStr);

                    nPictures  += ExistingFiles.Where(n => n.Name.Contains("Image")).Count();
                    nVideos    += ExistingFiles.Where(n => n.Name.Contains("Video")).Count();
                    NewFileName = string.Format("{0}_{1}_{2}_{3}_{4}.dcm", StudyDate, StudyTime, PatientID, PatientName, (nFrames > 1 ? ("Video" + nVideos) : ("Image" + nPictures)));
                    foreach (char c in new char[] { '/', '\\', '?' })
                    {
                        NewFileName = NewFileName.Replace(c.ToString(), "-");
                    }                                                                                                                               //these are invalid as f**k for files
                    foreach (char c in InvalidFileChars)
                    {
                        NewFileName.Replace(c.ToString(), "");
                    }

                    //Copy File, Test File, if there's a request, Delete File
                    string NewFilePath = Path.Combine(OutputFolder.FullName, NewFileName);
                    if (File.Exists(NewFilePath))
                    {                     //Need to change name not to overwrite; safety first
                        int fileCount = OutputFolder.GetFiles(Path.GetFileNameWithoutExtension(NewFileName) + "*").Length + 1;
                        NewFileName = string.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(NewFileName), fileCount, Path.GetExtension(NewFileName));
                        NewFilePath = Path.Combine(OutputFolder.FullName, NewFileName);
                    }                     //File.Exists(NewFilePath)

                    try
                    {
                        File.Copy(DICOMFile, NewFilePath);
                        copyCount++;
                    }
                    catch (UnauthorizedAccessException) { MessageBox.Show("Access denied for export directory. Please select another directory or restart as administrator."); return; }
                    catch (IOException ioe) { MessageBox.Show("Unexpected IO Exception: " + ioe.Message); }

                    if (checkBoxCheckAndDeleteAfterCopy.Checked)
                    {
                        if (TestFileEquality(new FileInfo(DICOMFile), new FileInfo(NewFilePath)))
                        {
                            File.Delete(DICOMFile);
                            deleteCount++;
                        }
                    }            //checkBoxCheckAndDeleteAfterCopy.Checked
                }                //if checkboxCopyAndRename.Checked

                if (checkBoxExtractMetadata.Checked)
                {
                    using (StreamWriter metadataFile = new StreamWriter(Path.Combine(OutputFolder.FullName, "metadata.txt"), true))
                    {
                        metadataFile.WriteLine("Metadata extracted from " + DICOMFile + "\nTag\tValue");
                        foreach (DICOM.Tag tag in dcf.Tags)
                        {
                            metadataFile.WriteLine(tag.Name + "\t" + tag.Value);
                        }                                                                                                                    //TODO something fucky here.
                        metadataFile.WriteLine("\n");
                        extractCount++;
                    }
                }        //if checkboxExtractMetadata.Checked
            }            //foreach DICOMFile

            if (checkBoxCreateMetadataTable.Checked)
            {
                MetadataTable = new StreamWriter(Path.Combine(textBoxOutputDirectory.Text, DateTime.Now.ToString("yyyy-MM-dd_HH-mm") + "_DICOMFilesMetadata-Table.txt"), false);
                MetadataTable.WriteLine("Filename\tPatient ID\tPatient Name\tStudy Date\tStudy Time\tData Type\tFrames");
                foreach (var obj in StudyFiles)
                {
                    MetadataTable.WriteLine(string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", Path.Combine(obj.Folder, obj.File), obj.PatientID, obj.PatientName, obj.StudyDate, obj.StudyTime, (obj.nFrames > 1 ? "Movie" : "Image"), obj.nFrames));
                    tableCount++;
                }
                MetadataTable.Flush();
                MetadataTable.Close();
            }

            if (checkBoxExportPerStudyCounts.Checked)
            {
                HashSet <string> DataFolders = new HashSet <string>(StudyFiles.Select(x => x.Folder));

                CountsTable = new StreamWriter(Path.Combine(textBoxOutputDirectory.Text, DateTime.Now.ToString("yyyy-MM-dd_HH-mm") + "_DICOMFiles-DataPerStudyTable.txt"), false);
                CountsTable.WriteLine("Full Path\tStudy\tImages\tVideos");
                foreach (var DataFolder in DataFolders)
                {
                    HashSet <string> Studies = new HashSet <string>(StudyFiles.Where(x => x.Folder == DataFolder).Select(x => x.NewFolder));
                    foreach (var Study in Studies)
                    {
                        IEnumerable <StudyObject> Members = StudyFiles.Where(x => (x.NewFolder == Study && x.Folder == DataFolder));
                        CountsTable.WriteLine(string.Format("{0}\t{1}\t{2}\t{3}", DataFolder, Study, Members.Count(y => y.nFrames <= 1), Members.Count(y => y.nFrames > 1)));
                    }
                }
                CountsTable.Flush();
                CountsTable.Close();
            }

            //Generating success message to user
            string Report        = "Operation(s) completed successfully\n";
            string LostTagReport = lostTagCount > 0 ? lostTagCount + " values could not found and defaulted to 'undefined'.\n" : "";

            if (checkBoxCopyAndRename.Checked)
            {
                Report += copyCount + " files copied and renamed. " + LostTagReport;
            }
            if (checkBoxCheckAndDeleteAfterCopy.Checked)
            {
                Report += deleteCount + " files deleted.\n";
            }
            if (checkBoxExtractMetadata.Checked)
            {
                Report += "Metadata extracted for " + extractCount + " files. " + LostTagReport;
            }
            if (checkBoxCreateMetadataTable.Checked)
            {
                Report += tableCount + " files processed for metadata table. " + LostTagReport;
            }
            progressBarDICOMFiles.Value = 100;
            Application.DoEvents();
            MessageBox.Show(Report, "DICOM processing complete.");
        }         // TraverseDirectoryTree
            //default constructor
            public Tag(DICOM.File Dicomfile)
            {
                GroupElement = (uint)((Dicomfile.EASR.GetUShort() << 16) | Dicomfile.EASR.GetUShort());                 //grab two Ushorts, mash together into GroupElement
                Console.WriteLine("* Reading element 0x{0:x8}", GroupElement);

                if (DICOMSpec.Tags.ContainsKey(GroupElement))
                {
                    Name = DICOMSpec.Tags[GroupElement];
                    Console.WriteLine("* Element identified as \"{0}\"", Name);
                }

                if (Dicomfile.TransferSyntax == TransferSyntax.ExplicitVR)
                {
                    //If VR is explicit (according to transfer syntax agreed upon in file header), there's two different types of possible encoding (Table 7.1-1 and 7.1-2)
                    VR = Dicomfile.EASR.GetString(2);
                    if (TwoByteVRCodes.Contains(VR))                                    //some VR types encode themselves differently and need two bytes of spacing after the actual value, for SOME reason.
                    {
                        Dicomfile.EASR.GetUShort();                                     //Advance by two bytes (those two bytes are padding set to 0x000, thus they're discarded)
                        //Those bytes have to be discarded; if you just did a ReadInt32() you'd get a wrong number... right?
                        ValueLength = Dicomfile.EASR.GetInt();                          //Value length field is 4 bytes long for these VR codes
                    }
                    else
                    {
                        ValueLength = Dicomfile.EASR.GetUShort();
                    }            //Value length is a simple 2 byte integer for all other VR codes
                }                //if TransferSyntax == TransferSyntax.ExplicitVR

                if (Dicomfile.TransferSyntax == TransferSyntax.ImplicitVR)
                {
                    //If VR is implicit (according to transfer syntax agreed upon in file header), there's just one possible encoding (Table 7.1-3)
                    VR          = "IMPLICIT";
                    ValueLength = Dicomfile.EASR.GetInt();                      //Value length field is 4 bytes long for these VR codes
                }

                if (GroupElement == 0xFFFEE0DD && ValueLength == 0)                                     //Sequence Delimination tag (not item delimination!) found, e.g. a sequence has ended
                {
                    Dicomfile.Tags.Add(new Tag(GroupElement, -1, "Sequence End", new byte[0]));
                    return;
                }                                                                        //if (SubTag is Sequence End Tag)

                if (Dicomfile.TransferSyntax == TransferSyntax.ExplicitVR && VR == "SQ") //|| (Dicomfile.TransferSyntax == TransferSyntax.ImplicitVR && DICOMDataDictionary.SequenceItems.Keys.Contains(GroupElement))
                {
                    Dicomfile.Tags.Add(new Tag(this.GroupElement, this.ValueLength, "Sequence Start", new byte[0]));
                    if (this.ValueLength > 0)
                    {
                        Dicomfile.EASR.BaseStream.Seek(this.ValueLength, SeekOrigin.Current);
                        Dicomfile.Tags.Add(new Tag(this.GroupElement, 0, "Sequence End", new byte[0]));
                        return;
                    }                     //skip this data. TODO: Do proper implementation, see Table 7.5-1
                    else
                    {
                        //this is very hack-y and I apologise
                        ushort LastUS2 = 0;
                        while (true)
                        {
                            ushort us1 = Dicomfile.EASR.GetUShort();
                            ushort us2 = Dicomfile.EASR.GetUShort();
                            uint   GE1 = (uint)((us1 << 16) | us2);
                            uint   GE2 = (uint)((LastUS2 << 16) | us1);
                            if (GE1 == 0xFFFEE0DD || GE2 == 0xFFFEE0DD)
                            {
                                Dicomfile.EASR.GetInt();
                                return;
                            }                             //0xFFFEE0DD is the Sequence Delimiter, followed by 0x00000000 (thus the GetInt())
                            else
                            {
                                LastUS2 = us2;
                            }
                        }
                    }
                }                // if VR == SQ and transfer syntax == Explicit

                if (Name == "Pixel Data")
                {
                    return;
                }                                                     //again, stop reading in a bajillion pixels.

                if (ValueLength == -1)
                {
                    ValueLength = 0;
                    Dicomfile.Tags.Add(this);
                    return;
                }

                if (ValueLength % 2 != 0)
                {
                    throw new ArgumentException("Value Length must be an even number?!");
                }
                _Value = Dicomfile.EASR.ReadBytes(ValueLength);
                Value  = System.Text.Encoding.UTF8.GetString(_Value);
                DateTime ConversionDummy = new DateTime();

                switch (VR)
                {
                //---Important ones:---
                case "DA":                         //date, 8b fixed, YYYYMMDD
                    if (DateTime.TryParseExact(Value, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out ConversionDummy))
                    {
                        Value = ConversionDummy.ToString("yyyy-MM-dd");
                    }
                    break;

                case "DT":                         //DateTime 26b max, YYYYMMDDHHMMSS.FFFFFF&ZZXX
                    if (DateTime.TryParseExact(Value, "yyyyMMddHHmmss.ffffff", CultureInfo.InvariantCulture, DateTimeStyles.None, out ConversionDummy))
                    {
                        Value = ConversionDummy.ToString("yyyy-MM-dd_HH-mm");
                    }
                    break;

                case "TM":                         //Time
                    string format = Value.Contains('.') ? "HHmmss.ffffff" : "HHmmss";
                    if (DateTime.TryParseExact(Value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out ConversionDummy))
                    {
                        Value = ConversionDummy.ToString("HH-mm-ss");
                    }
                    break;

                case "FL":
                    if (ValueLength == 4)
                    {
                        Value = System.BitConverter.ToSingle(_Value, 0).ToString();
                    }
                    break;

                case "FD":
                    if (ValueLength == 8)
                    {
                        Value = System.BitConverter.ToDouble(_Value, 0).ToString();
                    }
                    break;

                case "SS":
                    if (ValueLength == 2)
                    {
                        Value = System.BitConverter.ToInt16(_Value, 0).ToString();
                    }
                    break;

                case "US":
                    if (ValueLength == 2)
                    {
                        Value = System.BitConverter.ToUInt16(_Value, 0).ToString();
                    }
                    break;

                case "SL":
                    if (ValueLength == 4)
                    {
                        Value = System.BitConverter.ToInt32(_Value, 0).ToString();
                    }
                    break;

                case "UL":
                    if (ValueLength == 4)
                    {
                        Value = System.BitConverter.ToUInt32(_Value, 0).ToString();
                    }
                    break;

                //---values no one cares about---
                case "UI":
                case "UN":
                case "UR":
                case "UT":
                //Unique Identifier			Unknown			Universal Resource Locator	Unlimited text
                case "CS":
                //Code string, 16b max, trim leading/trailing space [20]
                case "UC":
                case "SH":
                case "LO":
                //Unlimited Characters		short string	Long string
                case "LT":
                case "OB":
                case "OD":
                case "OF":
                case "OL":
                //Long Text					Other Byte		Other Double	Other Float	Other Long
                case "OW":
                case "AT":
                case "DS":
                //Other Word				"Attribute Tag"	decimal string
                case "AE":
                case "ST":
                case "AS":
                //Application Entity		Short Text		Age String, 4 bytes fixed
                case "IS":                         //12b max, 0-9+-[space]
                //int BufferInt;
                //if (int.TryParse(Value, out BufferInt)) {  }
                case "PN":                         //Person Name 64char max times 5, = [3D] delimits groups
                //Value = "PN Converted: " + System.Text.Encoding.UTF8.GetString(_Value);
                case "SQ":                         //Sequence of Items
                //TODO do multiplicity = n, loop until next SQ (get length, read length bytes, repeat)
                default:
                    break;
                }        //switch VR
                Dicomfile.Tags.Add(this);
            }            //public Tag