Open-source project
pydicom/pynetdicom avatar
pydicom/pynetdicom

pynetdicom: speaking DICOM over the network from Python

A Python implementation of the DICOM networking protocol

576 stars196 forksPythonMIT

At a glance

What is it?
pynetdicom is a pure Python implementation of the DICOM networking protocol, built on pydicom, that lets you write a Service Class User or a Service Class Provider without a native library. It covers the DIMSE-C and DIMSE-N services, ships eight command line applications, and has been the implementation behind much of the Python DICOM ecosystem for years.
Who is it for?
Adopt pynetdicom if you need to talk to a DICOM modality or a PACS from Python and you would rather not bind a native library into your process, since the whole protocol is implemented in pure Python. Do not adopt it as a DICOM server for clinical use without reading the security posture of your deployment, because the protocol it implements negotiates its own association over TCP with no encryption built in.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What DICOM networking actually is

DICOM is the international standard for medical images and related information, and it defines the formats and communication protocols for media exchange in radiology, cardiology, radiotherapy and other medical domains.

pynetdicom is a pure Python package that implements the DICOM networking protocol, originally based on the legacy pynetdicom project. Working with pydicom, it allows the easy creation of DICOM Service Class Users, abbreviated SCUs, and Service Class Providers, abbreviated SCPs.

Those two acronyms are the whole architecture of DICOM networking. A service class user is a client that requests something. A service class provider is a server that offers it. A modality is a SCU that pushes images to a PACS, a PACS is an SCP that receives them, and the same machine can be either depending on what it is doing. The protocol is built around a negotiation phase, where the two sides agree on which operations are supported, followed by a phase where the actual messages flow.

The main user class is the Application Entity, AE, which represents a DICOM application entity. With it you can start the application as an SCP by specifying the supported presentation contexts and then calling start_server and waiting for incoming association requests. Or you can use the application as an SCU by specifying the presentation contexts you want the peer SCP to support, then requesting an association via the associate method, which returns an Association thread.

That last detail is worth pausing on. The association is a thread, not a coroutine, which tells you the implementation model underneath, and it matters for anyone building on top of it, because it determines how you structure concurrency in a server that has to handle many modalities at once.

The DIMSE services, and what the two letter families mean

Once an association is established, the services available to it are used by sending DIMSE-C and DIMSE-N messages, and the readme tabulates the full set for the SCU side.

The two families are the DIMSE service groups. The C services are the ones with a response. The SCU services available are the C-ECHO for testing that the peer is alive, C-FIND for querying a data set such as a patient or study list, C-GET for retrieving a data set, C-MOVE for asking the peer to move data to a third party, and C-STORE for sending a data set to the peer.

The N services are the notification ones, which have no response. The SCU side includes N-ACTION, N-CREATE, N-DELETE, N-EVENT-REPORT, N-GET and N-SET. N-CREATE and N-DELETE are the ones that manage the lifetime of an object on the peer, N-ACTION asks the peer to perform an operation on an existing object, and N-EVENT-REPORT is how a peer asynchronously tells you something happened, which is how a modality announces that a new study is available without being asked.

The readme links each service to the method on the Association class that implements it, and the DIMSE-C and DIMSE-N references point at the relevant parts of the DICOM standard.

The practical picture is that pynetdicom covers the whole service surface a DICOM client needs, not a subset. That is what makes it usable as a PACS simulator or a test harness, which is where most of its users meet it, and the applications directory at the top of the package reflects that.

Association negotiation, and what the thread means for a server

Everything in this library sits underneath one negotiation step, and understanding it explains most of the API shape.

The DICOM association is not just a TCP connection. Before any data moves, the two sides exchange what are called presentation contexts, which are the pairs of abstract syntax and transfer syntax that one side is willing to accept. The SCU specifies the presentation contexts it wants the peer SCP to support, and the SCP replies with the subset it actually accepts, and every subsequent message is bound to one of those agreed pairs.

So a server is not just accepting connections. It has to be configured with the set of things it is willing to receive, and if a modality asks for a transfer syntax the server does not offer, the request is refused at association time and nothing downstream can recover it. That is a common reason a first association attempt fails, and it is a configuration problem rather than a bug.

The negotiation is also where the transfer syntax lives, which is the encoding of the pixel data itself. A compressed transfer syntax and an uncompressed one produce very different network traffic for the same study, and choosing badly is how a PACS integration ends up transferring several times more data than necessary or failing to decode at all.

Then there is the threading model. The associate method returns an Association thread, which means each association is a thread of control. For a SCU that is convenient, since a request and its response happen inside one object you can pass around. For an SCP that serves many modalities at once, it means you are running threads, and the question of how many associations you accept concurrently becomes an operational decision rather than a library detail. The packaged echoscp is the tool for measuring exactly that, since it does nothing but associate and echo.

