Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Removed

- The v1 compatibility shim (`dicom_tag`, `dicom_convert`, `dicom_net`, `is_dcm`, `Execute`). Deprecated in 2.0.0, it is removed here; the namespaced API is the only surface. This is a breaking change targeted for **3.0.0** and currently being soaked as a pre-release (RC).

## [2.0.0] - 2026-06-29

Complete clean-room rewrite of the library for modern PHP. Introduces a namespaced, fully-typed object API with exception-based error handling, alongside a drop-in compatibility layer so existing v1 code keeps running unchanged. Relicensed to Apache-2.0.

The library continues to drive the DCMTK command-line toolkit (a runtime dependency installed separately, not bundled).

### Added

- **Namespaced object API** under PSR-4 (`DICOM\`, `PACS\`, `DCMTK\`):
- `DICOM\File` -- `open()` returns a typed `Dataset`; VR-validated typed accessors (`getText`, `getDate`, `getTime`, `getDateTime`, `getPersonName`, `getUID`, `getInteger`, `getDecimal`, `getTextList`) with matching setters; static `isDICOM()` detection.
- `DICOM\Dataset` -- raw group/element access (`get`, `put`, `all`) for tags without a typed accessor.
- `DICOM\Tag` -- enum of known tags.
- `DICOM\Value\*` -- `Date`, `Time`, `DateTime`, `PersonName`, `UID` value objects with parsing, formatting, and component access.
- `DICOM\Convert` -- `toJPEG`, `toThumbnail`, `toVideo` (multiframe), `fromJpeg`, `fromPdf`.
- `DICOM\Compress` -- `compress` / `decompress` with a `Compression` factory (`losslessSV1`, `lossless`, `baseline`, `extended`).
- `PACS\EchoSCU` (C-ECHO), `PACS\SCU` (C-STORE send: `send`, `sendDirectory`), `PACS\SCP` (C-STORE receive server), with `PACS\Peer`, `PACS\Association`, and `PACS\TransferSyntaxProposal` for connection, AE, and transfer-syntax negotiation.
- `DCMTK\Toolkit` -- locates the DCMTK binaries (on `PATH` or an explicit directory).
- **Exception-based error model** -- `DICOM\Exception\IOException`, `PACS\Exception\NetworkException`, and `InvalidArgumentException` replace v1's sentinel return values; failures surface at the call site.
- **Backward-compatibility shim** -- the v1 global surface (`dicom_tag`, `dicom_convert`, `dicom_net`, `is_dcm`, `Execute`) reimplemented on top of v2, so v1.1.0 code runs unchanged while emitting deprecation notices that point at the v2 equivalents.
- **Independent integration test suite** -- conversions, tag operations, compression, and network calls are cross-validated with pydicom and pynetdicom, verifying that produced files are correct when read by a separate DICOM implementation rather than round-tripped through the tools that wrote them. CI runs the suite on PHP 8.5 + DCMTK.
- **Documentation** -- `README.md` rewritten v2-first with examples verified against live PHP and a full API reference; `docs/migration-v1-to-v2.md` with a per-element v1-to-v2 mapping; before/after migration recipes in `examples/`.

### Changed

- **License: relicensed from MIT to Apache-2.0.** The prior MIT declaration asserted terms the project could not grant (the 1.x line was a fork of an originally unlicensed library). The clean-room rewrite is sole-authored, written without reference to the legacy source, and released under Apache-2.0, with provenance recorded in `NOTICE`.
- **Minimum PHP raised to 8.5** (the 1.x line required 8.0+).
- Tag access is typed and VR-validated by default; raw `'gggg,eeee'`-style addressing remains available through `DICOM\Dataset` for tags without a typed accessor.
- Error handling moved from sentinel/return-code style to exceptions throughout.

### Fixed

- **JPEG-to-DICOM works on current DCMTK.** `Convert::fromJpeg()` generates the required UIDs directly and supplies tags via typed setters, replacing the v1 `dcm2xml` template path whose warning handling prevented pixel-data embedding. This resolves the `jpg_to_dcm()` issue tracked as a known limitation in 1.1.0.

### Deprecated

- The entire v1 global surface (`dicom_tag`, `dicom_convert`, `dicom_net`, `is_dcm`, `Execute`). It remains fully functional via the compatibility shim throughout the 2.x line but emits deprecation notices, and is scheduled for removal in 3.0.0.

### Notes

- No code is shared with the original `class_dicom.php` by Dean Vaughan, which is acknowledged as a conceptual predecessor only. See `NOTICE`.

## [1.1.0] - 2026-05-27

PHP 8.x compatibility, bug fixes, an integration test suite, and documentation, on the original procedural library. No breaking API changes from 1.0.0.

### Added

- Integration test suite (`tests/`) using pydicom and pynetdicom for independent cross-validation, so every conversion, tag operation, and network call is verified by a separate DICOM implementation rather than read back by the same tools that wrote it.
- Full `README.md` with usage examples verified against live PHP, an API reference, and testing instructions.
- `ROADMAP.md` outlining the planned v2.0.0 modern-PHP refactor and feature expansion.

### Changed

- Now requires PHP 8.0+ (declared in `composer.json`); tested through PHP 8.4.

### Fixed

- `compress()` was a silent no-op: an undefined variable meant the method did nothing on every call.
- `dcmcjpeg` and `dcmdjpeg` binary paths were swapped, so compress invoked the decompressor and vice versa. Corrected in both the Windows and Linux path blocks.
- PHP 8.2 dynamic property deprecations: added the missing `$template` and `$temp_dir` property declarations on `dicom_convert`.

### Known issues

- `jpg_to_dcm()` did not work on current DCMTK versions due to warning-handling logic that prevented pixel-data embedding. (Resolved in 2.0.0.)

## [1.0.0] - 2025-09-02

- Initial Packagist release of the procedural `class_dicom.php` library (a fork of the original by Dean Vaughan): DICOM tag read/write, JPEG conversion, compression, and DICOM send/receive, wrapping the DCMTK command-line tools.

[Unreleased]: https://github.com/rbraunm/class_dicom.php/compare/v2.0.0...HEAD
[2.0.0]: https://github.com/rbraunm/class_dicom.php/compare/v1.1.0...v2.0.0
[1.1.0]: https://github.com/rbraunm/class_dicom.php/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/rbraunm/class_dicom.php/releases/tag/v1.0.0
12 changes: 2 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,11 @@ Guidance for AI agents working in this repository. **Read `CONTRIBUTING.md` firs
it is the canonical guide for conventions, the dev/test environment, and CI. The
points below are the ones that must not be missed.

## Critical: clean-room rule

This is a clean-room, Apache-2.0 v2 rewrite. Never open, read, or reference the
legacy `class_dicom.php` source. Implement only from the DICOM standard (NEMA PS3),
the DCMTK documentation, and the published v1 surface (README, `examples/`,
`docs/v1-surface.json`). Reflection and black-box behavioral observation are
allowed; reading the legacy source is not.

## Where things live

- Conventions, testing standards, dev/test environment, CI: `CONTRIBUTING.md`.
- Tooling reference (provision, `ct_exec`, research harness): `tools/README.md`.
- Plan and phase status: `docs/v2-rewrite-plan.md`, `ROADMAP.md`.
- Roadmap: `ROADMAP.md`.

## Working norms

Expand All @@ -36,7 +28,7 @@ the human-developer paths in `CONTRIBUTING.md` (the CI image and the Proxmox LXC
an agent has no container runtime, so it installs the toolchain directly. Egress
hosts to whitelist are in `CONTRIBUTING.md` (Agent sandbox).

The target is PHP 8.5 (the v2 baseline; see `docs/v2-rewrite-plan.md`) and the
The target is PHP 8.5 and the
stable DCMTK the distribution ships -- in this sandbox that is Ubuntu's `dcmtk`,
which can differ from the pinned CI image. CI stays the authority for a green
suite; the sandbox is for development iteration.
Expand Down
17 changes: 3 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
# Contributing

This is a clean-room, Apache-2.0 v2 rewrite of a PHP DICOM library that wraps the
DCMTK command-line tools. This document is the working guide for everyone (human
or agent) touching the code: how to source an implementation, the conventions, how
to run the suite, and how CI gates changes.

## Clean-room rule (v2)

The legacy `class_dicom.php` is not opened, read, or referenced while writing v2
code. Implement only from the DICOM standard (NEMA PS3), the DCMTK documentation,
and the published v1 surface (README and `examples/`). The standard is the source
of truth. Interface facts (names, signatures, observable behavior) are reusable;
expression (bodies, structure, comments) is not. Reflection and black-box
behavioral observation are permitted; reading the legacy source is not. See
`docs/v2-rewrite-plan.md` and the frozen `docs/v1-surface.json`.
This is an Apache-2.0 PHP DICOM library that wraps the DCMTK command-line tools.
This document is the working guide for everyone (human or agent) touching the
code: the conventions, how to run the suite, and how CI gates changes.

## Source file headers

Expand Down
36 changes: 4 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

A PHP library for working with DICOM medical images: tag reading and writing, JPEG conversion, compression, multiframe-to-video, and DICOM networking (C-ECHO, C-STORE send and receive). It drives the [DCMTK](https://dicom.offis.de/dcmtk.php.en) command-line toolkit under a typed, namespaced PHP API.

Version 2 is a clean-room rewrite on PHP 8.5 with a first-class object API (`DICOM\*`, `PACS\*`) and value objects for dates, names, and UIDs. The original procedural surface (`dicom_tag`, `dicom_convert`, `dicom_net`, and the global helpers) is preserved as a compatibility shim so v1 code keeps running unchanged -- it now emits deprecation notices pointing at the v2 equivalents. See [Migrating from v1](#migrating-from-v1).
A modern PHP 8.5 library for reading and writing DICOM files, converting and compressing images, and DICOM networking (C-ECHO, C-STORE), wrapping the DCMTK toolkit. It exposes a typed object API (`DICOM\*`, `PACS\*`) with value objects for dates, names, and UIDs.

Originally created by Dean Vaughan ([deanvaughan.org](http://www.deanvaughan.org/projects/class_dicom_php/)).
Acknowledges the original `class_dicom.php` by Dean Vaughan ([deanvaughan.org](http://www.deanvaughan.org/projects/class_dicom_php/)) as a conceptual predecessor; this library shares no code with it (see [NOTICE](NOTICE)).

## Requirements

Expand Down Expand Up @@ -155,24 +155,6 @@ while ($process->isRunning()) {
}
```

## Migrating from v1

Existing v1 code runs unchanged against the compatibility shim, which emits deprecation notices:

```php
$d = new dicom_tag('/path/to/image.dcm'); // deprecated; use DICOM\File
$d->load_tags();
$name = $d->get_tag('0010', '0010');
```

The `examples/` directory contains a worked migration for every operation: each script shows the v1 form as a "Before" block and the runnable v2-native "After" that bypasses the shim. A full element-by-element mapping lives in [`docs/migration-v1-to-v2.md`](docs/migration-v1-to-v2.md).

A few migration notes:

- v1's raw `'gggg,eeee'` addresses were only necessary because v1 had no typed access. Prefer the typed accessors; the raw `Dataset` get/put remains for tags without one.
- v1's `jpg_to_dcm()` XML template is gone -- `Convert::fromJpeg()` generates the UIDs and typed setters supply the tags.
- `dicom_net::$transfer_syntax` was inert in v1 (it set nothing); the shim keeps it inert and warns. Use `PACS\TransferSyntaxProposal` with `PACS\SCU` for real negotiation.

## Testing

The suite runs under PHPUnit, with independent oracles (pydicom and pynetdicom) validating that files the library produces are correct when read by a separate implementation -- not just round-tripped through the same tools that wrote them.
Expand All @@ -190,7 +172,7 @@ composer test

## API reference

### v2 API
### API

| Namespace / class | Purpose |
|---|---|
Expand All @@ -206,19 +188,9 @@ composer test
| `PACS\Peer` / `PACS\Association` / `PACS\TransferSyntaxProposal` | Connection, AE, and negotiation settings |
| `DCMTK\Toolkit` | Locates the DCMTK binaries (PATH or an explicit directory) |

### Compatibility shim (deprecated)

| Class / function | v2 replacement |
|---|---|
| `dicom_tag` | `DICOM\File` / `DICOM\Dataset` |
| `dicom_convert` | `DICOM\Convert` / `DICOM\Compress` |
| `dicom_net` | `PACS\EchoSCU` / `PACS\SCU` / `PACS\SCP` |
| `is_dcm($file)` | `DICOM\File::isDICOM($path)` |
| `Execute($command)` | `DCMTK\Tool` (internal) |

## Examples

Each script in `examples/` is a v1-to-v2 migration recipe.
Each script in `examples/` demonstrates one operation against the bundled fixture.

| File | Operation |
|---|---|
Expand Down
84 changes: 39 additions & 45 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,41 @@
# Roadmap

## v2.0.0 (priority #1) -- Clean-room rewrite & Apache-2.0 relicense

This package is a fork of Dean Vaughan's originally-unlicensed `class_dicom.php`, so the current `MIT` declaration asserts terms we can't grant, and any refactor of the existing file inherits the same defect. The fix is a **from-scratch, clean-room reimplementation** -- sole-authored, written without reference to the legacy source -- released under **Apache-2.0**. v2 faithfully replaces v1's public surface as a clean, typed PHP wrapper over DCMTK, and ships a backward-compatibility shim so existing Packagist consumers move to `^2` without code changes.

The full plan -- clean-room discipline, licensing and provenance artifacts, architecture, capability scope, research methodology, phased delivery, testing, and done criteria -- lives in **[docs/v2-rewrite-plan.md](docs/v2-rewrite-plan.md)** and is maintained there only. This roadmap intentionally does not restate it.

**Status (current):** the clean-room wrapper is landed through the `DICOM\` and `PACS\` layers -- detection, tags, conversion, compression, and DICOM networking (C-ECHO, C-STORE SCU and SCP) are complete and CI-green. The backward-compatibility shim and the docs/release step remain; phase-level detail is in the plan.

## v1.1.0 (current)

PHP 8.x compatibility fixes and an integration test suite.

- Fixed swapped `dcmcjpeg`/`dcmdjpeg` binary definitions (compress and decompress were calling each other's binaries)
- Fixed undefined variable in `compress()` that caused the method to silently do nothing
- Added missing property declarations on `dicom_convert` (`$template`, `$temp_dir`) to eliminate PHP 8.2 dynamic property deprecations
- Added `php >= 8.0` requirement to `composer.json`
- Added integration test suite with pydicom/pynetdicom cross-validation covering tags, conversions, compression, and DICOM networking

### Known issues

- `jpg_to_dcm()` returns early when `xml2dcm` produces any output, including non-fatal warnings. The bundled XML template triggers a SOPInstanceUID mismatch warning on current DCMTK versions, preventing the `img2dcm` step from embedding pixel data. The output file contains only a DICOM header with no image.

## v3 -- expansion beyond v1

v2 deliberately stops at v1's surface (see the plan). v3 is the post-v1 line: it drops the deprecated compatibility shim and grows the wrapper to cover more of the DCMTK toolset than v1 ever did. The goal is not full pydicom/pynetdicom parity but the operations that matter in a PHP web application receiving, routing, and serving DICOM images. Each item is another DCMTK tool wrapped under the same discipline as v2.

### DICOM networking

- **C-FIND (Query).** Query a remote PACS for studies, series, or instances by patient name, date range, modality, accession number, or study UID (`findscu`). The single most-requested DICOM network feature for web applications.
- **C-MOVE / C-GET (Retrieve).** Trigger a PACS to send images to a specified AE title, or pull them directly (`movescu` / `getscu`).
- **Association negotiation control.** Expose transfer syntax and abstract syntax negotiation so callers can control what gets proposed and accepted, rather than the fixed set v1's `send_dcm` hard-codes.
- **TLS support.** DICOM TLS for C-STORE and C-FIND, since many hospital networks now require encrypted DICOM traffic.

### Image handling

- **Pixel data access.** Decode pixel data into a PHP array or GD/Imagick resource for server-side processing without converting to JPEG first, including windowing and level adjustment.
- **JPEG 2000 support.** Beyond v1's JPEG baseline/lossless: JPEG 2000 lossless and lossy (transfer syntaxes 1.2.840.10008.1.2.4.90 and .91), where the DCMTK build provides it.
- **Multi-frame handling.** Extract individual frames as images without converting the whole stack to video. Frame-level access is essential for ultrasound and fluoroscopy workflows.

### Metadata and conformance

- **DICOMDIR support.** Read and write DICOMDIR files for media interchange (CD/DVD, portable media).
- **Structured report reading.** Parse SR documents (radiologist reports, CAD results) into a traversable PHP structure.
- **UID generation.** Generate conformant DICOM UIDs with a registered root, so files the library creates are traceable and don't collide.
- **Conformance statement.** Document which SOP classes, transfer syntaxes, and DIMSE services the library supports, in the format PACS administrators expect.
The line beyond the current release grows the wrapper to cover more of the DCMTK
toolset, focused on the operations that matter in a PHP web application receiving,
routing, and serving DICOM images. The goal is not full pydicom/pynetdicom parity.
Each item is another DCMTK tool wrapped under the same discipline as the current
release.

## DICOM networking

- **C-FIND (query).** Query a remote PACS for studies, series, or instances by
patient name, date range, modality, accession number, or study UID (`findscu`).
The single most-requested DICOM network feature for web applications.
- **C-MOVE / C-GET (retrieve).** Trigger a PACS to send images to a specified AE
title, or pull them directly (`movescu` / `getscu`).
- **Association negotiation control.** Expose transfer-syntax and abstract-syntax
negotiation so callers can control what gets proposed and accepted.
- **TLS support.** DICOM TLS for C-STORE and C-FIND, since many hospital networks
now require encrypted DICOM traffic.

## Image handling

- **Pixel-data access.** Decode pixel data into a PHP array or GD/Imagick resource
for server-side processing without converting to JPEG first, including windowing
and level adjustment.
- **JPEG 2000 support.** JPEG 2000 lossless and lossy (transfer syntaxes
1.2.840.10008.1.2.4.90 and .91), where the DCMTK build provides it.
- **Multi-frame handling.** Extract individual frames as images without converting
the whole stack to video. Frame-level access is essential for ultrasound and
fluoroscopy workflows.

## Metadata and conformance

- **DICOMDIR support.** Read and write DICOMDIR files for media interchange
(CD/DVD, portable media).
- **Structured report reading.** Parse SR documents (radiologist reports, CAD
results) into a traversable PHP structure.
- **UID generation.** Generate conformant DICOM UIDs with a registered root, so
files the library creates are traceable and do not collide.
- **Conformance statement.** Document which SOP classes, transfer syntaxes, and
DIMSE services the library supports, in the format PACS administrators expect.
Loading
Loading