How to Write Your Own PACS: Receiving Images

This article will cover writing a storage server that is able to receive images, store them on your hard drive, create thumbnails of the images, parse out important header information, and store that header information in a database.

In my continuing effort to show off how handy my PHP DICOM class and the OFFIS DCMTK are, I’ve decided to write a series of articles on how to implement a PACS in PHP.

These articles will be focused on Linux, though I may amend them in the future to cover Windows. They will assume that you have some knowledge of PHP, mySQL, and the Linux command line.

You will need my PHP DICOM class installed. You will also need mySQL installed and have access to add users and databases.

One of the things I dislike about programming books is that they’ll spend pages upon pages showing snippets of code and explaining everything line by line. I’m not going to use that approach, I’m going to walk you through getting the example code running on your computer, testing it, and explaining how it works in a high level fashion. I tried to write the code plainly, simply, and use verbose comments through out explaining what is going on.

Lets get the sample code up and running!

Step 1:

wget http://www.deanvaughan.org/projects/PACS_Tutorial.tar.gz
tar xvf PACS_Tutorial.tar.gz
cd PACS_Tutorial/part1
chmod 755 import.php store_server.php send_test_image.php
chmod -R 777 received_images temp

Download all of the sample code and set permissions.

Step 2:

mysql -uroot -p < db.sql

We need to create a database and user for our PACS. The file db.sql will create a user, ‘pacs’, with password ‘pacspassword’, and grant it all privileges to a new database called ‘pacs’. It will then create two tables, studies and images.

If at all possible, don’t change any of this, it’ll make the tutorial a lot less of a headache. If you do change it, open up import.php, and look for mysql_connect. Change the values there as needed.

Step 3:

./store_server.php

With that command we’ve launched our storage server on port 1104. Notice that the script didn’t launch in the background? Don’t worry, I’ll cover that later on.

If you don’t want your server running on port 1104, edit store_server.php and its easy enough to change.

The storage server as is doesn’t care about AE titles, its promiscuous and will accept images from anyone.

Step 4:

Since our storage server is using our terminal, leave it running and open a new terminal for the next steps.

store_server.php is going to be receiving your images, after an image is received it will run another script, import.php. import.php will be responsible for moving the image to a safe place on the hard drive, parsing the image’s header information, placing that information into a database, and creating a JPEG thumbnail of the image for later use.

You can test to see if all of this works by sending an image to your computer’s IP address using port 1104. You can make something up for the AE title.

If you don’t have a way to send yourself images, I’ve included a small script that will send your new server one image.

./send_test_image.php

That will send dean.dcm from class_dicom.php’s examples over to your new server.

Once you have sent an image you can switch back to your terminal running store_server.php and see the log entries regarding the image transmission. Among many things the log will show you where the image was saved.

Step 5:

mysql -uroot -p < view_db.sql

Let’s take a look at the database. If all went well the command above should show you the contents of the database; information about the images sent to the server.

There are two tables.

The first table is studies. This table contains information obtained from the image pertaining to the study. Each study can have many images, in example, a chest study could have both a PA and lateral view; one study, two images.

The second table is images. This table contains information specific to an image.

You’ll see there isn’t a field for storing the file name of the image, this is because the file name is easily inferred from database information.

Take a look at this snippet from import.php:

$store_dir = "./received_images/$sent_from_ae/" . $img['year'] . "/" . $img['month'] . "/" . $img['day'] . "/" .
  $img['study_uid'];

The image is stored in a directory based on the AE title it came from, the appointment date, and the study uid. The image itself uses the SOP instance plus ‘.dcm’ for its file name. Since all of this information is in the database, we can find the file. Later tutorials will go into more detail on this.

Step 6:

Right now, the storage server is stuck running in its own terminal window, if you close the window the storage server stops running, not cool. You’ll want to get it running in the background and you’ll want it to start at boot.

