Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DICOM Directory Separator and Metadata Extractor

Python tool that:

  • Separates and organizes mixed DICOM studies (from a CD or DVD) by Exam (Series), putting the Patient name and the Protocol in the name of the output folder.
  • Copies and sequentially renames the images (0001.ext, 0002.ext, ...) preserving the extension, which is what ImageJ expects.
  • Exports metadata CSV files (global, per patient, and per patient and protocol) and generates basic QA reports.

Important: the file type is never changed. Only the base name of the organized copies is rewritten, and the originals are never modified.

Requirements

  • Python 3.9+
  • Packages: pydicom, pandas, pyyaml (installed by pip install -e .)

Installation (recommended)

Run these steps once to install the organize-dicom command inside a virtual environment. Run them from the root folder of the repository, the one holding pyproject.toml.

Linux (bash):

git clone https://github.com/jofavalle/dicom-directory-separator-and-metadata-extractor.git
cd dicom-directory-separator-and-metadata-extractor
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip setuptools wheel
python -m pip install -e .

# Check the installation
organize-dicom --help

Windows (PowerShell):

git clone https://github.com/jofavalle/dicom-directory-separator-and-metadata-extractor.git
cd dicom-directory-separator-and-metadata-extractor
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -U pip setuptools wheel
python -m pip install -e .

# Check the installation
organize-dicom --help

Notes:

  • python -m pip install -e . has to run in the repository folder, not inside the dicom/ data folder.
  • Once it is installed you can call organize-dicom from any folder; just point --input and --output at the paths you want.

Usage on Linux

From the directory holding dicomdir and/or the dicom/ folder:

# A) Run the script directly
python3 script/organize-dicom.py \
  --input . \
  --output organized_by_test \
  --config config.yaml

# B) Install the command and use it
pip install -e .
organize-dicom --input . --output organized_by_test --config config.yaml

Usage on Windows

In PowerShell, from the directory holding dicomdir and/or the dicom/ folder:

# A) Install the command (once per environment)
python -m pip install -e .

# B) Run the command
organize-dicom --input . --output organized_by_test --config config.yaml

# C) Or run the script directly
python script/organize-dicom.py --input . --output organized_by_test --config config.yaml

Notes for Windows:

  • Keep --link-mode copy (the default) unless you need hardlink (same NTFS volume) or symlink (which needs Developer Mode or an elevated console).
  • If you hit long path errors, enable long paths in Windows or use shorter output paths.
  • The filesystem is case insensitive; use --on-collision rename if you suspect collisions.

Common commands

# Export metadata only, without copying images
organize-dicom --input . --output out --no-organize --export-metadata --qa

# Export every tag (wide CSV) without QA
organize-dicom --input . --output out_all --all-tags --no-organize --no-qa

Where should the data and the tool live?

  • The repository does not need to sit in the same folder as the images.
  • There are two ways to use it:
    • Installed as a command (pip install -e .): call organize-dicom from any folder and point --input at the directory holding the DICOM files.
    • Running the script directly (without installing): run python script/organize-dicom.py from inside the repository, but --input can be any path (there is no need to move the repository next to the data).
  • --input has to point at the root holding dicomdir, or at a folder with a dicom/ subfolder.
  • --output is created if it does not exist. An absolute path is used as given, but a relative path is resolved against --input, not against the directory you are standing in. So --input /media/CD --output organized writes to /media/CD/organized, that is, inside the data. Pass an absolute path whenever the results have to land somewhere else.

Examples:

# Running from your home directory against a CD mounted at /media/CD
organize-dicom --input /media/CD --output ~/out --no-organize --export-metadata --qa

# From inside the repository, processing a folder outside it. The output path
# is absolute on purpose: ./organized would land in /path/to/studies/organized.
python3 script/organize-dicom.py --input /path/to/studies --output ~/organized

Examples on Windows (PowerShell):

# CD or DVD mounted as E:\
organize-dicom --input E:\ --output "$env:USERPROFILE\Documents\out" --no-organize --export-metadata --qa

# Folder with spaces (use quotes)
organize-dicom --input "E:\DICOM studies" --output "C:\Temp\organized"

# Run the script directly
python script/organize-dicom.py --input "D:\data\dicom" --output "C:\out\organized"

