download DICOM files from PACS

48 views
Skip to first unread message

SEB BEC

unread,
Mar 11, 2024, 1:12:05 AMMar 11
to Fellow Oak DICOM

I got the patient Id, now I need to download dicom files from the PACS system.

How to download all associated DICOM files pertaining to that Patient ID?

Alieksandr Kuznietsov

unread,
Mar 11, 2024, 2:22:26 PMMar 11
to Fellow Oak DICOM
Sequentially make C-FIND requests to search for UIDs of studies and series, and then make a C-GET request to receive files.


понедельник, 11 марта 2024 г. в 07:12:05 UTC+2, sbe...@gmail.com:

SEB BEC

unread,
Mar 11, 2024, 7:49:08 PMMar 11
to Fellow Oak DICOM

I have been trying to make the example to work, unfortunately, I am missing something.

-My configuration looks like this

private const string _storagePath = @"c:\DicomStorage";

// values of the Query Retrieve Server to test with.

private const string _qrServerHost = "172.16.12.32"; // "www.dicomserver.co.uk";

private const int _qrServerPort = 57347; // 104;

private const string _qrServerAET = "MyDaemon"; // "STORESCP";

private const string _aet = "localAE";

-The createCMoveByStudyUID looks like this

var cMoveRequest = CreateCMoveByStudyUID("MyDaemon", studyUID);

-Server daemon is running (MyDamone @ 57347) –tested with diff apps

-The LocalAE is trusted application

-Error I am getting

Patient SJH^SJH, -Study from 1/10/2011 12:00:00 AM with UID 1.2.840.113704.1.111.7872.1294685097.1

Success

Success

Error sending datasets: Move Destination unknown

Failure [a801: Move Destination unknown] -> Move destination Application Entity is not configured

-tried to change

var cMoveRequest = CreateCMoveByStudyUID(_storagePath, studyUID);

then I get

Patient SJH^SJH, -Study from 1/10/2011 12:00:00 AM with UID 1.2.840.113704.1.111.7872.1294685097.1

Success

Success

FellowOakDicom.Network.Client.DicomClient

 

Unhandled Exception: FellowOakDicom.DicomValidationException: Content "(0000,0600) AE Move Destination" does not validate VR AE: Number of items 2 does not match ValueMultiplicity 1

   at FellowOakDicom.DicomElement.ValidateVM()

   at FellowOakDicom.DicomElement.Validate()

Alieksandr Kuznietsov

unread,
Mar 12, 2024, 4:16:23 AMMar 12
to Fellow Oak DICOM
In order to use C-MOVE you need to launch your service to receive files and register it on the PACS server. It is better to use the C-GET protocol.
Here is my working code that gets the file from the server and saves it to the DICOM folder. To get all the files of a patient, you need to sequentially go through all his studies, and in each study all the series.
 
private async Task<bool> GetDicomSeriesUids(string SeriesUID, string StudyUID, DicomPacs pacs)
        {
            bool seriesGetSuccess = false;
            try
            {
                var clientGET = DicomClientFactory.Create(pacs.IpServer, short.Parse(pacs.DicomPort), false, pacs.LocalAet, pacs.AeTitle);
                var cGetRequest = new DicomCGetRequest(StudyUID, SeriesUID);
                var pcs = DicomPresentationContext.GetScpRolePresentationContextsFromStorageUids(
                             DicomStorageCategory.Image,
                             DicomTransferSyntax.ExplicitVRLittleEndian,
                             DicomTransferSyntax.ImplicitVRLittleEndian,
                             DicomTransferSyntax.ImplicitVRBigEndian,
                             DicomTransferSyntax.DeflatedExplicitVRLittleEndian,
                             DicomTransferSyntax.JPEG2000Lossless,
                             DicomTransferSyntax.JPEG2000Lossy,
                             DicomTransferSyntax.JPEGLSLossless,
                             DicomTransferSyntax.JPEGLSNearLossless);
                var pcsSR = DicomPresentationContext.GetScpRolePresentationContextsFromStorageUids(
                            DicomStorageCategory.StructuredReport,
                            DicomTransferSyntax.ExplicitVRLittleEndian,
                            DicomTransferSyntax.ImplicitVRLittleEndian,
                            DicomTransferSyntax.ImplicitVRBigEndian,
                            DicomTransferSyntax.ExplicitVRBigEndian,
                            DicomTransferSyntax.DeflatedExplicitVRLittleEndian,
                            DicomTransferSyntax.JPEG2000Lossless,
                            DicomTransferSyntax.JPEG2000Lossy,
                            DicomTransferSyntax.JPEGLSLossless,
                            DicomTransferSyntax.JPEGLSNearLossless);
                List<DicomDataset> imageDataset = new List<DicomDataset>();
                clientGET.OnCStoreRequest += (req) =>
                {
                    imageDataset.Add(req.Dataset);
                    return Task.FromResult(new DicomCStoreResponse(req, DicomStatus.Success));
                };
                clientGET.AdditionalPresentationContexts.AddRange(pcs);
                clientGET.AdditionalPresentationContexts.AddRange(pcsSR);
                await clientGET.AddRequestAsync(cGetRequest);
                await clientGET.SendAsync();
                if (imageDataset.Count > 0)                {
                    if (!Directory.Exists(Path.Combine(pathDownload, "DICOM")))
                    {
                        Directory.CreateDirectory(Path.Combine(pathDownload, "DICOM"));
                    }
                    foreach (DicomDataset dataset in imageDataset)
                    {
                        seriesGetSuccess = seriesGetSuccess || SaveImage(dataset, Path.Combine(pathDownload, "DICOM"));
                    }
                }
                return seriesGetSuccess;
            }
            catch (Exception ex)
            {
                Program.Log.Error($"ОШИБКА процедуры Загрузка снимков по УИДам исследования и серии: {ex.Message}. {ex.StackTrace}");
                return false;
            }
        }
    
        [Obsolete]
        private bool SaveImage(DicomDataset dataset, string pathDicomDir)
        {
            try
            {
                var studyUid = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, "").Trim();
                var seriesUid = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, "").Trim();
                var instUid = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, "").Trim();
                dataset.AutoValidate = false; // Отключаю проверку валидности датасета дайкома, т.к. не пропускал ведущие нули в УИДе
                                              // т.к. это устаревшее свойство, то перед заголовком процедуры вставлено [Obsolete]
                var path = Path.Combine(pathDicomDir, Crc32Algorithm.Compute(Encoding.UTF8.GetBytes(studyUid)).ToString("X8"));
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                path = Path.Combine(path, Crc32Algorithm.Compute(Encoding.UTF8.GetBytes(seriesUid)).ToString("X8"));
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                path = Path.Combine(path, Crc32Algorithm.Compute(Encoding.UTF8.GetBytes(instUid)).ToString("X8"));
                // Записываем файл на диск
                DicomFile dcmFile = new DicomFile(dataset);
                dcmFile.Save(path);
                if (File.Exists(path))
                {
                    return true;
                }
                else { return false; }
            }
            catch (Exception ex)
            {
                Program.Log.Error($"ОШИБКА процедуры Сохранение снимков из полученного датасета в файл: {ex.Message}. {ex.StackTrace}");

                return false;
            }
        }