This gets tricky as different flavors of Linux, and even different versions of those flavors, handle this differently. I’ll discuss the three most common approaches. If you have not done so already, control-c in the storage_server.php terminal to stop it.

CentOS 5.6+, Fedora, Red Hat, Ubuntu:

You should have a /etc/init directory. You can place script in this directory that init will use to start/stop and keep running your programs.

start on runlevel [3]
stop on runlevel [!$RUNLEVEL]
respawn
console output
exec /home/dean/PACS_Tutorial/part1/store_server.php

Create /init/store_server.conf and add all of that. Be sure to change the path to store_server.php to match your own setup.

‘initctl start store_server’ to start your server

inittab:

If you don’t have a /etc/init directory, you can always fall back to editing the inittab itself.

ss:3:respawn:/home/dean/PACS_Tutorial/part1/store_server.php

Edit /etc/inittab and add this line to the bottom of the file. Be sure to change the path so it points to your copy of store_server.php. Once you’re back at the command line, type ‘init q’. This will reload the inittab and start up store_server.php.

All else fails:

nohup store_server.php

nohup will start store_server.php in the background for you. It will not restart the process if it crashes as the methods above will.

The End?

Now you have a multi-threaded DICOM storage server capable of receiving thousands of studies worth of images everyday. The information it receives is placed into a database for easy access by other programs.

Stay tuned for part two where in I’ll show you how to write a user interface to your PACS.

