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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
> [`Documentation/releases/`](Documentation/releases/) as
> `RELEASE_NOTES_vX.Y.Z.md`; this file resumes at v10.9.1.

## [10.9.2] — 2026-05-21

**Stability + packaging patch**

Fixes two latent `SIGSEGV` crashes — one in the HTJ2K encoder, one in the performance-validation report generator — and makes J2KSwift resolvable as a SwiftPM URL dependency. Codestream bytes byte-identical to v10.9.1; encoder and decoder produce identical output on every input that did not previously crash.

### Fixed

- **HTJ2K encoder crash (#439)** — `J2KEncoderPipeline`'s fused HT entropy path force-unwrapped `src.baseAddress!` / `dst.baseAddress!` when a degenerate or zero-coefficient code-block left a coefficient buffer empty, trapping with `EXC_BAD_ACCESS` (observed crashing parallel code-block workers during the DICOMKit v1.1.0 integration). All five force-unwraps across the three coefficient-extraction sites are now `guard let` — an empty buffer skips the copy and the block falls through to the existing zero-block path. `HTBlockEncoderConformant.useNEONHotPath` is also pre-warmed before the parallel dispatch (the issue's "dispatch_once race" is not a real bug — `static let` is already thread-safe).
- **`ValidationReportGenerator.textReport` SIGSEGV** — `String(format:)` with a `%s` specifier was fed Swift `String` / `NSString` values; `%s` requires a C string, so the argument was read as a (tagged) pointer and `strlen`'d on a bogus address. The 14 `%s` sites in `textReport` (13) and `J2KAcceleratedEncoder.summary()` (1) now use Swift column padding + interpolation, keeping `String(format:)` for numeric specifiers only. A crashed test process emits no `Test Case … failed` line, so this had also been silently aborting full `swift test` runs.
- **SwiftPM URL consumption (#438)** — `Package.swift` declared a path dependency on a sibling `../CompressionFamily`, making J2KSwift itself non-resolvable via `.package(url:)`. The dependency is now conditional: a local path dependency when the sibling checkout is present, a public Git URL otherwise.

### Changed

- `Sources/J2KCore/J2KCore.swift` — `getVersion()` returns `"10.9.2"`.
- CI is now Apple-only — the Windows and Linux / Linux-ARM64 workflows and jobs were removed; all build / test CI runs on `macos-15`.
- `.swiftlint.yml` — the `error`-tier thresholds of the style / threshold rules were raised so the 288 long-standing style findings stay warnings rather than failing the lint gate; 2 genuine `force_cast` sites were fixed. No source behaviour change.
- `Sources/J2KMetal/J2KMetalDWT.swift` — the canvas-anchored LL / high-band split was consolidated into a single `BandGeometry` helper (output-identical refactor; replaces 9 hand-rolled sites).
- `Scripts/run-full-regression.sh` — new per-target / per-suite regression runner with a watchdog and explicit SIGSEGV / `fatalError` crash detection.

## [10.9.1] — 2026-05-21

**Decoder correctness hotfix — GPU multi-tile inverse 5/3 DWT**
Expand Down
161 changes: 161 additions & 0 deletions Documentation/releases/RELEASE_NOTES_v10.9.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# J2KSwift v10.9.2

**Stability + packaging patch.** Fixes two latent `SIGSEGV` crashes —
one in the HTJ2K encoder, one in the performance-validation report
generator — and makes J2KSwift resolvable as a SwiftPM **URL**
dependency. Codestream bytes are byte-identical to v10.9.1; the
encoder and decoder produce identical output on every input that did
not previously crash.

## Summary

v10.9.2 is a targeted patch. It closes two crash bugs surfaced by the
DICOMKit v1.1.0 integration pass and a full-suite regression sweep,
and resolves a packaging defect that has blocked URL-based consumption
of J2KSwift since v8.0.0.

- **HTJ2K encoder crash (issue #439).** The fused HT entropy path
force-unwrapped an empty buffer's `baseAddress` and trapped with
`EXC_BAD_ACCESS` on degenerate / zero-coefficient code-blocks.
- **Performance-validation report crash.** `ValidationReportGenerator
.textReport` used `String(format:)` with `%s` specifiers fed Swift
`String` values — `%s` requires a C string, so the argument was
read as a (tagged) pointer and `strlen`'d on a bogus address. This
also aborted full-suite test runs partway through.
- **SwiftPM URL consumption (issue #438).** `Package.swift` declared a
path dependency on a sibling `../CompressionFamily`, which made
J2KSwift itself non-resolvable via `.package(url:)`.

Single-tile and multi-tile encode / decode behaviour is otherwise
unchanged.

## Fixed

- **HTJ2K encoder `baseAddress` crash (#439).** `J2KEncoderPipeline`'s
fused HT entropy path copies each code-block's coefficients out of
`subbandCoefficients` into `coeffsBuffer` via `src.baseAddress! …` /
`dst.baseAddress! …`. When a degenerate or zero-coefficient block
leaves either array empty, `baseAddress` is `nil` and the
force-unwrap traps. Observed crashing parallel code-block workers
during the DICOMKit v1.1.0 integration (a fresh `J2KEncoder` per
encode call on an HT-J2K target). All five force-unwraps across the
three coefficient-extraction sites are now `guard let`: an empty
buffer skips the copy, leaving `coeffsBuffer` zero-filled so the
block is handled by the existing zero-block path.
- **`ValidationReportGenerator.textReport` SIGSEGV.** `String(format:)`
with a `%s` specifier expects a C string (`char *`); a Swift
`String`/`NSString` passed through varargs is read as a raw,
often tagged, pointer and `strlen`'d on an unmapped address. The
fourteen `%s` sites in `textReport` (13) and
`J2KAcceleratedEncoder`'s pipeline-timer `summary()` (1) are
replaced with Swift column padding + interpolation, keeping
`String(format:)` for numeric specifiers only. A crashed test
process emits no `Test Case … failed` line, so this defect had
also been silently aborting full `swift test` runs.
- **SwiftPM URL consumption (#438).** `Package.swift`'s
`CompressionFamily` dependency is now conditional — a sibling
`../CompressionFamily` checkout (local co-development) is used via a
path dependency, otherwise it is fetched from its public Git repo by
URL. The URL form is what makes J2KSwift resolvable as a
`.package(url:)` dependency. See *Known limitations* for the
remaining publish step.

## Changed

- `Sources/J2KCore/J2KCore.swift` — `getVersion()` returns `"10.9.2"`.
- **CI is now Apple-only.** The Windows and Linux / Linux-ARM64
workflows and jobs were removed — J2KSwift is an Apple-silicon
product (see the Apple-only product scope). All build / test CI runs
on `macos-15`.
- **SwiftLint gate.** `.swiftlint.yml` raises the `error` tier of the
threshold / style rules above the in-tree maxima so the ~288
long-standing style findings stay warnings rather than blocking the
lint gate; two genuine `force_cast` sites were fixed. No source
behaviour change.
- **`J2KMetalDWT` band geometry consolidated.** The canvas-anchored
LL / high-band split (ISO/IEC 15444-1 F.4.4) was hand-rolled at nine
sites — the v10.9.1 hotfix had to correct the same `height / 2`
error in two of them independently. A single `BandGeometry` helper
now owns the split. Output-identical refactor.
- Test tooling: `Scripts/run-full-regression.sh` — a per-target /
per-suite regression runner with a watchdog and explicit
SIGSEGV / `fatalError` crash detection (a crash emits no failure
line, so failure-count parsing alone mis-records it as a pass).

## Backward compatibility

Codestream bytes are **byte-identical to v10.9.1**. The encoder fix is
a crash guard that only changes behaviour on the previously-crashing
empty-buffer path; for every input that encoded successfully before,
the output is unchanged. The decoder is untouched. The
`textReport` / `summary()` fixes are diagnostics-only. `Package.swift`
changes the dependency *declaration*, not any code.

No public API was removed or changed.

## Validation

All on a clean release-mode build:

- **Mandatory commit gate** — `J2KMedicalCorpusEncodePerformanceTests`
+ `J2KMedicalCorpusPerformanceTests` + `J2KStrictCrossCodecValidationTests`
— 0 failures.
- **Cross-codec parity** — `HTCrossCodecConformantTests`,
`HTEndToEndCrossCodecTests`, `HTGPUForward53CrossCodecTests`,
`HTNativeMultiTileSelfRoundtripTests` — exercising OpenJPEG /
OpenJPH / Grok / Kakadu — 0 failures.
- Combined: **22 tests, 0 failures, 0 crashes.**
- The `#439` fix was additionally validated against the HT cross-codec
encode suites — encode output bit-identical (the guard only alters
the previously-crashing empty-buffer path).
- A full no-filter `swift test -c release` now runs to completion —
6148 tests — where the `textReport` SIGSEGV previously aborted it.

## Performance

No performance change. v10.9.2 ships crash-safety guards (which only
alter the previously-crashing path), a diagnostics fix, a
package-manifest change and CI / test tooling — **no codec hot-path
code was touched**, encoder or decoder.

The canonical warm cross-codec benchmark (`cross_codec_warm_bench.py`,
in-process, Apple M2, median-of-7) records J2KSwift winning **30/38
encode** and **27/38 decode** fixtures against OpenJPEG / OpenJPH /
Grok / Kakadu (v10.9.1 measured 28/38 and 31/38). Because no encoder
or decoder code changed between the two tags, the per-fixture
win/loss differences are pure run-to-run measurement noise on
fixtures where J2KSwift and a competitor sit within a few percent of
each other. Full data: `benchmark-results-arm64-v10.9.2-20260521.json`.

## Known limitations

- **Issue #438 is code-complete but needs one publish step.** J2KSwift
becomes resolvable via `.package(url:)` only once
`Raster-Lab/CompressionFamily` is published as a public repository
and tagged `1.0.0`. Until then, builds without a local
`../CompressionFamily` sibling (CI, third-party consumers) cannot
resolve the dependency — the same state as every prior release.
Local development with the sibling checkout is unaffected.
- **Issue #440 (tracked).** The GPU decode paths
(`decodeGPU` / `decodeWithGPUHT`) underperform the CPU path and
Kakadu on mid / large medical images. This is not a correctness bug
— the v10.0.0 `recommendedDecodeAPI` router already steers around
the slow GPU paths — and is tracked as an optimisation target.
- Unchanged from v10.9.1: the encoder still cannot produce genuine
multi-layer codestreams (a separate lossy / rate-allocation arc).

## Reproducing

```bash
# #439 regression coverage — HT cross-codec encode:
swift test -c release \
--filter 'HTCrossCodecConformantTests|HTEndToEndCrossCodecTests|HTGPUForward53CrossCodecTests'

# Mandatory commit gate:
swift test -c release \
--filter 'J2KMedicalCorpusEncodePerformanceTests|J2KMedicalCorpusPerformanceTests|J2KStrictCrossCodecValidationTests'

# Canonical warm cross-codec benchmark:
python3 Scripts/benchmarks/cross_codec_warm_bench.py --in-proc \
--output benchmark-results-$(uname -m)-v10.9.2-$(date +%Y%m%d).json
```
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.9.1
**Status**: Apple Silicon-first JPEG 2000 / HTJ2K (Part-15) implementation. v10.9.1 is a **decoder correctness hotfix** — it fixes a latent GPU multi-tile inverse-DWT defect that corrupted the bottom edge of sub-3-megapixel odd-origin multi-tile GPU decodes (latent since v10.3.0). Decoder-only; encoder and codestream bytes byte-identical to v10.9.0.
**Previous Release**: 10.9.0 (multi-layer decode conformance fix + decodeQuality)
**Current Version**: 10.9.2
**Status**: Apple Silicon-first JPEG 2000 / HTJ2K (Part-15) implementation. v10.9.2 is a **stability + packaging patch** — it fixes two latent `SIGSEGV` crashes (HTJ2K encoder; performance-validation report generator) and makes J2KSwift resolvable as a SwiftPM URL dependency. Codestream bytes byte-identical to v10.9.1.
**Previous Release**: 10.9.1 (decoder correctness hotfix — GPU multi-tile inverse 5/3 DWT)
**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.9.2** is a stability + packaging patch. It fixes two latent `SIGSEGV` crashes and a packaging defect. **HTJ2K encoder crash (#439):** the fused HT entropy path force-unwrapped an empty buffer's `baseAddress` and trapped on degenerate / zero-coefficient code-blocks — observed crashing parallel code-block workers during the DICOMKit v1.1.0 integration; the five force-unwraps across the three coefficient-extraction sites are now `guard let` (an empty buffer falls through to the existing zero-block path). **Report-generator crash:** `ValidationReportGenerator.textReport` passed Swift `String` values to `String(format:)` `%s` specifiers — `%s` requires a C string, so the argument was `strlen`'d as a bogus tagged pointer; the 14 `%s` sites are replaced with Swift column padding + interpolation (this defect had also been silently aborting full `swift test` runs). **SwiftPM URL consumption (#438):** `Package.swift`'s `CompressionFamily` dependency is now conditional — local path for co-development, public Git URL otherwise — making J2KSwift resolvable via `.package(url:)` once the public `CompressionFamily` repo is published. Also: CI restructured Apple-only, the SwiftLint gate fixed (288 error-level findings → 0, kept as warnings), and the `J2KMetalDWT` band geometry consolidated into one `BandGeometry` helper (output-identical). Codestream bytes byte-identical to v10.9.1 — the encoder fix only alters the previously-crashing path. Validated: mandatory commit gate + HT cross-codec suites 22/22, 0 failures; a full `swift test` (6148 tests) now runs to completion where the report crash previously aborted it. See [RELEASE_NOTES_v10.9.2.md](Documentation/releases/RELEASE_NOTES_v10.9.2.md).

**v10.9.1** is a decoder correctness hotfix. The GPU multi-tile per-tile inverse 5/3 DWT corrupted the bottom edge of decoded images for sub-3-megapixel tiles at an odd tile-component canvas origin — e.g. a DX 2800×2288 image decoded as 2×2 tiles (observed max abs diff ≈ 9823). Root cause: `inverse2DGPUInt32` and `inverse2DCPUInt32` sized the LH/HH high-band as `height/2` (and `width/2`) instead of the canvas-anchored `height − llH` / `width − llW` — at an odd canvas origin the ISO/IEC 15444-1 band partition is uneven. The v8.3 fix corrected the multi-level-fused path but missed these two per-level functions; v10.3.0's `_gpuHTEntropyEnabled` routing change then made them reachable. Pure Swift host-code fix — the Metal kernels were never wrong; `default.metallib` is unchanged. Validated: `V8_3_GPUIDWTRootCauseDiagnostic` 3/3, all IDWT parity/bit-exact suites, the 5 multi-tile self-roundtrips, mandatory commit gate 7/7, cross-codec parity 14/14 (OpenJPEG/OpenJPH/Grok/Kakadu), and the warm cross-codec benchmark within run-to-run noise of v10.9.0 (J2KSwift wins 28/38 encode, 31/38 decode). Encoder and codestream bytes byte-identical to v10.9.0; decoder-only. Also bundles test maintenance — 8 stale-constant test updates and 19 dead-test deletions for de-scoped/parked features (test-only). See [RELEASE_NOTES_v10.9.1.md](Documentation/releases/RELEASE_NOTES_v10.9.1.md).

**v10.9.0** closes the partial-decode arc and fixes a conformance defect. `decodeQuality` was the last of four `notImplemented` partial-decode stubs (after `decodeResolution` v10.4/v10.5, `decodeRegion` v10.6/v10.7, `decodePartial` v10.8). Implementing it uncovered — and fixes — a real bug: `extractTileData`'s packet loop was hardcoded single-layer, so J2KSwift **silently mis-decoded any multi-layer codestream** (confirmed with Kakadu 8.4.1: a 4-layer lossless codestream decoded to wrong pixels, no error raised). v10.9.0 adds `extractTileDataMultiLayer` — the layer-aware packet decode per ISO/IEC 15444-1 B.10 (layer loop, persistent per-precinct inclusion/zero-bit-plane tag-trees, per-block `Lblock`+pass+data accumulation across layers); `extractTileData` routes to it when `qualityLayers > 1`, the single-layer path a separate byte-exact-unchanged branch. **`decodeQuality(layer:L)`** decodes quality layers `0...L` (a lower-bitrate preview); `layer == last` equals `decode()`; `cumulative:false` throws `notImplemented`. `V10_15_MultiLayerDecodeTests` 3/3 PASS: `decode()` of Kakadu 2/3/4-layer lossless codestreams **bit-identical to the original** (the conformance fix); `decodeQuality(layer:L)` **bit-identical to `kdu_expand -layers(L+1)`** for every layer — full cross-codec conformance including lossy truncated reconstructions. Single-layer regression: gate 7/7 + the v10.5–v10.8 partial-decode suites 16/16 PASS — single-layer decode untouched. Codestream bytes byte-identical to v10.8.0; encoder unchanged. `decodeResolution` + `decodeRegion` + `decodePartial` + `decodeQuality` — all four partial-decode APIs now implemented. See [RELEASE_NOTES_v10.9.0.md](Documentation/releases/RELEASE_NOTES_v10.9.0.md) and [V10_15_QUALITY_LAYER_DECODE.md](Documentation/research/V10_15_QUALITY_LAYER_DECODE.md).
Expand Down
2 changes: 1 addition & 1 deletion Sources/J2KCore/J2KCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -888,5 +888,5 @@ public struct J2KConfiguration: Sendable {
///
/// - Returns: A string representing the current version in semver format.
public func getVersion() -> String {
"10.9.1"
"10.9.2"
}
Loading
Loading