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
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.12.0
**Status**: Apple Silicon-first JPEG 2000 / HTJ2K (Part-15) implementation. v10.12.0 extends v10.11.0's JP3D batched bridge to the **partial-resolution and ROI lanes** — `JP3DDecoder(cfg).decode` with `cfg.resolutionLevel > 0` and `JP3DROIDecoder().decode(data, region:)` now both run through ONE batched iDWT dispatch instead of N per-slice dispatches. M2 release JP3D wins **−10 ms to −83 ms** on production CT volumes across full / partial-res / ROI lanes (1.07–1.33× faster). Decoder-only; codestream bytes byte-identical to v10.11.0.
**Previous Release**: 10.11.0 (JP3D batched GPU iDWT — full-volume)
**Current Version**: 10.13.0
**Status**: Apple Silicon-first JPEG 2000 / HTJ2K (Part-15) implementation. v10.13.0 closes the last open cell of the JP3D **{K, ROI}** decode matrix — `JP3DROIDecoder(cfg).decode(data, region:)` with `cfg.resolutionLevel > 0` now returns a downsampled sub-volume (`ceil(roi.w / 2^K) × ceil(roi.h / 2^K) × roi.d`) instead of throwing. v10.12.0 had shipped the K=0+ROI and K>0+nil lanes but left this combination as a known limitation. Decoder-only; codestream bytes byte-identical to v10.12.0.
**Previous Release**: 10.12.0 (JP3D batched bridge — partial-resolution + ROI lanes)
**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.13.0** closes the last open cell of the JP3D **{K, ROI}** decode matrix. v10.11.0 shipped the K=0+nil (full-volume) batched bridge; v10.12.0 added K=0+ROI and K>0+nil; v10.13.0 ships K>0+ROI. Both the per-tile throw in `JP3DSliceStackCodec.decode` and the upstream throw in `JP3DROIDecoder.decode` (both fail-loud guards introduced in v10.18 as Phase 5 wiring tripwires) are removed. **`JP3DROIDecoder(cfg).decode(data, region:)`** with `cfg.resolutionLevel > 0` now returns a sub-volume sized `ceil(region.width / 2^K) × ceil(region.height / 2^K) × region.depth`, with voxels bit-identical to `JP3DDecoder(cfg).decode(data)` cropped to the downsampled-mapped region. `JP3DBridgeOptions.regionOfInterest` is standardised on **full-image coordinates** (matching the 2D codec's `decodeRegion` / `decodePartial` convention); the bridge maps the region onto the reduced grid for the crop step via `region / 2^(N − partialResolutionLevel)` (mirrors `decodePartial`'s `J2KAdvancedDecoding.swift:528-543` logic). `JP3DROIDecoder` is now K-aware end-to-end: the output `roiBuffers` is sized for the downsampled output dims when K>0, and the per-tile composite uses downsampled stride for both src (the slice-stack returns downsampled per-slice buffers) and dst (the roiBuffers). Z stays full — JP3D's K is a 2D-only halving; the Z-delta residual chain is per-slice. **`V10_18_TrueSelectiveParityTests.testCombinedResolutionLevelAndROI`** is the new K+ROI parity oracle (was the v10.18 fail-loud tripwire, inverted to a success-branch check); **9/9 PASS**. `V10_21_BatchedBridgeOptionsParityTests` updated to use full-coords ROI for the K=4+ROI composition test — **7/7 PASS**. `V10_20_BatchedBridgeParityTests` 5/5, `V10_20_BatchedInverseInt32ParityTests` 12/12, `V10_20_JP3DBridgeParityTests` 5/5 — all unchanged. Full `swift test --filter JP3D` regression sweep **519/519 PASS**, mandatory commit gate 7/7 PASS. Codestream bytes byte-identical to v10.12.0; encoder unchanged. The full {K, ROI} matrix — (K=0, no ROI), (K=0, ROI), (K>0, no ROI), (K>0, ROI) — is now batched. See [RELEASE_NOTES_v10.13.0.md](RELEASE_NOTES_v10.13.0.md).

