DICOMForge
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
pydicombackend 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.SliceThicknesswrong group/element — The tag was registered as(0050,0018)(DeviceDiameter) instead of the correct(0018,0050). Any dataset lookup usingTag.SliceThicknesswould silently returnNoneon real DICOM files. Fixed in v0.7.0. -
io._KNOWN_VRincomplete — Approximately 30 tags includingAccessionNumber, all date/time tags (StudyDate,SeriesDate, …),InstitutionName,ReferringPhysicianName,FrameOfReferenceUID,PatientIdentityRemoved, andDeidentificationMethodwere missing from the internal VR map. Writing these tags viaio.write()produced VR"UN"instead of the correct DICOM VR. Fixed in v0.7.0.
Current limitations
-
dicomforge.networkis 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 apynetdicombridge is planned for adicomforge-networkpackage in a future release. -
Compressed pixel access is delegated to pydicom —
adapt.pixel_arraycan 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_previewproduces 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 VRUNunder 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)
| File | Size | Uploaded | |
|---|---|---|---|
| dicomforge-0.10.0.tar.gz | 113.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|