Tips:

  • On Windows, use double quotes "..." when the path contains spaces.
  • Both --input and --output accept absolute and relative paths, keeping in mind that a relative --output hangs off --input.

Main options

  • --input: root holding dicomdir, or a folder with a dicom/ subfolder (default: .)
  • --dicom-folder: subfolder to walk when there is no DICOMDIR (default: dicom)
  • --dry-run: write nothing, only print the summary of what the run would do
  • --organize/--no-organize: copy and rename per exam (default: yes)
  • --export-metadata/--no-export-metadata: global and per patient CSV files (default: yes)
  • --qa/--no-qa: QA reports (default: yes)
  • --config: YAML used to normalize protocol names (see the example in config.example.yaml)
  • --all-tags: export every tag at instance level (much wider CSV)
  • --workers: threads used to read metadata (default: 8)

Organization and copying

  • One output folder per Exam (Series): Exam_<PatientName>__<ProtocolNorm>__Series_<SeriesNumber>_<SeriesInstanceUID>/
  • Inside it: the sequential copies 0001.ext, 0002.ext, ... keeping the original extension, plus a manifest.csv holding the mapping.
  • Advanced options:
    • --link-mode {copy,hardlink,symlink} (default: copy)
    • --on-collision {skip,overwrite,rename} (default: skip)
    • --pad-width N (fixed padding; automatic when 0)
    • --copy-workers N (copy concurrency)

Breaking change: these folders used to be named Prueba_<...>. Any Prueba_* glob in a script or an ImageJ macro stops matching, and re-running over an --output that still holds an old tree builds a parallel tree beside it instead of taking the --on-collision skip branch, because the names no longer line up. Rename the old folders if you need both runs to meet:

for d in Prueba_*; do mv "$d" "Exam_${d#Prueba_}"; done

Filters

  • --modality CT (repeatable)
  • --date-range YYYYMMDD:YYYYMMDD
  • --patient-id <ID> (repeatable)
  • --protocol-include "regex" / --protocol-exclude "regex"

Outputs

  • global_index_instances.csv: one row per file with the main metadata (or every tag with --all-tags).
  • global_index_series.csv: summary per series (counts, InstanceNumber ranges).
  • csv/patients/...: CSV files per patient (instances and series).
  • csv/patient_protocols/...: CSV files per patient and protocol.
  • csv/patient_protocols/summary_ct_params_by_patient_protocol.csv: one row per patient and protocol with the CT acquisition parameters, in the columns PatientID, ProtocolNorm, n_series, n_instances, kernel, kvp, exposure_time_ms, xray_tube_current_mA, exposure_mAs and pitch. Every distinct value found in the group is listed, separated by |, so a cell holding more than one value means the parameter changed within that protocol.
  • qa/*.csv: hierarchy, InstanceNumber gaps, duplicate SOPs and critical tags (whichever apply). A check that found nothing writes no file.

Beyond the identifying tags, global_index_instances.csv carries the CT exposure and reconstruction parameters when the files provide them: ConvolutionKernel, KVP, ExposureTime, ExposureTimeInms, XRayTubeCurrent, Exposure, SpiralPitchFactor and PitchFactor. Those are the tags the CT summary aggregates: the exposure time is taken from ExposureTimeInms and falls back to ExposureTime, and the pitch from SpiralPitchFactor and falls back to PitchFactor.

Protocol configuration

Example in config.example.yaml:

protocol_map:
  "Senos Paranasales": "CT_Senos_Paranasales"
  "Cerebro mas de 10 anios": "CT_Cerebro_>10a"

protocol_regex:
  - pattern: "^cerebro.*anios$"
    replace: "CT_Cerebro"

Save it as config.yaml in the root, or pass it with --config path.

The keys are the protocol names as the scanner writes them, so they are matched byte for byte and are left in the original language on purpose.

Development

There is a smoke test, and it needs no real study: it builds a synthetic DICOM tree in a temporary directory, runs the tool over it and checks the result, so it can run on a fresh clone.

python3 -m venv .venv && .venv/bin/pip install -e .
./tests/smoke.sh

License

MIT. See LICENSE.

Contact and credits

About

Python tool that separates mixed DICOM studies by exam (series), renames the images for ImageJ and exports metadata and QA reports to CSV.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages