Skip to main content

DICOMForge

DOI CI Python Status License Core DICOMweb i18n

dicom · medical-imaging · radiology · pacs · dicomweb · de-identification · pixel-data · healthcare-ai · python

DICOMForge is a Python DICOM processing library for medical imaging applications. The goal is a lightweight core with typed, predictable APIs, explicit safety boundaries, and optional integrations for heavier work such as pixel codecs, wire-compatible networking, and DICOMweb.

This repository is intentionally starting with a small, solid core:

  • typed tags and value access
  • transfer syntax classification
  • pluggable codec registry
  • de-identification profiles, deterministic UID remapping, date shifting, and audit reports
  • DICOM UID grammar validation and UUID-derived UID generation
  • structural and SOP Class (Type 1 / Type 2 attribute) dataset validation
  • pixel metadata and safety checks
  • VOI window, rescale, and photometric interpretation helpers
  • optional pydicom-backed compressed pixel decode and frame iteration
  • optional Pillow-backed 8-bit JPEG preview generation
  • Specific Character Set helpers and structured person-name parsing
  • async networking primitives for association lifecycle and DIMSE-style commands
  • DICOMweb query, retrieval, upload, and multipart helpers
  • dependency-free native DICOM Part 10 file IO (read/write), with an optional pydicom backend for compressed pixel data
  • standard-library tests

Why another DICOM library?

The adoption target is not just feature count. The library should feel safe for production teams and approachable for new medical-imaging developers.

Design priorities:

  • Small import surface: core imports should not pull in NumPy, Pillow, codec wheels, or networking stacks.
  • Typed access: avoid stringly-typed dataset code where common attributes can be accessed predictably.
  • Explicit codec model: make unsupported transfer syntaxes visible before a transcoding job fails halfway through.
  • Lazy IO: support large studies and multi-frame objects without forcing full pixel loading.
  • Character-set correctness: treat text encoding as a first-class concern.
  • Concurrency-safe services: design networking and DICOMweb APIs around explicit lifecycle and backpressure.
  • Good errors: explain the DICOM concept, the offending tag, and the next action where possible.

See docs/architecture.md and docs/roadmap.md. Current implementation boundaries are tracked in docs/conformance.md. For repository naming and discoverability notes, see docs/branding.md.

Commercial Readiness

DICOMForge is MIT licensed and designed for commercial use as a developer library. It is not a medical device, diagnostic application, complete PS3.15 de-identification engine, or wire-compatible DIMSE implementation. See docs/safety.md, docs/conformance.md, and docs/compatibility.md before using it in regulated clinical workflows.

When To Use It

Use DICOMForge when you need:

  • typed, dependency-light DICOM metadata handling
  • pixel metadata validation before decoding or processing
  • pydicom-backed pixel extraction for compressed syntaxes when optional codec plugins are installed
  • display-ready PIL images and JPEG preview bytes for lightweight viewer/API workflows
  • explicit character-set validation before writing non-ASCII DICOM text
  • de-identification planning with deterministic UID remapping, date shifting, and audit reports
  • mandatory-attribute validation against common image storage SOP Classes
  • native, dependency-free DICOM file IO — or a pydicom-backed backend — behind a smaller application API
  • DICOMweb URL/query, DICOM JSON, STOW multipart, and response parsing helpers
  • async lifecycle and backpressure primitives for DICOM-like service design

Do not use DICOMForge as the only component for:

  • diagnostic interpretation or medical-device behavior
  • legal de-identification approval without site policy and human review
  • direct DIMSE/PACS interoperability over the DICOM Upper Layer
  • full-fidelity DICOM editing that requires every VR, character set, and IOD rule
  • replacing pydicom, pynetdicom, or integration-tested PACS/VNA validation

Architecture At A Glance

dicomforge.tags            typed tag parsing and common keyword constants
dicomforge.dataset         lightweight mutable dataset wrapper
dicomforge.transfer_syntax transfer syntax classification
dicomforge.codecs          codec capability registry
dicomforge.charset         Specific Character Set and person-name helpers
dicomforge.pixels          pixel metadata safety checks and small value helpers
dicomforge.anonymize       starter de-identification plans and audit reports
dicomforge.dictionary      tag → VR data dictionary and VR category metadata
dicomforge.part10          native, dependency-free DICOM Part 10 read/write
dicomforge.adapt           pydicom, numpy, Pillow, JSON, and pynetdicom adapters
dicomforge.api             lazy file wrapper and one-call application helpers
dicomforge.io              native + optional pydicom read/write backends
dicomforge.network         async command lifecycle primitives, not DICOM UL PDUs
dicomforge.dicomweb        QIDO/WADO/STOW helpers with injectable HTTP transport
dicomforge.transport       optional requests/retry/auth DICOMweb transports

