Rust workspace for reading, writing, transcoding, and converting DICOM data.
This repository contains:
dcmnorm: a library crate with DICOM file, memory, and JSON conversion helpersexec/dcmnorm: a CLI for converting between DICOM, transcoded DICOM, JSON, and rendered images/raw frames
.
├── Cargo.toml
├── src/
│ └── dicom_io.rs
├── exec/
│ └── dcmnorm/
└── test/
└── files/
Default builds enable the MPEG and JPEG-LS codec features.
Native prerequisites for the default build on Debian or Ubuntu are:
build-essentialclangcmakelibc6-devlibclang-devpkg-configlibavutil-devlibavcodec-devlibavformat-devlibswscale-devlibswresample-dev
The FFmpeg integration is built with a reduced ffmpeg-next feature set, so
libavfilter-dev and libavdevice-dev are not required for the current build.
Example install command:
sudo apt-get update
sudo apt-get install -y \
build-essential \
clang \
cmake \
libc6-dev \
libclang-dev \
pkg-config \
libavutil-dev \
libavcodec-dev \
libavformat-dev \
libswscale-dev \
libswresample-devBuild the entire workspace from the repository root:
cargo build --workspaceBuild the entire workspace in release mode:
cargo build --workspace --releaseBuild the workspace with Kakadu FFI enabled:
cargo build --workspace --features kakadu-ffiBuild with Kakadu FFI using explicit include/lib locations:
KAKADU_INCLUDE_DIR=$HOME/.local/include/kakadu \
KAKADU_LIB_DIR=$HOME/.local/lib \
cargo build --workspace --features kakadu-ffiBuild without the default MPEG and JPEG-LS codec features:
cargo build --workspace --no-default-featuresRelease binaries are written to target/release/.
Install the CLI tools directly from the workspace using Cargo:
cargo install --path exec/dcmnormTo install every CLI under exec/ with one command, use the helper script:
./scripts/install-source.shThe install script automatically detects Kakadu headers and libraries and enables
kakadu-ffi when available.
The install script also verifies the default codec toolchain before invoking Cargo.
For the default build this means pkg-config, clang, standard C headers, and the
FFmpeg development packages listed above must already be installed.
This installs the binaries into Cargo's bin directory, usually ~/.cargo/bin.
If ~/.cargo/bin is not already on your PATH, add this to your shell profile:
export PATH="$HOME/.cargo/bin:$PATH"If you prefer not to use cargo install, you can still build and copy the release binaries manually.
Build the release binaries first:
cargo build --workspace --releaseThe executables will be available at:
target/release/dcmnorm
To install them for the current user, copy them into a directory on your PATH, for example ~/.local/bin:
mkdir -p ~/.local/bin
cp target/release/dcmnorm ~/.local/bin/If ~/.local/bin is not already on your PATH, add this to your shell profile:
export PATH="$HOME/.local/bin:$PATH"To install the latest published release binary from GitHub (or a specific version), use:
./scripts/install-release.shThis repository includes two GitHub Actions workflows for SemVer-based CLI releases:
.github/workflows/semver-tag.yml: manually creates and pushes the nextvX.Y.Ztag from the latest existingv*tag.github/workflows/release.yml: runs on pushed version tags, builds the CLI, and creates a GitHub Release with artifacts
Release flow:
- Run the SemVer Tag workflow from the Actions tab and choose
patch,minor, ormajor. - The workflow pushes a new version tag (for example
v0.1.1). - The Build and Release CLI workflow is triggered by that tag and publishes:
dcmnorm-<tag>-linux-x86_64.tar.gzdcmnorm-<tag>-linux-x86_64.tar.gz.sha256
Prereleases are supported in the SemVer tag workflow via the prerelease input.
If you prefer not to manually run the tag workflow in GitHub, use the local helper script:
./scripts/release-tag.sh patchSupported bump types are patch, minor, and major.
You can create a prerelease tag locally:
./scripts/release-tag.sh minor --prerelease rcUse --dry-run to preview the computed next tag without creating or pushing it.
The script updates versions in:
Cargo.tomlexec/dcmnorm/Cargo.toml
Then it creates a release commit, pushes that commit to origin, and pushes the version tag.
The pushed tag triggers .github/workflows/release.yml automatically.
If no v* tags exist yet, the script uses the root Cargo.toml package.version as the baseline for computing the next version.
Build only dcmnorm:
cargo build -p dcmnorm-cliBuild the CLI in release mode:
cargo build -p dcmnorm-cli --releaseThis repository includes a multi-stage Dockerfile that builds dcmnorm in a
toolchain stage and copies only the release binary into a slim runtime stage.
Build the image:
docker build -t dcmnorm .Run the CLI:
docker run --rm dcmnormConvert a file from a bind-mounted working directory:
docker run --rm \
-v "$PWD":/work \
-w /work \
dcmnorm \
test/files/dx.dcmThe final runtime image installs these native packages:
ca-certificatesffmpeglibstdc++6
Build-only dependencies such as clang, cmake, pkg-config, and FFmpeg -dev
packages are kept in the builder stage and are not present in the final image.
Kakadu is not included in the Docker image. If you need JPEG 2000 Kakadu support,
provide the Kakadu headers and shared libraries yourself and build with kakadu-ffi.
Run all tests in the workspace:
cargo test --workspaceGet the full option reference from either help form:
dcmnorm -h
dcmnorm --helpdcmnorm command shape:
dcmnorm [OPTIONS] [INPUT] [OUTPUT]
Positional arguments:
[INPUT]: input DICOM or JSON file[OUTPUT]: output DICOM, JSON, or rendered file
General options:
-h,--help-V,--version--list-transfer-syntaxes--check-dicom--jpeg2000-codec <auto|openjpeg|kakadu>--verbose-I,--stdin-paths--filter <KEY>--overwrite--input-type <dicom|json>--output-type <dicom|json|raw|png|jpeg|mpeg4>
DICOM Editing:
--set <KEY=VALUE>--remove <KEY>--remove-private-tags
JSON Conversion:
--format <flat|standard>--keys <name|hex>--bulk-data <inline|uri>--bulk-data-source [<SOURCE>]
DICOM Transcoding:
--transfer-syntax <UID>
Rendering:
--render-frame <N>--render-all-frames--render-fps <FPS>--no-modality-lut--no-voi-lut--no-icc-profile--window-center <FLOAT>--window-width <FLOAT>--jpeg-quality <1-100>--output-width <PIXELS>--output-height <PIXELS>--scale-max-size <PIXELS>--redact-box <X,Y,W,H>--redact-color <R,G,B|#RRGGBB>--pad--pad-color <R,G,B|#RRGGBB>
Runtime environment variables:
DCMNORM_PERF- Enables scoped performance timing logs to stderr.
- Truthy values:
1,true,yes,on.
DCMNORM_JPEG2000_CODEC- JPEG 2000 decoder preference:
auto,openjpeg, orkakadu. - The CLI always sets this from
--jpeg2000-codec(defaultauto).
- JPEG 2000 decoder preference:
DCMNORM_JPEG2000_DEBUG- Enables JPEG 2000 debug logging when truthy.
--verbosesets this to1.
LD_LIBRARY_PATH- Used to discover Kakadu shared libraries (
libkdu*.so) at runtime.
- Used to discover Kakadu shared libraries (
Build-time environment variables (primarily for --features kakadu-ffi):
KAKADU_INCLUDE_DIR- Explicit include directory containing Kakadu headers.
KAKADU_LIB_DIR- Explicit library directory containing
libkdu*.so.
- Explicit library directory containing
KAKADU_LIB_NAME- Optional Kakadu library base name override for linker configuration.
Convert a DICOM file to flattened JSON using named keys:
cargo run -p dcmnorm-cli -- test/files/dx.dcmConvert a DICOM file to standard JSON with hex keys and write to a file:
cargo run -p dcmnorm-cli -- test/files/dx.dcm out.json --format standard --keys hexFilter DICOM attributes before conversion (only filtered tags are parsed and emitted):
cargo run -p dcmnorm-cli -- test/files/dx.dcm --filter StudyInstanceUIDUse multiple filters (repeat --filter or comma-separate values):
cargo run -p dcmnorm-cli -- test/files/dx.dcm out.json --filter StudyInstanceUID,PatientID--filter applies only to DICOM input. The parser reads until the requested
attributes are available, drops non-filtered attributes, and then continues with
the normal conversion pipeline (for example, DICOM to JSON output).
By default, dcmnorm emits bulk data as relative BulkDataURI values (?offset=...&length=...) when converting DICOM to JSON, and values of 32 bytes or less are automatically emitted as InlineBinary.
To embed absolute file:// URIs in BulkDataURI, pass --bulk-data-source without a value:
cargo run -p dcmnorm-cli -- test/files/dx.dcm --bulk-data uri --bulk-data-sourceConvert JSON back to a DICOM file:
cargo run -p dcmnorm-cli -- out.json out.dcmConvert JSON with BulkDataURI references back to DICOM using a source file:
cargo run -p dcmnorm-cli -- out.json out.dcm --bulk-data-source test/files/dx.dcmdcmnorm infers the conversion direction from the input and output file types:
- DICOM input + JSON output, or no output, runs DICOM to JSON
- DICOM input + DICOM output with
--transfer-syntax <UID>runs DICOM to DICOM transcoding - DICOM input +
.png/.jpg/.jpeg/.rawoutput runs DICOM frame rendering - JSON input + DICOM output runs JSON to DICOM
- JSON to DICOM requires an output path
Use --check-dicom to validate DICOM files by checking for a Part 10 header first,
then falling back to dataset parsing up to SOPClassUID for streams without file meta.
Single file:
cargo run -p dcmnorm-cli -- --check-dicom test/files/dx.dcmRead paths from stdin (-I / --stdin-paths) and print only valid DICOM paths:
find . -type f | dcmnorm -I --check-dicom--check-dicom behavior:
- prints only successful (valid DICOM) paths to stdout
- suppresses per-file failure messages
- returns exit code
0when all inputs are valid - returns exit code
1if any input is invalid, unreadable, or not a regular file
Use --input-type to explicitly specify the input file type (useful for files without or with incorrect extensions):
cargo run -p dcmnorm-cli -- noextension --input-type dicom --output-type jsonUse --output-type to explicitly specify the output file type, including render formats:
cargo run -p dcmnorm-cli -- test/files/dx.dcm output --output-type jsonSupported --output-type values are: dicom, json, raw, png, jpeg, mpeg4
This allows you to process files that are missing extensions or have misleading names:
# Convert a DICOM file with no extension to JSON
cargo run -p dcmnorm-cli -- dicom_data --input-type dicom
# Write DICOM output without an extension
cargo run -p dcmnorm-cli -- input.json output --output-type dicom
# Render a DICOM file to an arbitrary extension as PNG
cargo run -p dcmnorm-cli -- test/files/dx.dcm frame.img --output-type png
# Render a DICOM file as MPEG4 without a recognized extension
cargo run -p dcmnorm-cli -- test/files/ct.dcm output.video --output-type mpeg4 --render-fps 24Set one or more DICOM element values while converting by repeating --set KEY=VALUE:
cargo run -p dcmnorm-cli -- test/files/dx.dcm out.dcm --transfer-syntax 1.2.840.10008.1.2.1 --set SOPClassUID=1.2.840.10008.5.1.4.1.1.2 --set StudyDescription=NormalizedKEY can be a DICOM keyword (for example, SOPClassUID) or a tag expression (for example, (0008,0016)).
Use --overwrite to write DICOM output back to the input path. This is useful for in-place edits with --set:
cargo run -p dcmnorm-cli -- test/files/dx.dcm --set SOPClassUID=1.2.840.10008.5.1.4.1.1.2 --overwriteRender the first frame of a DICOM file to PNG:
cargo run -p dcmnorm-cli -- test/files/dx.dcm out.pngRender frame 2 to JPEG with explicit quality:
cargo run -p dcmnorm-cli -- test/files/ct.dcm out.jpg --render-frame 1 --jpeg-quality 95Render to raw 8-bit frame bytes:
cargo run -p dcmnorm-cli -- test/files/dx.dcm out.rawRender all frames from a multiframe dataset to numbered PNG files (out_000001.png, out_000002.png, ...):
cargo run -p dcmnorm-cli -- test/files/ct.dcm out.png --render-all-framesRender all frames from a multiframe dataset to a single .mp4 video:
cargo run -p dcmnorm-cli -- test/files/ct.dcm out.mp4 --render-fps 24If --render-fps is omitted for .mp4 output, dcmnorm uses frame-rate metadata
from the DICOM instance when available (RecommendedDisplayFrameRate, CineRate,
FrameTime, or FrameTimeVector) and falls back to 24 FPS otherwise.
Use --verbose to print render/conversion diagnostics. Without --verbose, external
tool output such as ffmpeg is suppressed unless an error occurs.
For stage-by-stage performance timing, set DCMNORM_PERF=1 (or true/yes/on).
This prints scoped timings to stderr, for example:
DCMNORM_PERF=1 dcmnorm test/files/mr.dcm out.jpg --output-width 920 --output-height 758Or with an explicit render format for a file without a recognized extension:
DCMNORM_PERF=1 dcmnorm test/files/mr.dcm output.img --output-type jpeg --output-width 920 --output-height 758Rendering supports 1-bit, 8-bit, and 16-bit monochrome pixel data, as well as RGB data.
The render pipeline includes decompression when needed and applies modality LUT and VOI LUT/windowing by default.
Use --no-modality-lut and/or --no-voi-lut to disable those steps, and use
--window-center / --window-width to override VOI windowing.
Photometric interpretations supported by rendering include:
MONOCHROME1MONOCHROME2PALETTE COLORRGB
Both planar configurations are supported for RGB rendering (PlanarConfiguration 0 and 1).
.mp4 output requires ffmpeg installed and available on PATH.
Pipe input paths from stdin using -I / --stdin-paths, one path per line:
find . -name "*.dcm" | dcmnorm -IThis applies the same options as single-file mode to every path. Errors for individual files are printed to stderr with the filename, and dcmnorm exits non-zero if any file fails.
--set also applies in piped mode. The same element updates are applied to each input path:
find . -name "*.dcm" | dcmnorm -I --set SOPClassUID=1.2.840.10008.5.1.4.1.1.2To update each input file in place in piped mode, combine --set with --overwrite:
find . -name "*.dcm" | dcmnorm -I --set SOPClassUID=1.2.840.10008.5.1.4.1.1.2 --overwriteTo emit file:// BulkDataURI values in piped mode, also pass --bulk-data-source without a value:
find . -name "*.dcm" | dcmnorm -I --bulk-data uri --bulk-data-sourceTranscode a DICOM file to Explicit VR Big Endian:
cargo run -p dcmnorm-cli -- test/files/dx.dcm out.dcm --transfer-syntax 1.2.840.10008.1.2.2List the transfer syntaxes known to the current build and whether dataset read/write and pixel decode/encode are available:
cargo run -p dcmnorm-cli -- --list-transfer-syntaxesTransfer-syntax support is build-specific. The default build in this repository enables the MPEG and JPEG-LS codec features in addition to the DICOM library support that is available without extra native imaging libraries:
- native uncompressed syntaxes
- deflated dataset syntaxes
- encapsulated uncompressed pixel data
- MPEG transfer syntax support via FFmpeg-backed build integration
- JPEG baseline decode/encode
- JPEG extended and JPEG lossless decode-only
- JPEG-LS transfer syntax support via CharLS-backed build integration
- JPEG 2000 decode-only
- RLE lossless decode-only
Transfer syntaxes which the current build cannot encode or decode are reported explicitly by --list-transfer-syntaxes and by transcoding errors.
For JPEG 2000, dcmnorm checks LD_LIBRARY_PATH at runtime for Kakadu libraries (libkdu*.so).
Kakadu use is FFI-only (Rust -> C++ interop), not CLI-based.
To enable Kakadu interop, build with feature kakadu-ffi and make the required Kakadu headers
available in a normal include location such as ~/.local/include/kakadu, /usr/local/include/kakadu,
or /usr/include/kakadu so the C++ bridge can be compiled automatically.
If your headers are installed in a non-standard location, you can still point the build at them with
KAKADU_INCLUDE_DIR.
If Kakadu FFI is not enabled or Kakadu is unavailable, the OpenJPEG-based path remains in use.
For DICOM to JSON, dcmnorm defaults to:
- flattened JSON output
- named lookup keys where possible
- relative
BulkDataURIbulk data output (?offset=...&length=...) file://BulkDataURIoutput when--bulk-data-sourceis passed without a value- automatic
InlineBinaryfallback for bulk values of 32 bytes or less
For JSON to DICOM, dcmnorm defaults to:
- flattened JSON input
- optional
--bulk-data-sourcewhen resolvingBulkDataURI