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