API Stability

DICOMForge is pre-1.0. Public APIs are intended to be small and stable, but breaking changes may happen while the library moves toward a 1.0 adoption bar. Breaking changes should be documented in release notes and paired with migration guidance.

Quick Start

from dicomforge import DicomDataset, Tag, TransferSyntax

ds = DicomDataset()
ds.set(Tag.PatientName, "Anonymous")
ds.set(Tag.Modality, "CT")

syntax = TransferSyntax.from_uid("1.2.840.10008.1.2.1")
assert syntax.is_little_endian
assert syntax.is_explicit_vr

Read and write DICOM files — no dependencies required:

from dicomforge import dcmread, dcmwrite, DicomDataset, SopClassUID, Tag

# Read any uncompressed DICOM file with the built-in native codec.
dataset = dcmread("image.dcm", stop_before_pixels=True)
print(dataset.get("PatientName"))

# Write one back out as a conformant DICOM Part 10 file.
ds = DicomDataset(
    {
        Tag.SOPClassUID: SopClassUID.SecondaryCaptureImageStorage,
        Tag.SOPInstanceUID: "1.2.3.4.5",
        Tag.PatientName: "Doe^Jane",
        Tag.Modality: "OT",
    }
)
dcmwrite("out.dcm", ds)

dcmread / dcmwrite (aliases of dicomforge.read / write) select a backend automatically: the dependency-free native codec when pydicom is not installed, and pydicom when it is. Force a backend explicitly with backend="native" or backend="pydicom". The native codec handles Implicit/Explicit VR Little Endian, Explicit VR Big Endian, and Deflated Explicit VR Little Endian; for compressed (encapsulated) pixel data, install dicomforge[pydicom] and use backend="pydicom".

from dicomforge import part10

# Serialise directly to/from bytes, choosing the transfer syntax.
blob = part10.dumps(ds, transfer_syntax="1.2.840.10008.1.2")  # Implicit VR LE
restored = part10.loads(blob)

Basic de-identification:

from dicomforge import AnonymizationPlan, DicomDataset, PrivateTagAction

dataset = DicomDataset(
    {
        "PatientName": "Ada Lovelace",
        "PatientID": "MRN-123",
        "StudyInstanceUID": "1.2.826.0.1.3680043.8.498.1",
        (0x0011, 0x1001): "private vendor value",
    }
)

plan = AnonymizationPlan.starter_profile(
    uid_salt="project-specific-secret",
    private_tag_action=PrivateTagAction.REMOVE,
)
report = plan.apply_with_report(dataset)

assert dataset.get("PatientName") == "Anonymous"
assert dataset.get("PatientIdentityRemoved") == "YES"
assert report.private_tags_removed == 1

Date-shift de-identification (preserves intervals between studies while hiding real dates):

from dicomforge import AnonymizationAction, AnonymizationPlan, DicomDataset, Rule, Tag

dataset = DicomDataset({Tag.StudyDate: "20260101", Tag.SeriesDate: "20260102"})

plan = AnonymizationPlan(
    [
        Rule(Tag.StudyDate, AnonymizationAction.SHIFT_DATE, -30),
        Rule(Tag.SeriesDate, AnonymizationAction.SHIFT_DATE, -30),
    ]
)
plan.apply(dataset)

assert dataset.get(Tag.StudyDate) == "20251202"
assert dataset.get(Tag.SeriesDate) == "20251203"

SOP Class mandatory-attribute validation:

from dicomforge import DicomDataset, SopClassUID, Tag, validate_for_sop_class

ct = DicomDataset({Tag.SOPClassUID: SopClassUID.CTImageStorage})
for issue in validate_for_sop_class(ct):
    print(issue)
# [error] CT Image Storage requires SOPInstanceUID (0008,0018) (Type 1 — must be present and non-empty).
# ...

UID generation and validation:

from dicomforge import generate_uid, is_valid_uid

uid = generate_uid()  # UUID-derived under the standard "2.25" root (PS3.5 §B.2)
assert is_valid_uid(uid)
assert not is_valid_uid("1.2.03.4")  # leading zero in a component

Async networking:

from dicomforge.network import DimseServer, open_association

async with DimseServer(ae_title="LOCAL-SCP") as server:
    async with await open_association(
        "127.0.0.1",
        server.bound_port,
        called_ae_title="LOCAL-SCP",
    ) as association:
        status = await association.c_echo()
        assert status.is_success

DICOMweb query building:

from dicomforge.dicomweb import DicomwebClient, QidoQuery, UrllibDicomwebTransport

client = DicomwebClient(
    "https://pacs.example/dicomweb",
    UrllibDicomwebTransport(timeout=10),
)
studies = client.search_studies(QidoQuery().patient_id("MRN-123").modality("CT"))

