Skip to content
Merged
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
20 changes: 20 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ let package = Package(
.library(
name: "J2KXS",
targets: ["J2KXS"]),
// v10.17.0 — DICOM-bridge helpers product. Phase 1 ships the
// Transfer Syntax UID enum + J2KEncodingConfiguration mapping +
// codestream sniffer + PhotometricInterpretation enum mirror.
// ADR-004 compliant: no DICOM library dependency anywhere in
// J2KSwift; this product depends only on J2KCore + J2KCodec.
// Consumers who don't need DICOM ergonomics simply don't import
// J2KDICOMHelpers.
.library(
name: "J2KDICOMHelpers",
targets: ["J2KDICOMHelpers"]),
.executable(
name: "j2k",
targets: ["J2KCLI"]),
Expand Down Expand Up @@ -173,6 +183,12 @@ let package = Package(
.target(
name: "J2KXS",
dependencies: ["J2KCore"]),
// v10.17.0 — DICOM-bridge helpers. Pure additive surface,
// ADR-004 compliant: depends only on J2KCore + J2KCodec.
// No DICOM library dependency introduced.
.target(
name: "J2KDICOMHelpers",
dependencies: ["J2KCore", "J2KCodec"]),
.testTarget(
name: "J2KCoreTests",
dependencies: ["J2KCore", "J2KFileFormat"]),
Expand Down Expand Up @@ -207,6 +223,10 @@ let package = Package(
.testTarget(
name: "J2KXSTests",
dependencies: ["J2KXS", "J2KCore"]),
// v10.17.0 — J2KDICOMHelpers Phase 1 test target.
.testTarget(
name: "J2KDICOMHelpersTests",
dependencies: ["J2KDICOMHelpers", "J2KCore", "J2KCodec"]),
.testTarget(
name: "J2KComplianceTests",
dependencies: ["J2K3D", "J2KCore"]),
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

A pure Swift 6.2 implementation of JPEG 2000 (ISO/IEC 15444) encoding and decoding with strict concurrency support.

**Current Version**: 10.16.0
**Status**: Apple Silicon-first JPEG 2000 / HTJ2K (Part-15) implementation. v10.16.0 adds **`JP3DDecoder` partial-decode convenience overloads** — `decode(_:resolutionLevel:)`, `decode(_:region:)`, and `decode(_:region:resolutionLevel:)` — surfacing the v10.18-research partial-resolution pipeline + v10.13.0 K+ROI composition behind the canonical `JP3DDecoder` type. Consumers no longer have to know to construct `JP3DDecoderConfiguration(resolutionLevel:)` or switch to `JP3DROIDecoder`: the same speedups are now reachable from `JP3DDecoder` itself — **2.2-3.1× faster** for level-1 thumbnails, **3.4-4.1× faster** for ~1/4-extent ROI decodes (v10.18-research bench). MINOR / additive API — no perf change on existing paths, codestream bytes byte-identical to v10.15.0.
**Previous Release**: 10.15.0 (JP3DDecoder.preWarm discoverable convenience API)
**Current Version**: 10.17.0
**Status**: Apple Silicon-first JPEG 2000 / HTJ2K (Part-15) implementation. v10.17.0 ships **`J2KDICOMHelpers` (Phase 1)** — a new public SwiftPM library that bridges DICOM Transfer Syntax UIDs to `J2KEncodingConfiguration`. Closes a long-standing product-layer gap: library consumers using their own DICOM parser (DICOMKit, pydicom-via-XPC, etc.) get a Transfer Syntax UID + Pixel Data bytes, and previously had to hand-roll the UID-to-config mapping. Ships `J2KDICOMTransferSyntax` enum (7 DICOM UIDs, round-trip + classification), `J2KDICOMCodestreamDetector.detect(_:)` (SOC + CAP sniff), and `J2KDICOMPhotometricInterpretation` ↔ `J2KColorSpace` mapping. ADR-004 compliant: no DICOM library dependency anywhere; new product depends only on J2KCore + J2KCodec; consumers not importing it are unaffected. MINOR / additive product — no perf change, codestream bytes byte-identical to v10.16.0.
**Previous Release**: 10.16.0 (JP3DDecoder partial-decode discoverable convenience overloads)
**Release process**: see [RELEASING.md](RELEASING.md). Every release MUST update this README (Current Version line + new Release Status paragraph) — see the Release artefacts checklist for the full requirements.

## 📦 Release Status

**v10.17.0** ships **`J2KDICOMHelpers` (Phase 1)** — a new public SwiftPM library closing a long-standing product-layer gap. Per [ADR-004](Documentation/ADR/ADR-004-no-dicom-dependency.md), J2KSwift core libraries don't depend on any DICOM library. The CLI ships a `DICOMSupport.swift` for `.dcm` file loading (uses a Python fallback for compressed transfer syntaxes), but the `DICOMTransferSyntax` / `DICOMTransferSyntaxInfo` types there are CLI-private — not surfaced to library consumers. Library consumers using their own DICOM parser (DICOMKit, pydicom-via-XPC, etc.) extract `(Transfer Syntax UID, Pixel Data bytes)` and previously had to hand-roll the UID-to-`J2KEncodingConfiguration` mapping. v10.17.0 lifts the UID-bridge layer into a new public `J2KDICOMHelpers` SwiftPM library: **`J2KDICOMTransferSyntax`** enum (`CaseIterable` over all seven DICOM Part 5 Annex A JPEG 2000 / HTJ2K UIDs — `j2kLossless`, `j2kLossy`, `j2kPart2MulticompLossless`, `j2kPart2Multicomp`, `htj2kLossless`, `htj2kLossyConstant`, `htj2kLossy`) with `init?(uid:)` / `var uid` round-trip (whitespace + trailing-NUL tolerant per PS3.5 §6.2), `isLossless` / `isHTJ2K` / `isPart2` classification flags, and `encodingConfiguration(bitDepth:psnrTarget:)` returning a matching `J2KEncodingConfiguration` (with `htj2kBlockFormat: .conformant` for DICOM interop); **`J2KDICOMCodestreamDetector.detect(_:)`** sniffs SOC + (optional) CAP markers in the first ~256 bytes to classify raw codestreams as `.j2kLossless` (Part 1, no CAP) or `.htj2kLossless` (Part 15, CAP present), returning nil for non-J2K bytes (JPEG SOI, PNG signature, random bytes all correctly reject); and **`J2KDICOMPhotometricInterpretation`** enum mirror (`MONOCHROME1/2`, `RGB`, `YBR_FULL/422`, `YBR_RCT/ICT`) with bidirectional `J2KColorSpace` mapping (greyscale ↔ MONOCHROME2, sRGB ↔ RGB, yCbCr ↔ YBR_FULL; `hdr`/`hdrLinear`/`iccProfile`/`unknown` return nil reverse). ADR-004 compliant: **no DICOM library dependency anywhere in J2KSwift** — the new product depends only on J2KCore + J2KCodec; consumers not importing `J2KDICOMHelpers` are completely unaffected. **Validation**: `V10_29_*` test suites **26/26 PASS** release mode (9 TransferSyntaxRoundTrip + 5 EncodingConfigurationParity + 6 CodestreamDetection + 6 PhotometricInterpretation); end-to-end bit-exact lossless round-trip confirmed for `.j2kLossless` + `.htj2kLossless` via the helpers-built configurations. Full `swift test --filter JP3D` regression sweep **528/528 PASS**; mandatory commit gate 7/7 PASS. Also bumps `getVersion()` 10.16.0 → 10.17.0. Phase 1 deliberately ships the smallest viable shape — UID-and-config bridge only, no DICOM file parsing (Phase 2 territory: extract more of `J2KCLI/DICOMSupport.swift` or ship a DICOMKit adapter as a sibling opt-in product). AsyncSequence progress streams (originally Phase 3) deferred to a dedicated MINOR. See [RELEASE_NOTES_v10.17.0.md](RELEASE_NOTES_v10.17.0.md).

**v10.16.0** adds three **convenience overloads on `JP3DDecoder`** that surface the JP3D partial-decode capabilities behind the canonical type. The underlying pipelines have been production-default since v10.10.0 (true partial-resolution from v10.18-research, K+ROI composition from v10.13.0), but the only way to reach them from `import J2K3D` was to either construct `JP3DDecoderConfiguration(resolutionLevel: K)` (discoverable only by reading docs) or switch to `JP3DROIDecoder` (a separate actor type). v10.16.0 closes the gap with `decode(_:resolutionLevel:)`, `decode(_:region:)`, and `decode(_:region:resolutionLevel:)` overloads — all thin wrappers that construct a transient inner decoder/ROI-decoder with the requested options and delegate. The actor's `configuration` (`tolerateErrors`, `maxQualityLayers`) propagates to the transient inner instances; negative levels are clamped to 0; level 0 is bit-exact equivalent to `decode(_:)`. **Speedups surfaced (existing pipelines, v10.18-research bench)**: level-1 thumbnail decode **2.2-3.1× faster** than full decode on small-mid JP3D fixtures (mr_3d_small 2.22×, ct_3d_small 3.05×, mr_3d_mid 3.01×); ROI ~1/4-extent decode **3.4-4.1× faster** (mr_3d_small 3.37×, ct_3d_small 4.14×, mr_3d_mid 3.96×); K+ROI composition stacks both savings. Decoder-only, codestream bytes byte-identical to v10.15.0, encoder unchanged. **Validation**: `V10_28_JP3DDecoderPartialOverloadsParityTests` **6/6 PASS** (all three overloads byte-identical to existing JP3DDecoderConfiguration + JP3DROIDecoder usage; no-op level (0) bit-exact to `decode(_:)`; negative level clamped; outer-configuration propagation verified); full `swift test --filter JP3D` regression sweep **528/528 PASS** (519 pre-existing + 9 new = 2 V10_27 probe + 6 V10_28 parity + 1 pre-existing skip); mandatory commit gate 7/7 PASS. The natural symmetric ship to v10.15.0 would have been `JP3DEncoder.preWarm`; the V10_27 cold-start probe killed that candidate (leftover encoder-specific cold cost +0.10 / −1.25 ms on 128/256-cube fixtures — `JP3DDecoder.preWarm(includeWarmupDispatch: true)` already amortises encoder Metal init via `J2KMetalSession.processShared`). Also bumps the stale `getVersion()` constant `10.9.2 → 10.16.0` (drifted across 8 releases). See [RELEASE_NOTES_v10.16.0.md](RELEASE_NOTES_v10.16.0.md).

**v10.15.0** adds **`JP3DDecoder.preWarm()`** (and `JP3DROIDecoder.preWarm()`) as discoverable convenience wrappers around the existing `J2KDecoder.preWarm()` (v5.28.0). JP3D consumers inherit the one-shot Metal session init amortisation automatically (per-slice `J2KMetalSession.processShared`), but the canonical preWarm entry point lived only in `J2KCodec` — consumers using the JP3D module surface alone had to import `J2KCodec` separately. v10.15.0 closes this discoverability gap with 2-line static wrappers in `Sources/J2K3D/JP3DDecoder.swift` + `JP3DROIDecoder.swift` that delegate to `J2KDecoder.preWarm(includeWarmupDispatch:)`. Same idempotent semantics, same failure handling (silently caught on Linux where Metal is unavailable). **M2 release, mr_3d_small 128×128×16 LCG JP3D fixture**: first decode in process **28.26 ms** (cold; includes Metal init), first after `JP3DDecoder.preWarm()` **14.60 ms**, warm median **14.33 ms** — **−13.93 ms cold-start savings** that clears the 3 ms acceptance threshold. Savings are constant per process (one-shot init cost) so they're most visible on small-volume JP3D decodes; for consumers doing one-shot decodes (e.g., a DICOM viewer opening one study), this is a real-world latency win delivered through a discoverable API. No perf change on warm paths; codestream bytes byte-identical to v10.14.0; decoder-only. Validation: `V10_26_JP3DPreWarmProfile` 1/1 PASS confirms `JP3DDecoder.preWarm` wires through correctly; full `swift test --filter JP3D` regression sweep **520/520 PASS** (519 pre-existing + 1 new V10_26); mandatory commit gate 7/7 PASS. Two preceding research arcs (v10.24 + v10.25) confirmed M2 + Swift release lever-ceiling on the ROI iDWT optimization space — v10.15.0 pivots to additive discoverability and ships honestly. See [RELEASE_NOTES_v10.15.0.md](RELEASE_NOTES_v10.15.0.md).
Expand Down
Loading
Loading