60 comments

  1. I am trying to follow this tutorial and I am having a bit of an issue. I am using debian and not CentOS. I don’t know if that is a major problem or not.

    Here is the error message that I am getting when I execute the ./store_server.php from the command line.

    root@debian:/var/www/PACS_Tutorial/part1# ./store_server.php
    F: cannot read config file: not a valid abstract syntax UID: HardcopyColorImageStorage

    I was reading the store_server.php file and set the env variable for the TOOLKIT thinking that would fix the problem but it did not.

    Any hints?

    Thank you,

    1. The config file I provided, store_server_config.cfg, is for an older version of DCMTK, as such it has some syntaxes defined that are no longer supported in the new versions.

      If you open up store_server_config.cfg and comment out the line about HardcopyColorImageStorage it’ll work!

  2. Hi Dean,

    Thanks for your help so far. I am working with a OEC 9800 C-ARM and we are getting this error message “DICOM ERROR check configuration of DICOM device”. We can run the network configuration and tested the connectivity. It returns successful. In the copy to the server is listed by name in the copy paths. My concern is that there is no place to designate what folder the files are being copied to.

    I really don’t expect you to know this machine but in general in the DiCOM world, does the “to” folder have to be specified somewhere in the configuration?

    Thanks!

    1. I’m not familiar with that machine. Generally in DICOM we talk over the network. I would bet that either the sending or receiving end is strict about AE titles and they are not matching what it is expecting.

  3. Dean,

    Below is the contents of the store_server.php file. There is no -aec or -aet in the file. Could you tell me where to add those setting?

    Thank you!

    #!/usr/bin/php
    <?php
    /**
    * User: Dean Vaughan
    * Date: 9/4/12
    * Time: 10:22 AM
    * This should not be called by a web browser
    */

    // Since we're going to call this script from init, lets make sure we chdir to the directory the script resides in
    // That way I know how to find class_dicom.php relatively.
    chdir(dirname(__FILE__));

    require_once('../class_dicom_php/class_dicom.php');

    // We're going to build up the command and arguments we're going to use to run DCMTK's store server.
    $storescp_cmd = TOOLKIT_DIR . "/storescp -v -dhl -td 20 -ta 20 –fork " . // Be verbose, set timeouts, fork into multiple processes
    "-xf ./storescp.cfg Default " . // Our config file
    "-od ./temp/ " . // Where to put images we receive
    "-xcr \" ./import.php \"#p\" \"#f\" \"#c\" \"#a\"\" " . // Run this script with these args after image reception
    "1104 "; // Listen on this port

    system($storescp_cmd);

    1. storescp, for receiving images, doesn’t let you set an AE title. It should accept images from any AE.

      storescu, for sending images, does let you set an AE title with the -aec and -aet flags. These AE titles are also settable via the function call, $my_ae and $remote_ae/

  4. How can I test the PACS server from a PC? I need to verify that the server is can receive dicom images from another source other than the modality that we have. Your suggestions are greatly appreciated…

    Thanks!

  5. Dean,

    I have run the cleaner that you suggested and it can not connect to the server. I run the send test file and it works fine. I have gone back through the instructions twice and still the cleaner does not connect to the server. I opened the 1104 port on the server and it listens on 0.0.0.0 port 1104 (debian, lenny). I am stumped as to what can be wrong.

    Have you run into this before?

  6. Thanks for all of your work with this project! You mentioned “Stay tuned for part two where in I’ll show you how to write a user interface to your PACS.”. Any ideas re: when we might get to see part two? Great material!

    1. I’m slowly but surely working on. No real ETA. The next chapter is going to cover inserting information from import.php into a database, then having a web script that will list out the contents of the DB.

      1. I have written all of this into a PACs (Linux Based) Utilizing your Class and MySQL. The Server Stores Images, AutoRoutes them to other PACs/Locations. Compresses the Images. Stores DICOM Data in the DB. Supports Q/R/C-MOVE. And has Integrated Services with a Management WebSite.

        I am currently working on adding a web based upload to PACs feature. Once done, I will post as a fork to your DICOM Class on GitHub.

        After that i am incorporating a lightweight HTML5 DICOM Viewer as well.

        Great Class, Great Work!

        If anyone would like to see some scripts before I post to Git, please email me.

        1. Hello,

          Did you end up writing the lightweight HTML5 viewer? If you have a view I would definitely like to see it..

          Thanks,
          Gana

  7. Thanks a lot for these wonderfull tutorials..

    I tried your pacs tutorial. I tried with my multiframe dicom file. there is an error…
    output is :
    Sending file… E: No presentation context for: (XA) 1.2.840.10008.5.1.4.1.1.12.1 E: Store SCU Failed: 0006:0208 DIMSE No valid Presentation Context ID Something bad happened!

    What may be wrong?

    1. The config file I provided for storescp doesn’t support the presentation context for XA. If you try with a different file using a different presentation context it’ll work.

  8. Dear Dean,
    Thank you so much for helping to write pacs.
    I have a problem with storing info in database.
    I’m working with your codes. It’s working great and sending and receiving very well. It receives and stores image in my dicom_dir.
    But it seems that it’s not executing -xcr command to run store_handler.
    Could you please help me?
    Thanks

      1. Thanks for your reply Dean.
        Yes, i get output.
        I ran import.php in part1 folder separately and gave it values instead of arguments and that output tags and info and saved info in database.
        But when i set import.php as handler file, it doesn’t do that job.

        Emmmm…. I think i’m doing this in a wrong way. Should i run these on linux based os? Because storescp.exe runs an exe file as handler, but it can’t run php file.
        Could you please help me?

        (Excuse me, if my english is not very well)

        Thanks

          1. These are the steps i have done:
            1- downloaded PACS_Tutorial.tar.gz and unzipped in wams server’s www folder.
            2- copied dcmtk folder in PACS_Tutorial folder
            now the path is:
            c:/wamp/www/PACS_Tutorial
            and inside this folder i have:
            a) class_dicom_php
            b) dcmtk
            c) part1
            and README file
            3- imported db.sql file in phpMyAdmin (pacs user and pacs database created successfully)
            4- class_dicom.php file inside the class_dicom_php folder i set the TOOLKIT_DIR to:
            ‘/../dcmtk/bin’
            5- ran store_server.php from part1 folder and i got an error about HardcopyColorImageStorage systax and i commented that line.
            after that, server is running…
            6- ran send_test_image.php and i got message:
            Sending file… Sent!
            7- the sent file is saved in temp folder but nothing is stored in database using import.php file.

            Have i missed something?

            When i set variables in import.php file (file & folder & sent_to_ae & sent_from_ae) and open it by itself (http://localhost/PACS_Tutorial/part1/import.php), all image data (name & other stuff) are shown and data are saved in database.

            Any help will be appreciated.

          2. It sounds line import.php is running OK, it just cannot contact the database. If you run import.php on the command line (not via the browser), do you get any output? Does store_server.log have anything in it? Can you log into the SQL server with the same credentials import.php is using?

  9. Hi Dear Dean Vaughan
    Thank you for your excellent tools.
    I’m writing my personal pacs.
    Could you please help me to use c-find and c-move in php?
    How should i implement these?
    Thanks

      1. Thanks
        I took a look at those pages and i implemented my find using findscu.exe and my command is:

        findscu.exe -P -k “(0010,0010)=VAUGHAN*” localhost 1104 123.dcm

        but i’m getting this message error:

        D: Context ID: 1 (Abstract Syntax Not Supported)
        D: Abstract Syntax: =FINDPatientRootQueryRetrieveInformationModel

        E: No Acceptable Presentation Contexts
        E: 0006:0208 DIMSE No valid Presentation Context ID

        Any help would be appreciated.

          1. Thanks Dean for some great work – this is a brilliant way into the fairly obscure world of DICOM for PHP developers.

            I’d like to add to my application the ability to find, get and update DICOM files. I’ve look at FindSCU as suggested above but don’t see how, having received the find request, I would hook into my PHP to actually go and find the files requested and deliver them back to the SCU – there’s no -xcr which would allow me to point off to my PHP. I have a feeling I’m missing something basic in the way that DICOM/PACS works but if you could give me any pointers, I’d be very grateful.

            Thanks again

            Damion

  10. Hello Dean, I congratulate the Post is very good.

    However I am having the same problem as Ali Farhoudi reported above.

    I checked and the connection to the database is valid.

    Send_test_image.php’m running through the terminal, and am getting the confirmation messages sent:

    Sending file …
    Sent!

    The file is inserted into the Temp, but does not insert any record in the database and the images are also not uploaded in received_images directory.

    Could you help me with this?

    I thank your help.

  11. I am trying your pacs tutorial. In executing step 4 i.e. ./send_test_image.php
    I am getting following error
    Sending file…
    F: Association Request Failed: 0006:031b Failed to establish association
    F: 0006:0317 Peer aborted Association (or never connected)
    F: 0006:031c TCP Initialization Error: Connection refused

    I have followed upto step 3 exactly the way it instructed. My database is created with user ‘pacs’ and password ‘pacspassword’ and port is 1104. I didn’t change anything. Then also getting above error.
    Please help me to resolve the issue.

      1. How to check that ?
        And I also got following error while executing ./store_server.php
        F: cannot read config file: not a valid abstract syntax UID: HardcopyColorImageStorage
        Is this creating a problem?

  12. Thanks for Great Tutorial!
    My issue got resolved after I commented out line containing HardcopyColorImageStorage in PACS_Tutorial/part1/storescp.cfg file which was preventing ./store_server.php to run.

  13. Is it possible to encrypt the data when it is sent to database? Is there any solution to make the storescu encrypt the patient_id and other information, before send then to database?

          1. It was what I needed:

            $sql = “INSERT INTO studies(” .
            “`firstname`, ” .
            “`lastname`, ” .
            “`id`, ” .
            “`appt_date`, ” .
            “`dob`, ” .
            “`study_uid`, ” .
            “`study_desc`, ” .
            “`accession`, ” .
            “`history`, ” .
            “`institution`, ” .
            “`sent_from_ae`, ” .
            “`sent_to_ae` ” .
            “) VALUES (” .
            “(AES_ENCRYPT(‘$img[firstname]’,’senhadb’)), ” .
            “(AES_ENCRYPT(‘$img[lastname]’,’senhadb’)), ” .
            “\”$img[id]\”, ” .
            “\”$img[appt_date]\”, ” .
            “\”$img[dob]\”, ” .
            “\”$img[study_uid]\”, ” .
            “\”$img[study_desc]\”, ” .
            “\”$img[accession]\”, ” .
            “\”$img[history]\”, ” .
            “\”$img[institution]\”, ” .
            “\”$sent_from_ae\”, ” .
            “\”$sent_to_ae\” ” .
            “)”;

  14. Sorry Dean, can’t reply directly to your last message (no reply button!). Thanks so much for the swift reply. Re-reading my post, I don’t think I made it clear that I’m talking about a server application. I have an existing web app and I’m trying to create a DICOM way into it so that I can allow Osirix, etc to access the images that I’m also going to be displaying as jpegs in the web app. So what I’m trying to do is, as a sort of SCP, support the C-FIND, C-STORE etc commands that OsiriX expects. As I said before, may be barking up the wrong tree here completely but it seems to me that it should be possible….I just seem to be missing some vital piece of understanding in working out how to do it!
    Thanks again
    Damion

    1. Gotcha, you want dcmqrscp. It’ll allow you to set up a server you can use Osirix to query and retrieve files from. The program as included in the DCMTK is really just for testing, when you give it more than a couple hundred images to manage it doesn’t work well. For other projects I’ve worked on I’ve ended up rewriting it to use a mysql backend for the query portion.

      1. Aha, yes that looks like exactly what I need….without the restriction on number of images (a few hundred images in a series seems to be fairly commonplace). Don’t suppose your dcmqrscp backed onto MySQL is shareable 🙂

  15. hi Dean
    Great work I have to say, i was looking for making my own PACS server for research purposes , i have never used a Linux based OS before and yet I was able to make it thanks to you , I setup my pacs on ubuntu mate and i was able to send dicom images from another PC with no problems but i cant retrieve the dicom images back from the pacs server to my workstation , how will I do that ? thanks

  16. Hi Dean,

    Awesome! Great tutorial! I learn a lot from your tutorial and I have successfully followed your instruction and able to receive images from OsiriX. How can I access/browse/retrieve the data and images from the database using OsiriX. I am very puzzled.

    1. Hi Dean,

      I solved the previous problem. I am beginning to learn about DCMTK. I tried to import an Ultrasound DICOM file and had the following error:

      E: No presentation context for: (US) 1.2.840.10008.5.1.4.1.1.6.1
      E: Store SCU Failed: 0006:0208 DIMSE No valid Presentation Context ID
      I: Aborting Association

      What could I have missed?

      Thanks.

  17. Looks good from all the comments… but I’m having no luck with the .tar.gz file! I can’t open it on Windows or Linux. On my Ubuntu server, I get:
    gzip: stdin: not in gzip format
    tar: Child returned status 1
    tar: Error is not recoverable: exiting now

    Opening in Windows simply gives “Could not open the file as an archive”.

      1. I got there in the end – had a friend download it and email it to me, which worked! Weird thing was, I tried it from a different internet connection as well…

  18. Hello, great post!

    I have this receiving a dicom file.

    Not valid presentation contexts.

    Aborted.

    Do you know what happen?

    Thank you!

  19. Hello Dean, how are you?

    First of all I would like to congratulate you for PHP DICOM Class, this helped me a lot in a project I’m developing. THANK YOU SO MUCH.

    But I’m having a little problem and I’m not able to find a solution. I wonder if you could help me.

    I’m using your example, “How to Write Your Own PACS” and store_server.php is working perfectly and storing all images correctly. However, when I try to send from an Ultrasound, the system generates the following message:

    I: Association Received in child process (pid: 5550)
    I: Association Acknowledged (Max Send PDV: 15988)
    I: (but no valid presentation contexts)

    You could tell me what was wrong.

Leave a Reply

Your email address will not be published. Required fields are marked *