Skip to main content

dicom_toolkit 0.3.0 copy "dicom_toolkit: ^0.3.0" to clipboard
dicom_toolkit: ^0.3.0 copied to clipboard

An advanced medical imaging and DICOM processing library for Flutter.

0.3.0 #

Swift Package Manager support, and the iOS/TestFlight initialisation failure is fixed.

  • (fix) iOS and macOS: the Rust core is now linked into the app target instead of the pod's own target, and protected from dead stripping. The podspec gained a user_target_xcconfig with -force_load .../libdicom_toolkit.a and DEAD_CODE_STRIPPING = NO. Previously the flag sat in pod_target_xcconfig, which never reaches the app's link line — and with use_frameworks! :linkage => :static (or no use_frameworks!) the pod has no link step at all, so the entire Rust core was dropped. Because the FFI entry points are resolved by name at runtime rather than referenced at link time, Release builds then dead-stripped whatever remained. The app compiled and failed at runtime with "Could not initialize the DICOM engine" — typically only on a physical device or in Release/TestFlight. Dynamic frameworks and static linkage now both work. DEAD_CODE_STRIPPING = NO applies to the consumer's whole app target, so binary size grows slightly; see the README.
  • (fix) DicomToolkit.init() on iOS/macOS resolves the Rust FFI symbols from the process image instead of dlopening a relative dicom_toolkit.framework/dicom_toolkit path, which cannot work inside a packaged app (those images live in the app bundle, not the working directory). It no longer depends on the framework's file name or build layout. Development workflows (flutter test, dart run) still use the dylib built by cargo build --release.
  • (feat) Swift Package Manager support for iOS and macOS. Flutter detects a plugin's SwiftPM support from ios/<plugin_name>/Package.swift (and macos/<plugin_name>/), so a manifest is now shipped for both. SwiftPM cannot build Rust, so the core is a prebuilt dynamic XCFramework (dicom_toolkit.xcframework, produced by the new tool/rebuild_xcframework.sh and committed to git, ~8 MB, no Rust toolchain needed by consumers). It must be dynamic: Flutter links the generated plugin package statically and the FFI symbols are only resolved by name at runtime, so a static library would be dead-stripped. CocoaPods keeps working unchanged, and apps need no Podfile at all on the SwiftPM path. Maintainers: re-run tool/rebuild_xcframework.sh and commit the result whenever rust/src/** changes, exactly like web/pkg after a WASM rebuild.
  • (feat) New DicomInitializationException, thrown when the native Rust engine cannot be loaded into the process. It names the cause and the fix instead of surfacing a raw dynamic-library error.
  • (fix) Pinned flutter_rust_bridge to exactly 2.12.0. The generated bridge (lib/src/rust/**, rust/src/frb_generated.rs) and rust/Cargo.toml are all stamped 2.12.0, but the manifest allowed ^2.12.0, so a fresh pub get resolved 2.13.0 and every consumer aborted at startup with Bad state: dicom_toolkit's codegen version (2.12.0) should be the same as runtime version (2.13.0). The pin is exact because the runtime check is exact.
  • (fix) Web/CPU fallback: DicomViewerController.hasData no longer requires the fragment shader, so platforms without shader support (web/CanvasKit) render the image instead of showing "No DICOM data loaded.". When no shader is available the controller now windows, colorizes and inverts on the CPU, re-rendering on windowing / color-map / invert changes.
  • (fix) The no-shader render path (DicomRenderer.render) now honours the colour map, invert, MONOCHROME1 and rotation for monochrome images instead of applying windowing only, so CPU output matches the shader. applyWindowingRgba gained optional lut and invert parameters.
  • (fix) Shader compilation is attempted at most once. A failed compile — the normal case on web — previously re-loaded the shader asset on every render call, i.e. on every windowing drag frame.
  • (fix) Removed the per-frame trace in DicomShaderPainter.paint. It ran on every repaint (~60×/s) and accounted for ~93% of all log output (382 → 25 lines for a single parse-and-render). The renderer, decoder and controller traces remain, and release builds were already silent — the guard const-folds away, so the log strings are not even in the AOT binary.
  • (fix) RoiStatistics.stdDev returns 0 for a single-pixel ROI instead of NaN (the sample variance divided by n - 1).
  • (build) Version alignment: pubspec.yaml, rust/Cargo.toml, and the committed rust/pkg / web/pkg wasm-pack manifests all report 0.3.0 (the Rust crate and the WASM manifest had drifted to 0.1.0). The committed XCFrameworks are stamped with the same version.
  • (build) Dropped the unused file_picker: ^11.0.2 dev-dependency from the root package; only the example uses file_picker (currently ^13.1.0).
  • (test) A fresh clone now runs green: the groups in test/dicom_integration_test.dart that need the dcms/ vendor corpus (Carestream / Sirona / Generic XPECT) skip themselves when that directory is absent instead of failing with PathNotFoundException, and the Windows-only backslash-path test in test/dicom_reader_test.dart now skips off Windows. Local result: 283 passed, 25 skipped (was 286 passed / 22 failed).
  • (docs) AGENTS.md: documented the u_is_rgb shader uniform (index 9), the missing dicom_controller_test.dart entry, the new lib/src/platform/ loaders, and a new Apple integration section (both dependency managers, the XCFramework rebuild rule, the FlutterFramework warning).
  • (build) iOS example deployment target raised to 14.0 (file_picker_darwin requires it); it was already 14.0+ for the plugin itself.

0.2.10 #

Android build configuration: AGP 9-ready and AGP 10-proof. Consumers can delete their compileSdk workaround.

Android module (android/build.gradle, manifest) #

  • (breaking / build) compileSdkVersion 33 → compileSdk 36. The AAR's implicit minCompileSdk is now 36, which matches the Flutter 3.47 app template default, so consumers are never pushed above their own compileSdk. This removes the AAR-metadata failure below, and with it the need for the subprojects { afterEvaluate { ... compileSdkVersion ... } } hack in consumer root builds — delete that block:

    Dependency 'androidx.lifecycle:lifecycle-process:2.7.0' requires libraries and
    applications that depend on it to compile against version 34 or later of the Android
    APIs. :dicom_toolkit is currently compiled against android-33.
    
  • (breaking / build) minSdkVersion 19 → minSdk 24. Flutter 3.47 hard-errors on an app minSdk < 23 and its template default is 24.

  • (build) sourceCompatibility/targetCompatibility Java 8 → JavaVersion.VERSION_17.

  • (build) Dropped the vestigial buildscript { classpath 'com.android.tools.build:gradle:7.3.0' } block; AGP is supplied by the consumer's settings.gradle (via Flutter's plugin loader), which is what actually happened, and the pin was actively misleading.

  • (build) namespace is now unconditional and every property uses property-assignment syntax, so the file is valid under both android.newDsl=false and android.newDsl=true.

  • (build) Removed the legacy package attribute from android/src/main/AndroidManifest.xml; AGP 9 rejects it and the namespace is declared in build.gradle.

cargokit (vendored, patched in place) #

  • (fix) Gradle 9 removed Project.buildDir and Project.exec. Both were used by cargokit/gradle/plugin.gradle, so every Android build on Gradle 9.3.1 failed with Could not find method exec() for arguments [...] on project ':dicom_toolkit' (or, earlier, a buildDir error). Replaced with project.layout.buildDirectory and an injected org.gradle.process.ExecOperations.
  • (fix) cargokit no longer touches any API that AGP 9 blocks when android.newDsl=true (the AGP 9 default, and the only mode AGP 10 supports). Swapped: android.applicationVariants/libraryVariants + variants.all → androidComponents.onVariants; android.sdkDirectory → androidComponents.sdkComponents.sdkDirectory; android.compileSdkVersion.substring(8) → CommonExtension.compileSdk; defaultConfig.minSdkVersion.apiLevel → defaultConfig.minSdk with a variant.minSdk.apiLevel fallback; and AndroidSourceSet.jniLibs + the merge<BuildType>NativeLibs hook → variant.sources.jniLibs.addGeneratedSourceDirectory(...).
  • (build) Behaviour is unchanged: one Rust build per build type, targets from FlutterPluginUtils.getTargetPlatforms() (+ x86/x64 for debug), output merged into jniLibs. The task keeps its @Inputs and is still always out-of-date, because the Rust sources are outside Gradle's input model (cargokit's own crate-hash cache decides whether cargo recompiles).
  • (docs) cargokit/gradle/plugin.gradle now documents the local deviation from upstream fzyzcjy/cargokit (the maintained fork of the archived irondash/cargokit) so future syncs rebase instead of re-copying.

Example app #

  • (build) example/android regenerated from the Flutter 3.47 template: Gradle 9.3.1, AGP 9.1.0, Kotlin 2.4.0, android.newDsl=false + android.builtInKotlin=false, kotlin { compilerOptions { jvmTarget = JVM_17 } }. MainActivity moved into the namespace's package directory.
  • (fix) Example: file_picker 11 → ^13.1.0. file_picker 11.x applies the Kotlin Gradle Plugin and its FilePickerPlugin is never compiled under AGP 9 — even in a stock app with no dicom_toolkit involved (cannot find symbol: class FilePickerPlugin). 13.x also removed the FilePickerResult wrapper, so the picker call site was updated.
  • (tool) tool/agp_newdsl_probe.dart — builds a plain AGP app against android/ with android.newDsl=true and asserts libdicom_toolkit.so is packaged. Local verification tool; there is no CI.

Known limitation: android.newDsl=true in a Flutter app #

android.newDsl=true cannot be exercised through a Flutter app on Flutter 3.47, and this is not a dicom_toolkit problem: Flutter's own Gradle plugin casts the AGP extension to com.android.build.gradle.AbstractAppExtension (FlutterPlugin.kt:354, called from FlutterPlugin.apply) and Flutter responds to the resulting ClassCastException with its own Flutter Fix: "To resolve this update flutter or opt out of android.newDsl." No dicom_toolkit code runs before that point. The new DSL is therefore verified locally with tool/agp_newdsl_probe.dart, which builds a plain AGP app applying a stub Flutter plugin against the real module and asserts that libdicom_toolkit.so is packaged.

0.2.9 #

  • (feat) Added DicomTagId.planarConfiguration constant and u_is_rgb shader support for true-color images.
  • (fix) Android build failure caused by CRLF line endings in cargokit/run_build_tool.sh (issue #1). Normalized all shell scripts to LF and added root .gitattributes to enforce LF on checkouts and packaging.
  • (fix) Color DICOM rendering corruption for RGB, YBR, and palette color images (issue #2).
    • Rust engine now handles planar configuration 1 (interleaving planar channels into RGB triplets) and color conversion for YBR_FULL, YBR_FULL_422, YBR_PARTIAL_422, and PALETTE COLOR.
    • Dart renderer packs RGB triplets directly into 32-bit RGBA texture data via packRgb().
    • Shader and CPU fallback now render true-color pixels directly without grayscale windowing distortion.
  • (test) Added test coverage for RGB pixel packing, color texture creation, color ROI luminance calculation, and color PNG export.

0.2.8 #

  • (fix) DicomMetadata.pixelSpacingX/pixelSpacingY now map DICOM row/column spacing to the image Y/X axes correctly. Previously the two getters were swapped: pixelSpacingX returned the row spacing and pixelSpacingY returned the column spacing. DICOM encodes [row, column]; the convenience getters now present X/column (component 1) then Y/row (component 0). This is a behavioral correction for anisotropic images — consumers that relied on the swapped values will see corrected output.
  • (fix) Unsigned 8-bit images no longer get their window centre offset by -32768 in the Rust parser, so 8-bit pixel values and window metadata stay in the same 0..255 coordinate space (clinically correct contrast).
  • (fix) DicomExport.toPngBytes now disposes the rendered ui.Image on success and failure paths (encoding null/exception), preventing a large raster leak under memory pressure. PNG encoding is now injectable so failures can be tested.
  • (fix) DicomRuler now correctly maps DICOM row/column spacing to the image Y/X axes (row spacing → y, column spacing → x), fixing horizontal/vertical distance measurements on anisotropic images.
  • (refactor) DicomRenderer extracts pure, GPU-free pack16Bit and applyWindowingRgba helpers, and routes rendering through them — enabling unit testing without a GPU/rasterizer.
  • (feat) new DebugLogger (debugLog) replaces raw print() calls across the decoder, renderer, viewer, and controller. Logs now emit only in debug builds (kDebugMode); production/profile/release consoles stay clean.
  • (test) 292 tests pass — new coverage for the color-map LUT, CPU-windowing and 16-bit-packing helpers, forImage preset edge cases, PNG export (including failure-path disposal), and the corrected pixel-spacing semantics.

0.2.7 #

  • (fix) web: DicomToolkit.init() detects web platform and loads WASM from the Flutter asset path instead of the web root — no manual copy step needed in the consuming app
  • (build) tool/rebuild_wasm.ps1 — convenience script for rebuilding WASM after Rust changes; handles RUSTFLAGS, wasm-pack, and .gitignore cleanup; release build produces ~1.3 MB WASM (down from 8.5 MB debug)
  • (cleanup) removed example/web/pkg/ — no longer needed since WASM is bundled via package assets

0.2.6 #

  • (fix) web: web/pkg/ and rust/pkg/ .gitignore files blocked WASM artifacts from git — consumers got empty directories on clone, causing wasm_bindgen is not defined + 404 errors at runtime
  • (fix) web: dicom_toolkit.js + dicom_toolkit_bg.wasm are now committed to git — consumers get pre-built WASM automatically, no Rust toolchain required
  • (fix) web: added web/pkg/ as a Flutter asset in pubspec.yaml — flutter build web now bundles WASM into assets/packages/dicom_toolkit/web/pkg/ automatically
  • (docs) AGENTS.md: updated Build Commands, architecture diagram, Web WASM artifacts section, and version to 0.2.6
  • (docs) README.md: updated Web support section with new rebuild script and asset bundling workflow

0.2.5 #

  • (feat) 2 new extracted DICOM tags in Rust DicomMetadata (35→37): imageOrientationPatient (0020,0037), numberOfFrames (0028,0008)
  • (feat) new DicomTagId constants (31→39): imageOrientationPatient, numberOfFrames, plus seriesDate, acquisitionDate, contentDate, toothNumber, toothRegion, imagerPixelSpacing (existing but untested)
  • (feat) DicomMetadata typed getters: imageOrientationPatient, numberOfFrames
  • (feat) DicomParseResult.frameCount now reads from metadata.numberOfFrames instead of hardcoded 1
  • (feat) dual-path pixel extraction: PATH 1 reads raw PixelData element bytes directly (fast, no decoder needed for uncompressed DICOM), PATH 2 falls back to dicom-pixeldata decoder for compressed transfer syntaxes
  • (feat) first-frame-only extraction: multi-frame datasets only process the first frame's pixel data, avoiding OOM and minutes-long decodes on large files
  • (feat) convert_pixels() helper extracted in Rust — shared by both pixel paths
  • (feat) DicomMetadata computed properties: isMultiFrame (numberOfFrames > 1), hasSpatialInfo (imagePositionPatient non-empty + slice spacing/thickness), isVolumetric (isMultiFrame || hasSpatialInfo)
  • (feat) example app _MetadataTab: volumetric indicator tile, new Spatial section (pixel spacing, image position, slice location, slice spacing, orientation), and Frames tile in File section
  • (fix) RangeError in DicomRenderer.createTexture when pixel buffer length exceeds width × height — loop now clamped to min(pixelCount, data.length)
  • (fix) web: bytes.toList() memory explosion on large files — pass Uint8List directly to avoid 8× JS number unboxing
  • (fix) web: FragmentProgram.fromAsset() unsupported on CanvasKit — shader compilation caught silently, falls back to CPU windowing via RawImage
  • (fix) web: DicomViewer null shader crash — uses RawImage widget when GPU shader unavailable
  • (fix) _MetadataTab missing group('Tooth info') header after refactor
  • (build) WASM max memory: 1 GB → 2 GB to support large multi-frame DICOMs
  • (perf) first-frame truncation avoids converting all frames to i16 and scanning all frames for window auto-range
  • (debug) Rust eprintln! traces behind #[cfg(debug_assertions)] — auto-strip in release builds
  • (debug) extensive Dart print() traces through decoder, renderer, controller, viewer, and example app
  • (docs) DicomTagId count: 34→39; DicomMetadata getter count: 35→37; DicomParseResult tag count: 28→37
  • (test) 259 tests pass; new computed property, spatial positioning, numberOfFrames, pixel spacing parsed component, and volumetric integration tests

0.2.4 #

  • (feat) 3 new spatial positioning fields in Rust DicomMetadata (32→35): imagePositionPatient (0020,0032), sliceLocation (0020,1041), spacingBetweenSlices (0018,0088) — enables CBCT multi-slice volume reconstruction
  • (feat) new DicomTagId constants (28→31): imagePositionPatient, sliceLocation, spacingBetweenSlices
  • (feat) DicomMetadata typed getters: imagePositionPatient, imagePositionX/Y/Z, sliceLocation, spacingBetweenSlices
  • (feat) convenience getters: pixelSpacingX, pixelSpacingY, instanceNumberInt
  • (refactor) dicom_decoder.dart tag map updated with 3 new spatial tags
  • (docs) README: 32→35 extracted tags, new Spatial category; AGENTS.md: version 0.2.0→0.2.3, tag/field counts updated
  • (test) dicom_tag_id_test.dart: 28→31 constants; all existing test DicomMetadata constructors updated for new required fields

0.2.3 #

  • (fix) JPEG-compressed DICOM decoding via dicom-pixeldata PixelDecoder trait — handles Carestream and other vendors using encapsulated transfer syntaxes (JPEG lossless, JPEG-LS, JPEG 2000, RLE)
  • (fix) window center alignment for unsigned pixel data — DICOM window values are now offset by -32768 to match the shader's signed coordinate space, eliminating all-white/all-black images across Generic, Sirona, and Carestream files
  • (fix) initial window defaults to pixel data range ("Full Range") instead of DICOM header values, which are often useless (e.g. C:32768/W:65536 spanning the full 16-bit range)
  • (fix) window now resets on each loadFromBytes call; previously kept the previous file's window values
  • (fix) level/width slider ranges computed from actual pixel data range instead of DICOM header values, eliminating ±40K dead zones
  • (deps) added dicom-pixeldata = "0.9" to Rust dependencies for transfer-syntax-aware pixel decoding
  • (build) WASM rebuilt with JPEG decoder support
  • (test) 27 new tests: 23 integration tests against real Carestream/Generic/Sirona DICOM files validating JPEG decoding, window alignment, MONOCHROME1 detection, 12-bit-in-16-bit handling, and preset computation; 4 controller tests for auto-window and load-reset behavior

0.2.2 #

  • (feat) expanded DICOM date extraction to 4 tags: studyDate (0008,0020), seriesDate (0008,0021), acquisitionDate (0008,0022), contentDate (0008,0023)
  • (feat) DicomMetadata.bestDate — resolves the most recent valid non-future date among all four date tags; handles DICOM date ranges, leap years, and malformed values
  • (feat) DicomMetadata.toothInfo — extracts tooth identification from dental DICOMs via cascade: standard Tooth Number (0018,6032), Tooth Region (0018,6033), then vendor private tags (Planmeca, Carestream, VATECH, Sirona, Dexis, KaVo), with generic fallbacks (ImageComments, AcquisitionProtocolName, AcquisitionContextDescription)
  • (feat) new DicomTagId constants: seriesDate, acquisitionDate, contentDate, toothNumber, toothRegion
  • (refactor) Rust DicomMetadata struct: 28→32 fields (added series_date, acquisition_date, content_date, tooth_info)
  • (refactor) dicom_decoder.dart tag map updated with new date and tooth tag entries
  • (docs) README: 28→32 extracted tags table, new Dates and Dental categories, bestDate usage section
  • (docs) AGENTS.md: updated field count and tag counts throughout
  • (test) 21 new tests: 11 bestDate edge cases, 5 tooth info tests, 5 date field delegation tests
  • (example) metadata tab: new Dates section (best date + all 4 raw dates), Tooth tile in Acquisition section; viewer overlay uses bestDate

0.2.1 #

  • Removed dart:io import completely. accepting only bytes as input.

0.2.0 #

  • From here on, dicom_toolkit was born.
  • (BREAKING) package renamed from flutter_dicom to dicom_toolkit
    • Update imports to package:dicom_toolkit/dicom_toolkit.dart
    • DicomController → DicomViewerController; DicomService removed
    • FileDicomLoader → RustDecoder (now implements DicomDecoder)
    • Loading is bytes-only on all platforms — loadFromBytes() only
  • (BREAKING) composable toolkit API replaces monolithic controller
    • New: DicomParser, DicomRenderer, DicomRoi, DicomRuler, DicomExport, DicomWindowPreset
    • All tools accept dependency injection via abstract interfaces
  • (feat) DicomTagId — 28 predefined DICOM tag constants + generic lookup via DicomMetadata.tag()
  • (feat) DicomPixelData — sealed class hierarchy, DicomInt16PixelData for 16-bit monochrome
  • (feat) DicomViewer widget — interactive flag to disable built-in gestures for measurement overlays
  • (feat) rotation — 0°/90°/180°/270° via rotateClockwise()/rotateCounterClockwise(), included in PNG export
  • (feat) pixel-spacing-aware DicomRuler — measures mm distance with fallback to Imager Pixel Spacing (0018,1164) for X-ray
  • (feat) modality-agnostic DicomWindowPreset.forImage() — presets adapt to pixel value range, not hardcoded HU
  • (feat) DicomExport.toPngBytes() — web-safe PNG export (no dart:io)
  • (feat) DicomParser.parseMetadata() — metadata-only parse with skipPixels: true, no GPU needed
  • (feat) web support — WASM build instructions, cross-origin isolation headers, serve_debug.py
  • (feat) example app with measurement tools (ruler + ROI), rotation, presets, color maps, PNG export
  • (fix) shader uniform index 8 bound for u_monochrome1 flag
  • (fix) RangeError.range used in DicomParseResult.frame() instead of .index (compatibility)
  • (refactor) 28-field Rust DicomMetadata struct (added pixel_spacing)
  • (refactor) public_member_api_docs enforced — all public API documented
  • (refactor) MedicalScreen removed — use DicomViewer directly
  • (chore) analysis_options.yaml strict lint rules; cargokit excluded from analysis
  • (docs) rewritten README.md and AGENTS.md reflecting new architecture
  • (docs) 175 unit + widget + integration tests, all passing

0.1.0+3 #

  • (docs): refine performance benchmarks in README

0.1.0+4 #

  • (feat): add readDicomInfo() — lightweight metadata-only API (no controller, no shaders, no pixels)
  • (feat): add DicomFileInfo class wrapping filePath, fileName, and DicomMetadata
  • (feat): expand metadata extraction from 13 to 27 DICOM tags
    • Added patient demographics: studyDescription
    • Added equipment info: modality, manufacturer, manufacturerModelName, institutionName
    • Added identifiers: studyInstanceUid, seriesInstanceUid, sopInstanceUid
    • Added acquisition details: seriesDescription, bodyPartExamined, sliceThickness, instanceNumber
  • (refactor): introduce get_str_tag, get_float_tag, get_int_tag helpers for cleaner tag extraction
  • (feat): add 8-bit pixel data support with signedness-aware conversion
  • (perf): reorder pixel extraction to try fast byte-chunking first, with transfer-syntax-aware fallback
  • (docs): update README with 27-field metadata table, readDicomInfo() guide, and usage examples

0.1.0+3 #

  • (docs): refine performance benchmarks in README

0.1.0+2 #

  • (feat): add comprehensive performance benchmarks and metadata defaults
  • (feat) add DicomMetadata defaults and refactor DICOM processing
  • (feat): implement DicomMetadata defaults and a new constructor in Rust; refactor process_dicom_file to use them.
  • Added Default implementation and a new constructor for DicomMetadata in Rust.
  • Refactored process_dicom_file to use DicomMetadata defaults when tags are missing.
  • (feat): add newInstance and default_ static methods to DicomMetadata and DicomFrameResult Dart classes.
  • Added newInstance and default_ static methods to DicomMetadata and DicomFrameResult Dart classes.
  • (feat): introduce LibShaders constant for centralized shader asset path management.
  • Introduced LibShaders constant for centralized shader asset path management.
  • (refactor): integrate LibShaders into DicomController.
  • Integrated LibShaders into DicomController for shader loading.
  • (fix): update MedicalScreen to support an optional title parameter.
  • Updated MedicalScreen to support an optional title parameter.
  • (feat): add series_performance_test.dart covering throughput, latency, windowing stress, and lifecycle tests.
  • (docs): add detailed performance benchmarks and workstation-grade metrics to README.
  • (chore): add test series data to .gitignore.

0.1.0+1 #

  • (fix): README images src

0.1.0 #

  • Initial release of Flutter-Dicom.
  • High-performance Rust core for DICOM parsing and pixel extraction.
  • GPU Fragment Shaders for real-time Windowing (Level/Width).
  • Support for 16-bit precision and Hounsfield Unit (HU) mapping.
  • Built-in DicomViewer widget with interactive pan, zoom, and contrast adjustments.
  • Comprehensive metadata extraction (Patient Name, Modality, Rescale Slope/Intercept, etc.).
  • Cross-platform support (Android, iOS, macOS, Windows, Linux).