Skip to main content

Synchronize medical imaging studies between storage modalities

Project description

dicomsync

CI PyPI PyPI - Python Version Code style: black Checked with mypy

Synchronize medical imaging studies between storage modalities

dicomsync logo

  • Copy and query medical imaging studies via CLI or python
  • Avoids copying duplicate studies by querying
  • Supports DICOM file folders, zipped studies and XNAT pre-archive

Installation

pip install dicomsync

Usage

An example of sending some dicom to some place.

TLDR;

$ dicomsync init
$ dicomsync place add dicom_root my_studies /tmp/dicom_root   # add dicom place
$ dicomsync find my_studies:patient1*                         # show studies
$ dicomsync place add zipped_root zipped /tmp/dicom_zipped    # add zipped place 
$ dicomsync send my_studies:* zipped                          # send matching studies

$ dicomsync place add xnat_pre_archive the_xnat_project https://xnat.health-ri.nl <xnat_project_name> <xnat_username>
$  export 'XNAT_PASS=<xnat_password>'                         # set xnat password 

Creating and querying a place

Say you have some folders containing dicom files in patient/study structure:

/tmp/dicom_root + patient1 + study1 + file1.dcm
                |          |        | file2.dcm
                |          |        | file3.dcm
                |          |
                |          + study2 + file1.dcm
                |          |        | file2.dcm
                |          |
                |          + study3 + file1.dcm
                |                   | file2.dcm
                |  
                + patient2 + study1 + file1.dcm
                                    | file3.dcm

Add this location to dicomsync as a place called my_studies

$ dicomsync init
$ dicomsync place add dicom_root my_studies /tmp/dicom_root

# you can now see this as a place
$ dicomsync place list
key         place
----------  --------------------------------
my_studies  Root folder at '/tmp/dicom_root'

You can show all patients and studies my_studies by using the find command:

$ dicomsync find my_studies:*
found 4:
my_studies:patient2/study1
my_studies:patient1/study3
my_studies:patient1/study2
my_studies:patient1/study1

The find query format is <place>:<patient>/<study>. You can use an astrisk * as a wildcard that matches any characters:

# All studies for patient1
$ dicomsync find my_studies:patient1*
found 3:
my_studies:patient1/study3
my_studies:patient1/study2
my_studies:patient1/study1

# only studies called study1 for any patient:
(base2) sjoerd@xps159500:/tmp$ dicomsync find my_studies:*study1
found 2:
my_studies:patient2/study1
my_studies:patient1/study1

# only a single study
$ dicomsync find my_studies:patient1/study2
found 1:
my_studies:patient1/study2

Sending studies

This follows on from the usage example above: we have a dicom_root place called my_studies.

Now we create a second place and send some studies to it. This will be a zipped_root. A folder that saves studies in the format /<patient>/<study>.zip

dicomsync place add zipped_root zipped /tmp/dicom_zipped

# there are now two places
dicomsync place list
key         place
----------  -----------------------------------------------
my_studies  Root folder at '/tmp/dicom_root'
zipped      Zipped DICOM Root folder at '/tmp/dicom_zipped'

Now we send a single study my_studies:patient1/study2 to zipped using the send command:

$ dicomsync send my_studies:patient1/study2 zipped
dicomsync.cli.send INFO: Sending all studies matching StudyQuery 'my_studies:patient1/study2' to 'zipped'
dicomsync.cli.send INFO: found 1 studies matching StudyQuery 'my_studies:patient1/study2'.
dicomsync.cli.send INFO: Found '0' duplicate studies.Sending the rest: patient1/study2 (DICOMStudyFolder)
dicomsync.cli.send INFO: processing patient1/study2 (DICOMStudyFolder)
dicomsync.local INFO: Creating zip archive for /tmp/dicom_root/patient1/study2 in /tmp/dicom_zipped/patient1/study2.zip

The study has been sent. You can use '*:*' to find all studies in all places:

$ dicomsync find '*:*'
found 5:
my_studies:patient2/study1
my_studies:patient1/study3
my_studies:patient1/study2
my_studies:patient1/study1
zipped:patient1/study2

If you try to send the same study again, it will not be sent as it already exists:

$ dicomsync send my_studies:patient1/study2 zipped
2025-10-06 13:22:22 dicomsync.cli.send INFO: Sending all studies matching StudyQuery 'my_studies:patient1/study2' to 'zipped'
2025-10-06 13:22:22 dicomsync.cli.send INFO: found 1 studies matching StudyQuery 'my_studies:patient1/study2'.
2025-10-06 13:22:22 dicomsync.cli.send INFO: Found 1 duplicate studies.
2025-10-06 13:22:22 dicomsync.cli.send INFO: All studies were duplicates. Not sending anything.