There is one more consequence of a pure Python implementation that is worth stating plainly. The protocol work, the binary parsing and the state machines all run in the interpreter rather than in a compiled extension, so the performance characteristics are those of Python. For the PACS and modality traffic that dominates real deployments that is entirely adequate, and it is a substantial win in deployment terms, since there is no platform specific binary to build, package or keep in step across a fleet of machines.

Eight command line applications, and one dependency

The packaging is where the practical shape of the project becomes clear.

The project has exactly one runtime dependency, pydicom at 3 or newer and below 4. That is a remarkable constraint for a protocol implementation, because DICOM networking involves binary parsing, association negotiation, and state machines, and the usual way to do that in Python is to bind a C library. pynetdicom does all of it in Python and reuses pydicom for the data set encoding.

The build backend is flit_core, the licence is MIT, and the version in the manifest is 3.1.0.dev0, which is a development version ahead of the released 3.0.4. The classifiers list Python 3.10 through 3.14, and the development status is given as 5, Production/Stable.

Then there are the eight console scripts, and they are the clearest statement of the intended use. echoscp and echoscu are an SCP and an SCU that do nothing but echo, which is the classic way to check that two hosts can associate at all. findscu queries a remote data set, getscu retrieves one, movescu asks for a move, qrscp is a query response provider, storescp is a store receiver and storescu is a store sender.

The optional extra, apps, pulls in sqlalchemy, which is what the applications need for their local database. The applications themselves live under pynetdicom.apps.

The install instructions are in the readme and are the usual two:

bash
    pip install -U pynetdicom
bash
    conda install -c conda-forge pynetdicom

The installation guide covers the development version as well.

The remaining badge row in the readme is a useful signal about the project's health: coverage from codecov, unit tests and type hints as separate GitHub workflows, documentation built on CircleCI, black for code style, and a Zenodo DOI, which is the convention in the scientific Python world for citing a specific software version permanently.

Documentation, and the shape of the release history

The documentation is comprehensive and split into the pieces a user actually needs.

There are tutorials, a user guide, code examples, application documentation and an API reference, all published for the current release, and the same set published separately for the development version. Splitting them is the right call for a library that ships both a stable release and a moving main branch, because the API reference for the development version often documents behaviour that has not shipped yet.

The recent releases are v3.0.4 from 2025-08-02, v3.0.3 from 2025-07-04 and v3.0.2 from 2025-06-18. Three releases inside about seven weeks in the summer of 2025 is the pattern of a project closing out a major version, and then the development version in the manifest moving to 3.1.0.

The last push was on 2026-10-11, so the code is being worked on even though the last tag is from the previous year. That is a normal pattern for a library whose releases are cut less often than its commits, and the split between the stable documentation and the development documentation is what lets a user track both.

The repository layout is conventional. There is a pynetdicom package directory, a docs directory, a build_tools directory, a pyproject.toml, a pre-commit configuration and a CircleCI configuration, plus an asv.conf.json, which is the configuration for airspeed velocity, the tool used to track performance changes over time. A networking library that measures its own performance across releases is a good sign, because the association negotiation path is exactly the kind of code where a regression would otherwise be silent.

Editorial conclusion

Adopt pynetdicom if you need to talk to a DICOM modality or a PACS from Python and you would rather not bind a native library into your process, since the whole protocol is implemented in pure Python. Do not adopt it as a DICOM server for clinical use without reading the security posture of your deployment, because the protocol it implements negotiates its own association over TCP with no encryption built in. Before you commit, check the version of pydicom you have against the required range, because pynetdicom pins it, and decide whether the packaged command line applications are enough for your testing or you need the library API.

Frequently asked questions

What is pynetdicom?

It is a pure Python package that implements the DICOM networking protocol, originally based on the legacy pynetdicom project. Working with pydicom, it allows the easy creation of DICOM Service Class Users and Service Class Providers, and it is MIT licensed.

What are the dependencies of pynetdicom?

One runtime dependency, pydicom at 3 or newer and below 4, and an optional extra named apps that pulls in sqlalchemy for the bundled command line applications. The build backend is flit_core and the supported Python versions are 3.10 through 3.14.

Which DICOM services does pynetdicom support?

As a Service Class User it supports the DIMSE-C services C-ECHO, C-FIND, C-GET, C-MOVE and C-STORE, and the DIMSE-N services N-ACTION, N-CREATE, N-DELETE, N-EVENT-REPORT, N-GET and N-SET. Each is available as a method on the Association object once the association is established.

What command line applications does pynetdicom ship?

Eight: echoscp and echoscu for testing that two hosts can associate, findscu, getscu and movescu as SCU tools, and qrscp, storescp and storescu as SCP tools. They are installed as console scripts named after each application.

How do I install pynetdicom?

The current release installs with pip install -U pynetdicom or with conda install -c conda-forge pynetdicom. The installation guide in the documentation also covers how to install the current development version, and the documentation is published separately for the release and for the development branch.

Official sources

  1. License: MIT
  2. Project website
  3. pydicom/pynetdicom on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pydicom-pynetdicom.svg)](https://hysenlabs.com/projects/pydicom-pynetdicom)

Was this useful?