Skip to content

Feature/j2kswift v3 integration#182

Merged
SureshKViswanathan merged 21 commits into
mainfrom
feature/j2kswift-v3-integration
Apr 23, 2026
Merged

Feature/j2kswift v3 integration#182
SureshKViswanathan merged 21 commits into
mainfrom
feature/j2kswift-v3-integration

Conversation

@SureshKViswanathan

Copy link
Copy Markdown
Contributor

No description provided.

raster and others added 21 commits April 20, 2026 10:57
…o v3.1.0

Matches toolchain already pinned in CI (SWIFT_VERSION 6.2, macos-15 runners with Xcode 16.2). Platform floors unchanged (macOS 14 / iOS 17 / visionOS 1); raising to macOS 15 is deferred to Milestone 1.0 when J2KSwift dependency is wired in.
…ion plan

JPEG XS is not part of the J2KSwift integration scope. Remove all Phase 10
JPEG XS / J2KXS references throughout the document:
- Executive summary sentence
- Section 1.1 J2KXS capability row
- Section 1.2 capability matrix JPEG XS row
- Section 2.1 J2KXS module map entry
- Section 2.3 J2KXS trait table row
- Section 3 Phase 10 JPEG XS section (removed)
- Section 3.9 deferred reference updated (Phase 11 → Phase 10)
- Section 7 execution order diagram

Former Phase 11 (docs/release) renumbered to Phase 10.
Completes Milestone 4.4 item and Phase 6 deliverable.

New public API in DICOMKit:

DICOMVolumeProgressiveUpdate (Sources/DICOMKit/DICOMVolume.swift)
- Sendable struct carrying sliceIndex, qualityLayer, totalLayers,
  sliceData, width, height, isVolumeComplete per JPIP quality pass.

DICOMFile.openVolumeProgressively(serverURL:sliceJPIPURIs:qualityLayers:)
(Sources/DICOMKit/DICOMKit+Volume.swift)
- AsyncStream<DICOMVolumeProgressiveUpdate> that fetches every slice
  at layer 1 first (fast overview), then iteratively refines up to
  qualityLayers passes. Designed for CT/MR studies too large to fetch
  at full quality in one round-trip.
- Failed slices are skipped (never abort the stream).

DICOMFile.openVolume(from:jpipServerURL:)
- Convenience overload for single-frame JPIP-referenced DICOM files:
  reads the JPIP URI from Pixel Data, fetches via DICOMJPIPClient,
  returns a single-slice DICOMVolume.

Tests (Tests/DICOMKitTests/JPIPTests.swift): +14 tests
- DICOMVolumeProgressiveUpdate Tests (6): property storage,
  isVolumeComplete, layer range, sliceData fidelity.
- Progressive Volume API Surface Tests (3): empty-URI stream
  terminates immediately, negative qualityLayers clamped to 1,
  Sendable conformance compile-time check.

Plan update (J2KSWIFT_V3_2_INTEGRATION_PLAN.md):
- Milestone 4.4 progressive JPIP item marked [x] with evidence.
- Phase 6 deferred items for dicom-viewer and DICOMStudio now [x].
- Phase 6 test count updated 28 → 42.
Add JP3D Multi-Planar Reconstruction (MPR) support for DICOMVolumes
decoded via the J2K3D codec.

New components:
- JP3DMPRSliceExtractor: Platform-independent axial, sagittal, and
  coronal slice extraction from DICOMVolume (frame-major voxel layout).
  Includes window/level application producing 8-bit grayscale buffers.
- JP3DMPRRenderHelpers: CGImage construction from 8-bit buffers,
  reference-line colour convention, and axis determination per plane pair.
- JP3DMPRViewModel (@observable): Manages three slice indices, crosshair
  synchronisation via MPRHelpers, and window/level state. Exposes
  setVolume(_:), setSliceIndex(_:for:), scroll(delta:in:),
  handleClick(x:y:in:), and setWindowLevel(center:width:).
- JP3DMPRView (SwiftUI): Three-panel layout (axial | sagittal | coronal)
  with Canvas image rendering, reference lines overlay, per-plane slice
  sliders, window/level sliders, crosshair-link and reference-line toggles.
  Full VoiceOver accessibility, NSLocalizedString localisation, Dynamic
  Type, and adjustable accessibility actions.

