├── .gitmodules ├── PacsExplorer ├── App.xaml.cs ├── Converters │ ├── NotNullToBooleanConverter.cs │ └── DateConverter.cs ├── DicomSeriesQuery.cs ├── DicomSeries.cs ├── PacsExplorer.csproj ├── DicomStudyQuery.cs ├── ConfigWindow.xaml.cs ├── Settings.settings ├── App.xaml ├── App.config ├── DicomStudy.cs ├── MainWindow.xaml ├── ConfigWindow.xaml ├── Settings.Designer.cs └── MainWindow.xaml.cs ├── .github └── workflows │ └── dotnet.yml ├── README.md ├── PacsExplorer.sln ├── .gitattributes └── .gitignore /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "DicomScu"] 2 | path = DicomScu 3 | url = https://github.com/iberisoft/DicomScu.git 4 | -------------------------------------------------------------------------------- /PacsExplorer/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace PacsExplorer 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | submodules: true 14 | - name: Build 15 | run: dotnet build PacsExplorer.sln 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PACS Explorer 2 | 3 | [![.NET Framework](https://github.com/iberisoft/PacsExplorer/actions/workflows/dotnet.yml/badge.svg)](https://github.com/iberisoft/PacsExplorer/actions/workflows/dotnet.yml) 4 | 5 | A window implementing DICOM store and query/retrieve SCU. 6 | 7 | The tool can upload DICOM files onto a PACS as well as query/retrieve them. If you install [MicroDicom Viewer](https://www.microdicom.com/) 8 | the tool will view retrieved images in MicroDicom. 9 | -------------------------------------------------------------------------------- /PacsExplorer/Converters/NotNullToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace PacsExplorer.Converters 6 | { 7 | class NotNullToBooleanConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value != null; 10 | 11 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PacsExplorer/Converters/DateConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace PacsExplorer.Converters 6 | { 7 | class DateConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => System.Convert.ToDateTime(value).ToShortDateString(); 10 | 11 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PacsExplorer/DicomSeriesQuery.cs: -------------------------------------------------------------------------------- 1 | using Dicom; 2 | using DicomScu; 3 | 4 | namespace PacsExplorer 5 | { 6 | public class DicomSeriesQuery : IDicomQuery 7 | { 8 | public string Modality { get; set; } = ""; 9 | 10 | public string Number { get; set; } = ""; 11 | 12 | public string Description { get; set; } = ""; 13 | 14 | public void CopyTo(DicomDataset dataset) 15 | { 16 | dataset.AddOrUpdate(DicomTag.Modality, Modality); 17 | dataset.AddOrUpdate(DicomTag.SeriesNumber, Number); 18 | dataset.AddOrUpdate(DicomTag.SeriesDescription, Description); 19 | dataset.AddOrUpdate(DicomTag.NumberOfSeriesRelatedInstances, ""); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PacsExplorer/DicomSeries.cs: -------------------------------------------------------------------------------- 1 | using Dicom; 2 | 3 | namespace PacsExplorer 4 | { 5 | class DicomSeries 6 | { 7 | public DicomSeries(DicomDataset dataset) 8 | { 9 | Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, ""); 10 | Number = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, ""); 11 | Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, ""); 12 | Uid = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, ""); 13 | if (dataset.TryGetSingleValue(DicomTag.NumberOfSeriesRelatedInstances, out int instanceCount)) 14 | { 15 | InstanceCount = instanceCount; 16 | } 17 | } 18 | 19 | public string Modality { get; } 20 | 21 | public string Number { get; set; } 22 | 23 | public string Description { get; } 24 | 25 | public string Uid { get; } 26 | 27 | public int? InstanceCount { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PacsExplorer/PacsExplorer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net472 6 | true 7 | Pavel Zaytsev 8 | PACS Explorer. 9 | Copyright © 2024 10 | 1.3.3 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | True 25 | True 26 | Settings.settings 27 | 28 | 29 | 30 | 31 | 32 | SettingsSingleFileGenerator 33 | Settings.Designer.cs 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /PacsExplorer/DicomStudyQuery.cs: -------------------------------------------------------------------------------- 1 | using Dicom; 2 | using DicomScu; 3 | using System; 4 | 5 | namespace PacsExplorer 6 | { 7 | public class DicomStudyQuery : IDicomQuery 8 | { 9 | public string PatientName { get; set; } = ""; 10 | 11 | public string PatientId { get; set; } = ""; 12 | 13 | public string AccessionNumber { get; set; } = ""; 14 | 15 | public string Modality { get; set; } = ""; 16 | 17 | public DateTime StartDate { get; set; } = DateTime.Today.AddYears(-1); 18 | 19 | public DateTime EndDate { get; set; } = DateTime.Today; 20 | 21 | public string Description { get; set; } = ""; 22 | 23 | public void CopyTo(DicomDataset dataset) 24 | { 25 | dataset.AddOrUpdate(DicomTag.PatientName, PatientName); 26 | dataset.AddOrUpdate(DicomTag.PatientID, PatientId); 27 | dataset.AddOrUpdate(DicomTag.AccessionNumber, AccessionNumber); 28 | dataset.AddOrUpdate(DicomTag.ModalitiesInStudy, Modality); 29 | dataset.AddOrUpdate(DicomTag.StudyDate, new DicomDateRange(StartDate, EndDate)); 30 | dataset.AddOrUpdate(DicomTag.StudyDescription, Description); 31 | dataset.AddOrUpdate(DicomTag.NumberOfStudyRelatedInstances, ""); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PacsExplorer/ConfigWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using Xceed.Wpf.Toolkit; 5 | 6 | namespace PacsExplorer 7 | { 8 | /// 9 | /// Interaction logic for ConfigWindow.xaml 10 | /// 11 | public partial class ConfigWindow : Window 12 | { 13 | public ConfigWindow() 14 | { 15 | InitializeComponent(); 16 | } 17 | 18 | private void Submit(object sender, RoutedEventArgs e) 19 | { 20 | DialogResult = true; 21 | } 22 | 23 | private void Window_Closed(object sender, EventArgs e) 24 | { 25 | if (DialogResult == true) 26 | { 27 | ServerHost.GetBindingExpression(TextBox.TextProperty).UpdateSource(); 28 | QrServerAeTitle.GetBindingExpression(TextBox.TextProperty).UpdateSource(); 29 | QrServerPort.GetBindingExpression(IntegerUpDown.ValueProperty).UpdateSource(); 30 | StoreServerAeTitle.GetBindingExpression(TextBox.TextProperty).UpdateSource(); 31 | StoreServerPort.GetBindingExpression(IntegerUpDown.ValueProperty).UpdateSource(); 32 | ClientAeTitle.GetBindingExpression(TextBox.TextProperty).UpdateSource(); 33 | ClientPort.GetBindingExpression(IntegerUpDown.ValueProperty).UpdateSource(); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PacsExplorer/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | localhost 7 | 8 | 9 | SERVER 10 | 11 | 12 | 106 13 | 14 | 15 | SERVER 16 | 17 | 18 | 104 19 | 20 | 21 | CLIENT 22 | 23 | 24 | 1000 25 | 26 | 27 | C:\Program Files\MicroDicom\mDicom.exe 28 | 29 | 30 | -------------------------------------------------------------------------------- /PacsExplorer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29613.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacsExplorer", "PacsExplorer\PacsExplorer.csproj", "{1B2A83C5-7E69-4D2F-B754-D4CEA20E6501}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DicomScu", "DicomScu\DicomScu\DicomScu.csproj", "{9A70DDB2-3D1C-4D2B-908E-894F5C005011}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {1B2A83C5-7E69-4D2F-B754-D4CEA20E6501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {1B2A83C5-7E69-4D2F-B754-D4CEA20E6501}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {1B2A83C5-7E69-4D2F-B754-D4CEA20E6501}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {1B2A83C5-7E69-4D2F-B754-D4CEA20E6501}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {9A70DDB2-3D1C-4D2B-908E-894F5C005011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9A70DDB2-3D1C-4D2B-908E-894F5C005011}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9A70DDB2-3D1C-4D2B-908E-894F5C005011}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9A70DDB2-3D1C-4D2B-908E-894F5C005011}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {4E96D08B-2BDC-45A2-B63C-DD2156680502} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /PacsExplorer/App.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 14 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /PacsExplorer/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | C:\Program Files\MicroDicom\mDicom.exe 18 | 19 | 20 | 21 | 22 | 23 | 24 | localhost 25 | 26 | 27 | SERVER 28 | 29 | 30 | 106 31 | 32 | 33 | SERVER 34 | 35 | 36 | 104 37 | 38 | 39 | CLIENT 40 | 41 | 42 | 1000 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /PacsExplorer/DicomStudy.cs: -------------------------------------------------------------------------------- 1 | using Dicom; 2 | using System; 3 | using System.IO; 4 | 5 | namespace PacsExplorer 6 | { 7 | class DicomStudy 8 | { 9 | public DicomStudy(DicomDataset dataset) 10 | { 11 | PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, ""); 12 | PatientId = dataset.GetSingleValueOrDefault(DicomTag.PatientID, ""); 13 | AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, ""); 14 | Modality = dataset.GetSingleValueOrDefault(DicomTag.ModalitiesInStudy, ""); 15 | if (dataset.TryGetSingleValue(DicomTag.StudyDate, out DateTime date)) 16 | { 17 | Date = date; 18 | } 19 | Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, ""); 20 | Uid = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, ""); 21 | if (dataset.TryGetSingleValue(DicomTag.NumberOfStudyRelatedInstances, out int instanceCount)) 22 | { 23 | InstanceCount = instanceCount; 24 | } 25 | } 26 | 27 | public DicomFile CreateEncapsulatedPdf(string filePath) 28 | { 29 | var dataset = new DicomDataset(); 30 | 31 | dataset.AddOrUpdate(DicomTag.PatientName, PatientName); 32 | dataset.AddOrUpdate(DicomTag.PatientID, PatientId); 33 | dataset.AddOrUpdate(DicomTag.AccessionNumber, AccessionNumber); 34 | if (Date != null) 35 | { 36 | dataset.AddOrUpdate(DicomTag.StudyDate, Date.Value); 37 | } 38 | dataset.AddOrUpdate(DicomTag.StudyDescription, Description); 39 | dataset.AddOrUpdate(DicomTag.StudyInstanceUID, Uid); 40 | 41 | dataset.AddOrUpdate(DicomTag.Modality, "DOC"); 42 | dataset.AddOrUpdate(DicomTag.ConversionType, "WSD"); 43 | dataset.AddOrUpdate(DicomTag.SeriesInstanceUID, DicomUID.Generate()); 44 | dataset.AddOrUpdate(DicomTag.SOPClassUID, DicomUID.EncapsulatedPDFStorage); 45 | dataset.AddOrUpdate(DicomTag.SOPInstanceUID, DicomUID.Generate()); 46 | 47 | dataset.AddOrUpdate(DicomTag.DocumentTitle, Path.GetFileNameWithoutExtension(filePath)); 48 | dataset.AddOrUpdate(DicomTag.MIMETypeOfEncapsulatedDocument, "application/pdf"); 49 | dataset.AddOrUpdate(DicomTag.EncapsulatedDocument, File.ReadAllBytes(filePath)); 50 | 51 | return new DicomFile(dataset); 52 | } 53 | 54 | public string PatientName { get; } 55 | 56 | public string PatientId { get; } 57 | 58 | public string AccessionNumber { get; } 59 | 60 | public string Modality { get; } 61 | 62 | public DateTime? Date { get; } 63 | 64 | public string Description { get; } 65 | 66 | public string Uid { get; } 67 | 68 | public int? InstanceCount { get; set; } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /PacsExplorer/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |