Feature/j2kswift v3 integration#182
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.