dicom_toolkit 0.3.0
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_xcconfigwith-force_load .../libdicom_toolkit.aandDEAD_CODE_STRIPPING = NO. Previously the flag sat inpod_target_xcconfig, which never reaches the app's link line — and withuse_frameworks! :linkage => :static(or nouse_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 = NOapplies 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 ofdlopening a relativedicom_toolkit.framework/dicom_toolkitpath, 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 bycargo build --release. - (feat) Swift Package Manager support for iOS and macOS. Flutter detects a plugin's
SwiftPM support from
ios/<plugin_name>/Package.swift(andmacos/<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 newtool/rebuild_xcframework.shand 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 noPodfileat all on the SwiftPM path. Maintainers: re-runtool/rebuild_xcframework.shand commit the result wheneverrust/src/**changes, exactly likeweb/pkgafter 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_bridgeto exactly2.12.0. The generated bridge (lib/src/rust/**,rust/src/frb_generated.rs) andrust/Cargo.tomlare all stamped 2.12.0, but the manifest allowed^2.12.0, so a freshpub getresolved 2.13.0 and every consumer aborted at startup withBad 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.hasDatano 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.applyWindowingRgbagained optionallutandinvertparameters. - (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.stdDevreturns0for a single-pixel ROI instead ofNaN(the sample variance divided byn - 1). - (build) Version alignment:
pubspec.yaml,rust/Cargo.toml, and the committedrust/pkg/web/pkgwasm-pack manifests all report0.3.0(the Rust crate and the WASM manifest had drifted to0.1.0). The committed XCFrameworks are stamped with the same version. - (build) Dropped the unused
file_picker: ^11.0.2dev-dependency from the root package; only the example usesfile_picker(currently^13.1.0). - (test) A fresh clone now runs green: the groups in
test/dicom_integration_test.dartthat need thedcms/vendor corpus (Carestream / Sirona / Generic XPECT) skip themselves when that directory is absent instead of failing withPathNotFoundException, and the Windows-only backslash-path test intest/dicom_reader_test.dartnow skips off Windows. Local result: 283 passed, 25 skipped (was 286 passed / 22 failed). - (docs) AGENTS.md: documented the
u_is_rgbshader uniform (index 9), the missingdicom_controller_test.dartentry, the newlib/src/platform/loaders, and a new Apple integration section (both dependency managers, the XCFramework rebuild rule, theFlutterFrameworkwarning). - (build) iOS example deployment target raised to 14.0 (
file_picker_darwinrequires 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 implicitminCompileSdkis now 36, which matches the Flutter 3.47 app template default, so consumers are never pushed above their owncompileSdk. This removes the AAR-metadata failure below, and with it the need for thesubprojects { 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 appminSdk < 23and its template default is 24. -
(build)
sourceCompatibility/targetCompatibilityJava 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'ssettings.gradle(via Flutter's plugin loader), which is what actually happened, and the pin was actively misleading. -
(build)
namespaceis now unconditional and every property uses property-assignment syntax, so the file is valid under bothandroid.newDsl=falseandandroid.newDsl=true. -
(build) Removed the legacy
packageattribute fromandroid/src/main/AndroidManifest.xml; AGP 9 rejects it and the namespace is declared inbuild.gradle.
cargokit (vendored, patched in place) #
- (fix) Gradle 9 removed
Project.buildDirandProject.exec. Both were used bycargokit/gradle/plugin.gradle, so every Android build on Gradle 9.3.1 failed withCould not find method exec() for arguments [...] on project ':dicom_toolkit'(or, earlier, abuildDirerror). Replaced withproject.layout.buildDirectoryand an injectedorg.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.minSdkwith avariant.minSdk.apiLevelfallback; andAndroidSourceSet.jniLibs+ themerge<BuildType>NativeLibshook →variant.sources.jniLibs.addGeneratedSourceDirectory(...). - (build) Behaviour is unchanged: one Rust build per build type, targets from
FlutterPluginUtils.getTargetPlatforms()(+ x86/x64 for debug), output merged intojniLibs. 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.gradlenow documents the local deviation from upstreamfzyzcjy/cargokit(the maintained fork of the archivedirondash/cargokit) so future syncs rebase instead of re-copying.
Example app #
- (build)
example/androidregenerated 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 } }.MainActivitymoved into the namespace's package directory. - (fix) Example:
file_picker11 →^13.1.0. file_picker 11.x applies the Kotlin Gradle Plugin and itsFilePickerPluginis never compiled under AGP 9 — even in a stock app with no dicom_toolkit involved (cannot find symbol: class FilePickerPlugin). 13.x also removed theFilePickerResultwrapper, so the picker call site was updated. - (tool)
tool/agp_newdsl_probe.dart— builds a plain AGP app againstandroid/withandroid.newDsl=trueand assertslibdicom_toolkit.sois 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.planarConfigurationconstant andu_is_rgbshader 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.gitattributesto 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, andPALETTE 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.
- Rust engine now handles planar configuration 1 (interleaving planar channels into RGB triplets) and color conversion for
- (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/pixelSpacingYnow map DICOM row/column spacing to the image Y/X axes correctly. Previously the two getters were swapped:pixelSpacingXreturned the row spacing andpixelSpacingYreturned the column spacing. DICOM encodes[row, column]; the convenience getters now presentX/column(component 1) thenY/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
-32768in the Rust parser, so 8-bit pixel values and window metadata stay in the same0..255coordinate space (clinically correct contrast). - (fix)
DicomExport.toPngBytesnow disposes the renderedui.Imageon success and failure paths (encodingnull/exception), preventing a large raster leak under memory pressure. PNG encoding is now injectable so failures can be tested. - (fix)
DicomRulernow 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)
DicomRendererextracts pure, GPU-freepack16BitandapplyWindowingRgbahelpers, and routes rendering through them — enabling unit testing without a GPU/rasterizer. - (feat) new
DebugLogger(debugLog) replaces rawprint()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,
forImagepreset 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.gitignorecleanup; 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/andrust/pkg/.gitignorefiles blocked WASM artifacts from git — consumers got empty directories on clone, causingwasm_bindgen is not defined+ 404 errors at runtime - (fix) web:
dicom_toolkit.js+dicom_toolkit_bg.wasmare now committed to git — consumers get pre-built WASM automatically, no Rust toolchain required - (fix) web: added
web/pkg/as a Flutter asset inpubspec.yaml—flutter build webnow bundles WASM intoassets/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
DicomTagIdconstants (31→39):imageOrientationPatient,numberOfFrames, plusseriesDate,acquisitionDate,contentDate,toothNumber,toothRegion,imagerPixelSpacing(existing but untested) - (feat)
DicomMetadatatyped getters:imageOrientationPatient,numberOfFrames - (feat)
DicomParseResult.frameCountnow reads frommetadata.numberOfFramesinstead of hardcoded1 - (feat) dual-path pixel extraction: PATH 1 reads raw
PixelDataelement bytes directly (fast, no decoder needed for uncompressed DICOM), PATH 2 falls back todicom-pixeldatadecoder 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)
DicomMetadatacomputed properties:isMultiFrame(numberOfFrames > 1),hasSpatialInfo(imagePositionPatientnon-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)
RangeErrorinDicomRenderer.createTexturewhen pixel buffer length exceedswidth × height— loop now clamped tomin(pixelCount, data.length) - (fix) web:
bytes.toList()memory explosion on large files — passUint8Listdirectly to avoid 8× JS number unboxing - (fix) web:
FragmentProgram.fromAsset()unsupported on CanvasKit — shader compilation caught silently, falls back to CPU windowing viaRawImage - (fix) web:
DicomViewernull shader crash — usesRawImagewidget when GPU shader unavailable - (fix)
_MetadataTabmissinggroup('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
i16and 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)
DicomTagIdcount: 34→39;DicomMetadatagetter count: 35→37;DicomParseResulttag 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
DicomTagIdconstants (28→31):imagePositionPatient,sliceLocation,spacingBetweenSlices - (feat)
DicomMetadatatyped getters:imagePositionPatient,imagePositionX/Y/Z,sliceLocation,spacingBetweenSlices - (feat) convenience getters:
pixelSpacingX,pixelSpacingY,instanceNumberInt - (refactor)
dicom_decoder.darttag 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 testDicomMetadataconstructors updated for new required fields
0.2.3 #
- (fix) JPEG-compressed DICOM decoding via
dicom-pixeldataPixelDecodertrait — 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
loadFromBytescall; 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
DicomTagIdconstants:seriesDate,acquisitionDate,contentDate,toothNumber,toothRegion - (refactor) Rust
DicomMetadatastruct: 28→32 fields (addedseries_date,acquisition_date,content_date,tooth_info) - (refactor)
dicom_decoder.darttag map updated with new date and tooth tag entries - (docs) README: 28→32 extracted tags table, new Dates and Dental categories,
bestDateusage section - (docs) AGENTS.md: updated field count and tag counts throughout
- (test) 21 new tests: 11
bestDateedge 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:ioimport completely. accepting only bytes as input.
0.2.0 #
- From here on, dicom_toolkit was born.
- (BREAKING) package renamed from
flutter_dicomtodicom_toolkit- Update imports to
package:dicom_toolkit/dicom_toolkit.dart DicomController→DicomViewerController;DicomServiceremovedFileDicomLoader→RustDecoder(now implementsDicomDecoder)- Loading is bytes-only on all platforms —
loadFromBytes()only
- Update imports to
- (BREAKING) composable toolkit API replaces monolithic controller
- New:
DicomParser,DicomRenderer,DicomRoi,DicomRuler,DicomExport,DicomWindowPreset - All tools accept dependency injection via abstract interfaces
- New:
- (feat)
DicomTagId— 28 predefined DICOM tag constants + generic lookup viaDicomMetadata.tag() - (feat)
DicomPixelData— sealed class hierarchy,DicomInt16PixelDatafor 16-bit monochrome - (feat)
DicomViewerwidget —interactiveflag 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 (nodart:io) - (feat)
DicomParser.parseMetadata()— metadata-only parse withskipPixels: 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_monochrome1flag - (fix)
RangeError.rangeused inDicomParseResult.frame()instead of.index(compatibility) - (refactor) 28-field Rust
DicomMetadatastruct (addedpixel_spacing) - (refactor)
public_member_api_docsenforced — all public API documented - (refactor)
MedicalScreenremoved — useDicomViewerdirectly - (chore)
analysis_options.yamlstrict lint rules;cargokitexcluded from analysis - (docs) rewritten
README.mdandAGENTS.mdreflecting 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
DicomFileInfoclass wrappingfilePath,fileName, andDicomMetadata - (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
- Added patient demographics:
- (refactor): introduce
get_str_tag,get_float_tag,get_int_taghelpers 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
DicomMetadatadefaults and refactor DICOM processing - (feat): implement
DicomMetadatadefaults and anewconstructor in Rust; refactorprocess_dicom_fileto use them. - Added
Defaultimplementation and anewconstructor forDicomMetadatain Rust. - Refactored
process_dicom_fileto useDicomMetadatadefaults when tags are missing. - (feat): add
newInstanceanddefault_static methods toDicomMetadataandDicomFrameResultDart classes. - Added
newInstanceanddefault_static methods toDicomMetadataandDicomFrameResultDart classes. - (feat): introduce
LibShadersconstant for centralized shader asset path management. - Introduced
LibShadersconstant for centralized shader asset path management. - (refactor): integrate
LibShadersintoDicomController. - Integrated
LibShadersintoDicomControllerfor shader loading. - (fix): update
MedicalScreento support an optionaltitleparameter. - Updated
MedicalScreento support an optionaltitleparameter. - (feat): add
series_performance_test.dartcovering 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
DicomViewerwidget 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).