**v10.12.0** extends v10.11.0's JP3D batched bridge SPI to the **partial-resolution and ROI lanes** — closes the explicit "Partial-resolution + ROI paths not yet batched" known limitation. A new public `JP3DBridgeOptions(partialResolutionLevel:, regionOfInterest:)` struct + `J2KDecoder._jp3dDecodeToCoefficients(_:options:)` overload thread the options into the bridge SPI; the batched orchestrator truncates the chain to `effectiveLevels = N − K` for partial-res (output dims = `levelSizes[K]`), runs the full chain for ROI and crops the per-slice output via `cropImage`, and composes both (`K>0 + ROI` inside the reduced grid). The eligibility gate now requires uniform options across the batch (which JP3D's per-volume options trivially satisfy). `JP3DSliceStackCodec` removes the per-slice fallback on both branches and now builds a `JP3DBridgeOptions` from `K` + `regionOfInterest`, decodes all slices to coefficients in parallel via the new overload, then runs ONE batched iDWT + finalize. **M2 release, J2KBenchMac --jp3d, in-process, 7 timed runs / 2 warmups, median**: `full` lane ct_3d_large **−82.89 ms** (1.13×), `res1` lane ct_3d_large **−25.82 ms** (1.12×), `ROIq` lane ct_3d_large **−47.53 ms** (1.33×); ct_3d_mid full **−38.85 / res1 −10.44 / ROIq −21.18**. Per acceptance discipline (≥3 ms wins): full 4/6, res1 2/6, ROIq 3/6 fixtures clear; smaller fixtures wash on the lighter lanes (reduced workload leaves less to amortise). K=0 thumbnail (no iDWT chain) falls back to per-slice serial. The only `K > 0 AND ROI` composition still throws (the 2D codec doesn't compose those — separate arc). Opt-out via `J2K_JP3D_BATCHED_BRIDGE=0` (introduced v10.11.0; still works for all three lanes). **`V10_21_BatchedBridgeOptionsParityTests` 7/7 PASS** (K=0/2/4/N, ROI 128² and 64², ROI+K composition), `V10_20_BatchedBridgeParityTests` 5/5 PASS, `V10_20_BatchedInverseInt32ParityTests` 12/12 PASS, `V10_20_JP3DBridgeParityTests` 5/5 PASS, full `swift test --filter JP3D` regression sweep **519/519 PASS**, mandatory commit gate 7/7 PASS. Codestream bytes byte-identical to v10.11.0; encoder unchanged. See [RELEASE_NOTES_v10.12.0.md](RELEASE_NOTES_v10.12.0.md) and the bench JSONs at [`Documentation/Benchmarks/data/jp3d-bench-arm64-v10_21-{batched,serial}-20260524.json`](Documentation/Benchmarks/data/).

**v10.11.0** ships **JP3D batched GPU iDWT** — the production landing of a multi-week research arc that splits the JP3D per-tile decode pipeline at the dequant↔iDWT boundary and submits one batched Metal dispatch across the whole z-range instead of N per-slice dispatches. A new opaque-payload bridge SPI on `J2KDecoder` (`_jp3dDecodeToCoefficients` / `_jp3dIDWTAndFinalize` / `_jp3dIDWTAndFinalizeBatched`) is the architectural surface; two new Metal kernels (`j2k_dwt_inverse_53_horizontal_int_tiled_batched`, `..._vertical_..._batched`) extend the v10.3 tiled threadgroup-memory kernels with a Z-dim grid axis, processing N slices in one dispatch. `JP3DSliceStackCodec` now runs **parallel `_jp3dDecodeToCoefficients` across `[zStart, zUpper)` via a TaskGroup, then ONE batched iDWT call** before the existing sequential Z-delta residual chain. Per-slice GPU dispatch overhead amortises across the volume; the gain scales with slice count × per-slice work. **M2 release, J2KBenchMac --jp3d, in-process, 7 timed runs / 2 warmups**: ct_3d_small **−5.12 ms** (1.13×), us_3d_small −4.15 ms (1.07×), mr_3d_mid **−9.50 ms** (1.12×), ct_3d_mid **−53.25 ms** (1.17×), **ct_3d_large 16M-voxel CT −114.57 ms** (1.17×). 5/6 fixtures clear the 3 ms acceptance threshold; the wash (mr_3d_small @ 13 ms wall) is the smallest fixture where per-slice overhead dominates anyway. Kernel-level bench (16 slices × 256×256 × 3 levels): **5.93 → 2.06 ms = 2.4× faster GPU dispatch**. Eligibility gate: only `K=0 + no ROI` routes through the batched path; `K>0` keeps the per-slice `decodeResolution` loop and ROI keeps `decodeRegion` (bridging those is future work). Opt-out via `J2K_JP3D_BATCHED_BRIDGE=0`. **`V10_20_BatchedBridgeParityTests` 5/5 PASS, `V10_20_BatchedInverseInt32ParityTests` 12/12 PASS, `V10_20_JP3DBridgeParityTests` 5/5 PASS, full `swift test --filter JP3D` regression sweep 519/519 PASS, mandatory commit gate 7/7 PASS**. Codestream bytes byte-identical to v10.10.0; encoder unchanged. See [RELEASE_NOTES_v10.11.0.md](RELEASE_NOTES_v10.11.0.md) and the bench JSONs in [Documentation/Benchmarks/data/](Documentation/Benchmarks/data/).
Expand Down
135 changes: 135 additions & 0 deletions RELEASE_NOTES_v10.13.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# J2KSwift v10.13.0

**JP3D K>0 + ROI composition closes the {K, ROI} matrix.** v10.12.0
shipped the batched bridge SPI for the K=0+ROI and K>0+nil lanes but
left K>0+ROI as a known limitation — both `JP3DSliceStackCodec` and
`JP3DROIDecoder` threw on the combination. v10.13.0 removes both
throws and makes `JP3DROIDecoder` K-aware: `JP3DROIDecoder(cfg).decode(data, region:)`
with `cfg.resolutionLevel > 0` now returns a downsampled sub-volume
sized `ceil(region.width / 2^K) × ceil(region.height / 2^K) × region.depth`.

Decoder-only release; codestream bytes are byte-identical to v10.12.0.
Encoder unchanged. MINOR per RELEASING.md — no public API removed or
signature-changed; semantics of the v10.12.0 `JP3DBridgeOptions.regionOfInterest`
clarified as full-image coordinates.

## Summary

Four coordinated changes that close the last open cell of the
{K, ROI} batched-bridge matrix:

1. **Throws removed.** `JP3DSliceStackCodec.decode` no longer
rejects `regionOfInterest != nil && resolutionLevel > 0`; nor
does `JP3DROIDecoder.decode` reject the same combination
upstream. The v10.18 fail-loud guards were specification
tripwires for the Phase 5 future-work case; v10.13.0 IS that
Phase 5 wiring.

2. **`JP3DBridgeOptions.regionOfInterest` semantics standardised
on full-image coordinates.** Matches the convention used by the
2D codec's `decodeRegion` (v10.6) and `decodePartial` (v10.8).
The bridge maps the full-coords region onto the reduced grid
for the crop step via the same formula `decodePartial` uses
(`J2KAdvancedDecoding.swift:528-543`):
`region / 2^(N − partialResolutionLevel)` with ceil-up
width/height + clamp.

3. **`JP3DSliceStackCodec.outTileWidth/outTileHeight` K-aware.**
When both `K > 0` and a ROI are set, the per-slice 2D codec's
partial-res output is at the reduced grid; the per-slice output
shape is therefore `ceil(roi.width / 2^K) × ceil(roi.height / 2^K)`,
not `roi.width × roi.height`. `outTile*` track that — the
Z-delta residual chain and per-slice composite read the right
buffer shape.

4. **`JP3DROIDecoder` K-aware composite.** The output `roiBuffers`
is now sized for the downsampled output dims when `K > 0`. The
per-tile composite logic switches to downsampled stride for both
src (the slice-stack returns downsampled per-slice buffers) and
dst (`roiBuffers` sized for downsampled output). Z stays full —
JP3D's `K` is a 2D-only halving; the Z-delta residual chain is
per-slice.

## What's New — production-default

| Public API | v10.12.0 behaviour | v10.13.0 behaviour |
|---|---|---|
| `JP3DROIDecoder(configuration: cfg).decode(data, region:)` with `cfg.resolutionLevel > 0` | Threw `decodingError("combining ... is not yet supported. Phase 5 wiring task.")` | Returns sub-volume sized `ceil(region.width / 2^K) × ceil(region.height / 2^K) × region.depth`, with voxels bit-identical to `JP3DDecoder(cfg).decode(data)` cropped to the downsampled-mapped region |
| `JP3DDecoder(configuration: cfg).decode(data)` (no ROI) | v10.12.0 batched (unchanged) | Unchanged |
| `JP3DROIDecoder().decode(data, region:)` (default config, ROI only) | v10.12.0 batched (unchanged) | Unchanged |

The `JP3DBridgeOptions` struct (v10.12.0) keeps the same surface
but now explicitly documents `regionOfInterest` as full-image
coordinates with bridge-side reduced-grid mapping when partial-res
is also active.

## What's New — opt-in / opt-out

`J2K_JP3D_BATCHED_BRIDGE=0` (v10.11.0) still applies — disables the
batched bridge across all four {K, ROI} cells, forcing per-slice
serial. Diagnostic-A/B only.

## Backward compatibility

- **Codestream bytes**: byte-identical to v10.12.0 on every input.
Encoder unchanged.
- **Existing decode paths** (full-volume, K-only, ROI-only) are
byte-identical to v10.12.0 — validated by the full
`swift test --filter JP3D` regression sweep (519/519 PASS) and
by `V10_21_BatchedBridgeOptionsParityTests` (7/7 PASS).
- **Behaviour change**: the previously-throwing `K > 0 + ROI`
combination now returns a valid sub-volume instead of throwing.
Code that caught the v10.18 "Phase 5 wiring task" error and
fell back to a workaround should remove the catch.

## Test Suite Results

| Suite | Tests | Result | Coverage |
|---|---:|---|---|
| `V10_18_TrueSelectiveParityTests` | 9/9 | PASS | Includes new `testCombinedResolutionLevelAndROI` (K=1 + ROI on 64×64×8 LCG volume, bit-identical to full-partial-then-crop oracle) |
| `V10_21_BatchedBridgeOptionsParityTests` | 7/7 | PASS | Updated K+ROI composition test now uses full-coords ROI |
| `V10_20_BatchedBridgeParityTests` | 5/5 | PASS | v10.11 full-volume batched bridge unchanged |
| `V10_20_BatchedInverseInt32ParityTests` | 12/12 | PASS | v10.11 batched orchestrator unchanged |
| `V10_20_JP3DBridgeParityTests` | 5/5 | PASS | Phase 1 bridge SPI composition unchanged |
| `swift test --filter JP3D` (regression sweep) | 519/519 | PASS | Full JP3D test suite green |
| Mandatory commit gate (release mode) | 7/7 | PASS | Encode-perf + decode-perf + cross-codec strict validation |

## API surface — no additions, no removals

`JP3DBridgeOptions.regionOfInterest`'s doc comment updates to spell
out full-image coordinates + the internal reduced-grid mapping
formula. No source-breaking change to consumers — code passing a
region in v10.12.0's "downsampled coords" convention was either
crashing or working accidentally for K=0; same code now works
consistently for any K.

## Known limitations

- The bench A/B (`J2KBenchMac --jp3d`) corpus doesn't include a
K>0 + ROI lane in its current `JP3DBench.swift` matrix (only
full / res1 / roiq independently). The bridge SPI parity tests
cover the composition; a future bench update will quantify the
end-to-end win when it lands.

## Reproducing the K+ROI parity oracle

The new `V10_18_TrueSelectiveParityTests.testCombinedResolutionLevelAndROI`
test is the canonical reference:

```bash
swift test --filter "V10_18_TrueSelectiveParityTests/testCombinedResolutionLevelAndROI"
```

It encodes a 64×64×8 LCG-noise volume, decodes both
`JP3DDecoder(cfg=resolutionLevel:1)` and
`JP3DROIDecoder(cfg=resolutionLevel:1).decode(data, region:)`,
then asserts the ROI voxels are bit-identical to the full-partial
decode cropped to the downsampled-mapped region.

## Backward upgrade

`swift package update` will not auto-pick this release if your
`Package.swift` pins an exact version; bump the requirement to
`from: "10.13.0"`. No source changes required for consumers — the
new behaviour is strictly additive (the throw case is replaced
with a working return).
Loading
Loading