You can send all studies. Duplicates will not be sent:

$ dicomsync send my_studies:* zipped
2025-10-06 13:23:42 dicomsync.cli.send INFO: Sending all studies matching StudyQuery 'my_studies:*/*' to 'zipped'
2025-10-06 13:23:42 dicomsync.cli.send INFO: found 4 studies matching StudyQuery 'my_studies:*/*'.
2025-10-06 13:23:42 dicomsync.cli.send INFO: Found '1' duplicate studies.Sending the rest: patient2/study1 (DICOMStudyFolder)
patient1/study3 (DICOMStudyFolder)
patient1/study1 (DICOMStudyFolder)
2025-10-06 13:23:42 dicomsync.cli.send INFO: processing patient2/study1 (DICOMStudyFolder)
2025-10-06 13:23:42 dicomsync.local INFO: Creating zip archive for /tmp/dicom_root/patient2/study1 in /tmp/dicom_zipped/patient2/study1.zip
2025-10-06 13:23:42 dicomsync.cli.send INFO: processing patient1/study3 (DICOMStudyFolder)
2025-10-06 13:23:42 dicomsync.local INFO: Creating zip archive for /tmp/dicom_root/patient1/study3 in /tmp/dicom_zipped/patient1/study3.zip
2025-10-06 13:23:42 dicomsync.cli.send INFO: processing patient1/study1 (DICOMStudyFolder)
2025-10-06 13:23:42 dicomsync.local INFO: Creating zip archive for /tmp/dicom_root/patient1/study1 in /tmp/dicom_zipped/patient1/study1.zip

XNAT place

To add an XNAT pre-archive as a place:

$ dicomsync place add xnat_pre_archive the_xnat_project https://xnat.health-ri.nl <xnat_project_name> <xnat_username>
# set your xnat password in environment. Note the extra space in front of the command to avoid logging the command 
$  export 'XNAT_PASS=<xnat_password>' 

Using dicomsync in scripts

See the /examples folder for examples

Development

To set up for development of dicomsync:

  • git clone from github
  • Install dependencies:
poetry install
  • Add pre-commit hooks:
pre-commit install 
  • To check code before committing:
pre-commit run

Design notes

Choices and intentions for this library. Guideline for development.

The goal of dicomsync is to make it easier to transfer medical imaging studies between different places. Some practical examples:

  • Avoiding duplicate uploads. Make it easy to do a send operation multiple times (after errors) without worry. Operations should be idempotent by default and minimize work by default.
  • Logging. Starting and leaving a multi-day upload, coming back and knowing what happened.

The main objects are ImagingStudy and Place. dicomsync works with imaging studies that can exists in different places. Initial places can be XNAT, Local folder, local zipfile. Each place tends to have its own ImagingStudy subclass

First version simplifications

Rabbit holes avoided in the first version

  • No universal intermediate data structures. It would be great to have a universal dicomsync internal data structure. For ImagingStudy subtype you define a to_dicomsync() and from_dicomsync() methods. However, this is way too involved for now. We will stick with to_specific_place() methods for each individual place.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dicomsync-1.2.0.tar.gz (93.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dicomsync-1.2.0-py3-none-any.whl (30.8 kB view details)

Uploaded Python 3

File details

Details for the file dicomsync-1.2.0.tar.gz.

File metadata

  • Download URL: dicomsync-1.2.0.tar.gz
  • Upload date:
  • Size: 93.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.23

File hashes

Hashes for dicomsync-1.2.0.tar.gz
Algorithm Hash digest
SHA256 62e23b7b40fbf5342473bfe15df249ae2c770ba22f08333e9fd8eeac63ad57fc
MD5 68b31a3b9753fd36a13d802c1fc90340
BLAKE2b-256 1e685a859283084e9bbd15bfb2ec4f0ac370dc609292699c1224fc759e25671f

See more details on using hashes here.

File details

Details for the file dicomsync-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: dicomsync-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 30.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.23

File hashes

Hashes for dicomsync-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bcea56219dd13e214f50acf9dadd877a664d842893b94b22b2e6b345b9a63a42
MD5 d851b1a67c07e23e83858ebe8efca0b8
BLAKE2b-256 fe08950e62bc14d5ffbb9e60e890da3fd0dd6b99eed959d8baf562ebc2a57435

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page