public class DicomPacs
    {     
        public string? AeTitle { get; set; } = "";   
        public string? IpServer { get; set; } = "";
        public string? DicomPort { get; set; } = "";         
        public string? LocalAet { get; set; } = "";       
        public string? Timeout { get; set; } = "30";
    }

Good materials for understanding the difference between the C-MOVE protocols and C-GET can be found here:
На русском языке у меня здесь:


вторник, 12 марта 2024 г. в 01:49:08 UTC+2, sbe...@gmail.com:

stacy....@gmail.com

unread,
Mar 12, 2024, 5:36:01 AMMar 12
to Fellow Oak DICOM
In my own experience, the "www.dicomserver.co.uk" of medicalconnections generally cannot support C-MOVE, only C-GET. To be able to exchange data with a dicom server using C-MOVE (which is mostly used in real life, almost exclusively in hospitals), you need to tell the server the IP and the AET of the receiving machine. To my best knowledge, this is no option with the medicalconnections  server. At least, it was so in 2020.
To test C-MOVE it would be most practical to use a local DICOM server or a server in the local network. (I used ConQuest, but it is probably not the most modern one). I also wrote my own DICOM server I could easily make public, but it uses DicomObjects which is not quite free.

Alexandr, thanks for the Subramanian link, the site is great. I could also add Oleg Pianykh's book " Digital Imaging and Communications in Medicine (DICOM): A Practical Introduction and Survival Guide" (ISBN 978-3642108495).

Picasso

unread,
Mar 12, 2024, 10:06:18 PMMar 12
to Fellow Oak DICOM

Thank you, both Alexandr and Stacy, for providing awesome references. Both ways seemed to have potential and may be applicable for different scenarios.

I do wasn’t to get C-Move work (as you stated, it is more representative).

Just to recap:

1.  I do have two local daemons running (for images: AE=DBDaemon /port=57347  for file storage: AE=FD /port 104

 

private const string _storagePath = @"c:\DicomStorage";

private const string _qrServerHost = "172.16.12.32"; // not using "www.dicomserver.co.uk, using a local daemon server";

private const int _qrServerPort = 57347; //

private const string _qrServerAET = "DBDaemon";

private const string _aet = "FD"// this is the second daemon listening to receive and store files";

private const int _qrSerFilPort = 104;

 

This is the bit I am not too sure about.

var cMoveRequest = CreateCMoveByStudyUID("FD", studyUID);

 

My guess is that it needs the port number (104) for the destination but not sure where to add it. I don't think there is anything else I need to change in the code

Many thanks bot of you.

Alieksandr Kuznietsov

unread,
Mar 13, 2024, 4:12:29 AMMar 13
to Fellow Oak DICOM
Saravanan Subramanian unfortunately does not have an example for the C-MOVE operation for fo-dicom library. I did this on my website: https://telepacs.com.ua/?p=801. It's in Russian, but I think you can use Google translator.
Briefly: in order to send a study from the AET1 repository to the AET2 repository using C-MOVE, it is necessary that the AET1 repository has information about AET2 -AeTitle, IP and port.
You will not be able to retrieve studies stored on dicomserver.co.uk using C-MOVE because you cannot specify in its configuration information about the storage location where you want to send the files.
Look at the example on my website, it works.


среда, 13 марта 2024 г. в 04:06:18 UTC+2, Picasso:

Picasso

unread,
Mar 13, 2024, 7:23:43 PMMar 13
to Fellow Oak DICOM
SUPER!!!
Many thanks indeed.
Я очень ценю вашу помощь. Большое спасибо.

Reply all
Reply to author
Forward
0 new messages