Tests:
- 37 new tests in JP3DMPRSliceExtractorTests.swift covering:
  - dimensionsModel conversion
  - sliceRange per plane
  - Axial slice extraction (dimensions, spacing, data size, voxel values)
  - Sagittal slice extraction (dimensions, spacing, data size, voxel values)
  - Coronal slice extraction (dimensions, spacing, data size, voxel values)
  - Window/level clamping and output size
  - JP3DMPRRenderHelpers (colours, axes, cgImage validity and dimensions)
  - JP3DMPRViewModel (initial state, setVolume, setSliceIndex clamping,
    scroll, window/level, crosshair linking, reference lines)

Plan: Mark Phase 8 JP3D MPR item [x] in J2KSWIFT_V3_2_INTEGRATION_PLAN.md.

Build: swift build — Build complete! (0 errors, 0 warnings)
Tests: swift test --filter JP3DMPR — 37/37 passed
Swift Testing requires @available on each @test function individually
when the suite struct itself does not carry @available. Matches the
pattern used by VolumeVisualizationViewModelTests.
Phase 9.2 — dicom-3d v1.5.0:
- Add `encode-volume` subcommand: encodes a DICOM slice series as a JP3D
  volumetric document via JP3DVolumeDocument; supports lossless, lossless-htj2k,
  lossy, and lossy-htj2k compression modes
- Add `decode-volume` subcommand: decodes a JP3D document back to individual
  DICOM slices (slice_0000.dcm … slice_NNNN.dcm)
- Add `inspect` subcommand: reads only the JSON sidecar (no codestream decode),
  displaying geometry, spacing, compression, and patient/study metadata
- Add `waitForTask` async bridge and `resolveInputFiles` directory helper
- Update dicom-3d README with JP3D sections and version history

Phase 10 — Documentation, Benchmarks, Release:
- Add Documentation/JPEG2000_GUIDE.md (transfer syntaxes, quick start,
  HTJ2K, JP3D, JPIP, hardware backends, CLI tools, benchmarks,
  troubleshooting, known limitations)
- Add dicom-j2k/README.md (subcommand table, transfer syntax table,
  fast-path transcoding note, test instructions)
- Update CHANGELOG.md: add [Unreleased] J2KSwift v3.2.0 integration entries
- Update Documentation/Architecture.md: full codec layering diagram,
  CodecBackend selection, macOS 14→15 floor
- Update Documentation/ConformanceStatement.md: v1.2.7, macOS 15,
  .92/.93/.94/.95 rows, codec section, JP3D private SOP, limitations 5–8
- Update PERFORMANCE_GUIDE.md: J2K/HTJ2K/JP3D section with decode table,
  backend speedups, JP3D throughput, JPIP latency, codec selection guide
- Update MILESTONES.md: Milestone 24 acceptance criteria (13 items, all ✅)
- Update README.md: J2KSwift integration bullets, transfer syntax table fixes,
  HTJ2K limitation removed
- Update J2KSWIFT_V3_2_INTEGRATION_PLAN.md: Phase 10 tasks marked complete,
  Section 4 Testing Strategy rewritten with real counts (677 tests/15 files),
  Acceptance Criteria checkboxes updated (9/10 met)

DICOMStudio progressive decode (Phase 8 follow-on):
- Add ProgressiveDecodeModel and ProgressiveImageView
- Wire progressive HTJ2K decoding into ImageDecodingService and
  ImageViewerViewModel/ImageViewerView

Tests:
- DICOM3DTests: add encode-volume/decode-volume/inspect scenario tests
  (lossless-HTJ2K round-trip, lossy round-trip, sidecar field verification,
  isJP3DVolumeDocument false-negative)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add J2KSwift v3.2 integration testing UI to DICOMStudio:

- J2KTestingView + J2KTestingViewModel: Interactive panel for verifying
  transfer syntax support matrix (decode/encode capability), running
  multi-iteration decode benchmarks per codec backend, and performing
  lossless/lossy round-trip tests with PSNR reporting
- CodecImageComparisonView: IDE-style 3-panel comparison (Raw | Encoded |
  Decoded) with synchronised zoom/pan for artefact inspection
- ViewerCommands: macOS menu bar commands for the image viewer
  (keyboard shortcuts via FocusedValues)