JPEG preview generation:

from dicomforge.adapt import to_jpeg_preview

preview = to_jpeg_preview(dataset, frame=0, quality=85)

Character-set aware person names:

from dicomforge import DicomDataset, PersonName, Tag

dataset = DicomDataset(
    {
        Tag.SpecificCharacterSet: "ISO_IR 192",
        Tag.PatientName: PersonName("Yamada", "Taro", ideographic="山田^太郎"),
    }
)

De-identification Scope

DICOMForge implements a practical, conservative subset of the DICOM PS3.15 Basic Application Level Confidentiality Profile for non-pixel attributes: common patient, accession, date/time, institution, operator, and UID fields are removed, emptied, replaced, or deterministically remapped. Private tag handling is explicit and defaults to removal. remove_private_tags on apply and apply_with_report remains available as a per-call override for older callers.

This is a software library, not a compliance certificate. Production use should pair it with site-specific policy, legal review, image pixel review, and a risk assessment for the data release context.

Development

Run the standard-library test suite:

PYTHONPATH=src python3 -m unittest

Optional checks used by maintainers when development dependencies are installed:

python -m ruff check src tests
python -m mypy src/dicomforge
python -m compileall -q src tests

Examples live in examples.

For a fuller commercial workflow, see examples/end_to_end_workflow.py.

Known Issues and Limitations

Fixed in 0.7.0

  • Tag.SliceThickness wrong group/element — The tag was registered as (0050,0018) (DeviceDiameter) instead of the correct (0018,0050). Any dataset lookup using Tag.SliceThickness would silently return None on real DICOM files. Fixed in v0.7.0.

  • io._KNOWN_VR incomplete — Approximately 30 tags including AccessionNumber, all date/time tags (StudyDate, SeriesDate, …), InstitutionName, ReferringPhysicianName, FrameOfReferenceUID, PatientIdentityRemoved, and DeidentificationMethod were missing from the internal VR map. Writing these tags via io.write() produced VR "UN" instead of the correct DICOM VR. Fixed in v0.7.0.

Current limitations

  • dicomforge.network is not wire-compatible DIMSE — The async networking module uses a lightweight JSON framing protocol, not the DICOM Upper Layer PDU wire format defined in PS3.8. It is not interoperable with real PACS systems over the wire. Wire-compatible DIMSE via a pynetdicom bridge is planned for a dicomforge-network package in a future release.

  • Compressed pixel access is delegated to pydicom — adapt.pixel_array can decode compressed transfer syntaxes when pydicom and the relevant pydicom pixel plugin are installed. DICOMForge does not bundle JPEG, JPEG-LS, JPEG 2000, or RLE codec implementations. adapt.to_jpeg_preview produces lossy 8-bit display previews, not diagnostic-quality derived images.

  • Character-set support is explicit, not magical — DICOMForge validates common declared character sets and person names, but full ISO 2022 edge cases and vendor-specific legacy files should still be verified with site fixtures.

  • Native IO covers uncompressed transfer syntaxes — The native Part 10 codec reads and writes Implicit/Explicit VR Little Endian, Explicit VR Big Endian, and Deflated Explicit VR Little Endian. It can read encapsulated (compressed) pixel data as opaque fragments, but writing compressed pixel data requires backend="pydicom", which re-encodes fragments faithfully. Tags outside the built-in dictionary resolve to VR UN under Implicit VR and are preserved as raw bytes rather than corrupted.

License

DICOMForge is distributed under the MIT License for personal and commercial use. See LICENSE.

Metadata

Release files for dicomforge 0.10.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dicomforge 0.10.0
File Size Uploaded
dicomforge-0.10.0.tar.gz 113.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dicomforge 0.10.0
File Interpreter ABI Platform
dicomforge-0.10.0-py3-none-any.whl Python 3 none any Details

Total release size: 188.5 kB

Release files / dicomforge-0.10.0.tar.gz

Download URL dicomforge-0.10.0.tar.gz
Size 113.7 kB
Tags Source
SHA-256 checksum
How to use checksums
662cdd9670d8f1441cf59d1b0eda9327e565414639be1bf3d7d4fbc53d9a7dd0
BLAKE2b-256 checksum
How to use checksums
780367ac1c3adb971740f1926954b0162af07224bf9701d201325ca04b28445d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / dicomforge-0.10.0-py3-none-any.whl

Download URL dicomforge-0.10.0-py3-none-any.whl
Size 74.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
61f179a94212257a867d16c50932dc588c95ec959ddec9ea2dd21d769e7fd376
BLAKE2b-256 checksum
How to use checksums
0d1b64a80add315e476df2eef9a62b062ca2e3381587447e072d1285dad80dd0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release history Release notifications | RSS feed

This release

0.10.0 This release

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page