- ImageViewerView: additional codec inspector panel and layout updates
- WindowLevelPanel: refactor for cleaner SwiftUI layout
- CineControlsView: playback controls improvements
- ImageViewerViewModel: minor additions for codec info exposure
- ProgressiveImageView: minor fix
- dicom-3d: fix JP3DVolumeDocument.encode label (series:)
pkg-config --libs libopenjp2 emits -L/opt/homebrew/opt/openjpeg/lib
-lopenjp2, which causes a dynamic load of libopenjp2.7.dylib. With the
App Sandbox enabled (DICOMStudio.entitlements), that Homebrew path is
outside the sandbox and dyld aborts with __abort_with_payload (#521).

Fix: remove pkgConfig from the COpenJPEG systemLibrary target so no
pkg-config linker flags are injected. The static link via
.unsafeFlags(["/opt/homebrew/lib/libopenjp2.a"]) in DICOMCore's
linkerSettings continues to provide the symbols without a runtime
dylib dependency.

Before: otool -L dicom-info included /opt/homebrew/opt/openjpeg/lib/libopenjp2.7.dylib
After:  no libopenjp2 entry in otool output
…und-trip card

- J2KRoundTripResult: add decodedBytes, imageWidth, imageHeight,
  bitsAllocated, samplesPerPixel fields
- roundTripEntryCard: add Dimensions row (W × H · bits · color model)
  above the metrics grid; rename 'Original' → 'Raw'; add 'Decoded' row
  coloured green (match) or red (mismatch) with status icon
- resultRow helper: add statusColor parameter for green/red badge
- notes: clarify wording to distinguish raw size vs decoded size
… panels

J2KSwift is pure Swift and runs unoptimized in debug builds, causing
artificially slow decode times (e.g. 4.7s vs 124ms for OpenJPEG).
OpenJPEG is a precompiled .a and is unaffected by build configuration.

Add a #if DEBUG orange banner to both the 'J2KSwift vs OpenJPEG Decode'
and 'Encode → Decode Round-Trip' sections explaining that timing numbers
are not representative and directing users to rebuild with Release.
Add OTHER_LDFLAGS = "/opt/homebrew/lib/libopenjp2.a" to both Debug
and Release build configurations of the DICOMStudioApp Xcode target.

The unsafeFlags linkerSettings in Package.swift only apply when building
via 'swift build'. Xcode ignores SPM unsafeFlags, so the static OpenJPEG
symbols (_opj_create_decompress, _opj_decode, etc.) were unresolved when
building DICOMStudioApp through Xcode in Release mode.
…s arm64-only)

The Homebrew-installed libopenjp2.a at /opt/homebrew/lib/ is an arm64-only
static library. Without EXCLUDED_ARCHS, Xcode attempts to link both arm64
and x86_64 slices, causing 19 _opj_* undefined symbol errors for x86_64.

Add EXCLUDED_ARCHS = x86_64 to the project-level Release build configuration.
This is correct for an Apple Silicon-only Mac app; Homebrew on Apple Silicon
does not provide x86_64 libraries.
DICOMKit was calling J2KDecoder().decode() which uses the CPU-only pipeline.
Switch to J2KDecoder().decodeGPU() which uses Metal GPU for the inverse
wavelet transform (CDF 9/7 lossy and Le Gall 5/3 lossless) and inverse
colour transform stages, falling back to CPU when Metal is unavailable
(e.g. Linux CI).

This enables the 1.5–10× speed improvement over OpenJPEG that J2KSwift
documents for GPU-accelerated decoding.
v4.0.1 is the upstream hotfix addressing DICOMKit Phase 1 JPEG 2000
regressions. Public API changes are purely additive (J2KComponent.ByteOrder,
sampleByteOrder, J2KCodeBlock.quantizationStep), so no call sites needed
changes; DICOMCore and DICOMKit build clean against the new version.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e codec

- DICOMCore: OpenJPEGCodec (decode-only, used by the comparison panel; not
  registered in CodecRegistry)
- COpenJPEG: module.modulemap for Homebrew openjpeg on macOS
- Studio: JP3DComparisonView and JP3DVolumeComparisonView + view models for
  comparing J2KSwift's JP3D path against the OpenJPEG baseline
- Studio: DICOMVolumeViewerView + view model with ScrollWheelModifier for
  slice navigation, plus EnterpriseRenderHelpers for the new render paths
- Wire new views into MainView / MainViewModel / NavigationService; refresh
  J2KTestingView and the DICOMStudio scheme accordingly
- .gitignore: exclude Xcode build/ output and Claude per-project memory/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Main's squash-merge of PR #181 lacked the 5 follow-up commits this branch
accumulated afterward (the debug-build warning, libopenjp2.a static link,
x86_64 Release exclusion, decodeGPU wiring, and the raw/encoded/decoded
size panel) plus the J2KSwift 4.0.1 bump. HEAD is a strict superset, so
conflicts were resolved by taking --ours throughout. DICOMCore builds clean
against the merged tree.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@SureshKViswanathan SureshKViswanathan merged commit 86f8e24 into main Apr 23, 2026
10 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant