Skip to main content

dicom_toolkit_dart 0.2.0 copy "dicom_toolkit_dart: ^0.2.0" to clipboard
dicom_toolkit_dart: ^0.2.0 copied to clipboard

Pure-Dart DICOM toolkit with an API compatible with `dicom_toolkit`. Parse, decode, render and interactively view DICOM images — no native code, MIT-licensed.

dicom_toolkit_dart #

Dart Flutter License

Pure-Dart DICOM toolkit with an API compatible with dicom_toolkit. Parse, decode, render and interactively view DICOM images — no native code, MIT-licensed, safe for commercial App Store distribution.

Features #

Capability Description
Parsing Full DICOM Part 10 file parser with implicit/explicit VR, little/big endian, deflated transfer syntaxes
Pixel decoding Uncompressed (16-bit signed / 8-bit unsigned / RGB / Palette), RLE Lossless, JPEG Baseline (Process 1)
Metadata 35+ typed getters for patient, study, series, equipment, spatial and windowing tags
Rendering CPU-based pipeline: window/level, rescale slope/intercept, color LUT, rotation
Color maps Grayscale, HotIron, PET, Rainbow, Cool, Bone pseudo-color
Multi-series loading Scan a directory, group files by Series Instance UID, sort by instance number
Slice navigation Next/previous/first/last slice, jump-to-slice, series switching
Viewer widget Interactive DicomViewer with pan/zoom, window/level drag gestures, scroll-based zoom & slice navigation
Measurement DicomRuler (physical mm via PixelSpacing), DicomRoi (region statistics)
Export PNG export of rendered images with full rendering options
Pluggable decoder Swap the pixel decoder (e.g. FFI-based) without changing the public API

Supported Transfer Syntaxes #

UID Name Decoder
1.2.840.10008.1.2 Implicit VR Little Endian Uncompressed
1.2.840.10008.1.2.1 Explicit VR Little Endian Uncompressed
1.2.840.10008.1.2.2 Explicit VR Big Endian Uncompressed
1.2.840.10008.1.2.1.98 Deflated Explicit VR Little Endian Uncompressed
1.2.840.10008.1.2.5 RLE Lossless RLE decoder
1.2.840.10008.1.2.4.50 JPEG Baseline (Process 1) JPEG decoder

Other JPEG variants (Extended, Sequential, Progressive), JPEG-LS, JPEG 2000, MPEG-2, and DICOM Deflated Big Endian are not yet supported.

Module Structure #

The library is organized into six layers, each with clear responsibilities:

lib/
├── dicom_toolkit_dart.dart          # Public API barrel export
└── src/
    ├── core/                        # Core domain models
    │   ├── dicom_parser.dart        #   Entry point: bytes → DicomParseResult
    │   ├── dicom_metadata.dart      #   35+ typed tag getters
    │   ├── dicom_parse_result.dart  #   Parse output (metadata + pixelData)
    │   ├── dicom_pixel_data.dart    #   Sealed pixel data hierarchy
    │   ├── dicom_series.dart        #   DicomSlice / DicomSeries models
    │   ├── dicom_series_loader.dart #   Directory scanner & series grouper
    │   ├── dicom_tag_id.dart        #   50+ tag constants
    │   ├── dicom_toolkit.dart       #   Top-level static facade
    │   ├── dicom_color_map.dart     #   Color map enum
    │   ├── dicom_point.dart         #   2D point
    │   ├── roi_statistics.dart      #   ROI stats model
    │   └── read_dicom_info*.dart    #   Metadata-only fast path (IO/web)
    ├── parser/                      # Low-level binary parsing
    │   ├── byte_reader.dart         #   Bounds-checked byte reader
    │   ├── dataset.dart             #   DICOM dataset with stopAfterGroup
    │   ├── data_element.dart        #   Tag + VR + value
    │   ├── dicom_file_format.dart   #   Part 10 preamble + meta header
    │   ├── transfer_syntax.dart     #   UID → endianness/VR mode
    │   ├── vr.dart                  #   Value Representation enum
    │   ├── tag_dictionary.dart      #   Tag → name/VR lookup
    │   └── deflate_codec.dart       #   zlib inflate for Deflated TS
    ├── decoder/                     # Pixel data decoders
    │   ├── pixel_decoder.dart       #   Decoder interface
    │   ├── pure_dart_decoder.dart   #   Dispatch by transfer syntax
    │   ├── uncompressed_decoder.dart#   INT16/UINT8/RGB/Palette
    │   ├── rle_decoder.dart         #   RLE Lossless
    │   └── jpeg_baseline_decoder.dart # JPEG Baseline (Process 1)
    ├── render/                      # CPU rendering pipeline
    │   ├── dicom_renderer.dart      #   Pixel → RGBA
    │   ├── windowing.dart           #   Window center/width LUT
    │   ├── color_lut.dart           #   Pseudo-color LUTs
    │   ├── dicom_window_preset.dart #   Bone/Brain/Lung/etc presets
    │   ├── rotation.dart            #   0/90/180/270° rotation
    │   └── image_encoder.dart       #   RGBA → ui.Image
    ├── viewer/                      # Interactive Flutter widget
    │   ├── dicom_viewer.dart        #   Widget with gestures + wheel
    │   ├── dicom_viewer_controller.dart # ValueNotifier controller
    │   └── dicom_viewer_state.dart  #   Immutable state snapshot
    └── tools/                       # Measurement & export
        ├── dicom_ruler.dart         #   Linear distance (mm)
        ├── dicom_roi.dart           #   ROI statistics
        └── dicom_export.dart        #   PNG export

Installation #

1. Add the dependency #

Option A — Local path (for development):

# your_app/pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  dicom_toolkit_dart:
    path: ../dicom_toolkit_dart

Option B — Git repository:

dependencies:
  dicom_toolkit_dart:
    git:
      url: https://github.com/iplusplus812/dicom_toolkit_dart
      ref: main

Option C — Pub.dev (when published):

dependencies:
  dicom_toolkit_dart: ^0.1.0

2. Install packages #

flutter pub get

3. Import the library #

All public APIs are exported from the single barrel file:

import 'package:dicom_toolkit_dart/dicom_toolkit_dart.dart';

Quick Start #

1. Parse a single DICOM file (metadata + pixel data) #

import 'dart:io';
import 'package:dicom_toolkit_dart/dicom_toolkit_dart.dart';

final bytes = await File('/path/to/image.dcm').readAsBytes();

final parser = const DicomParser();
final result = await parser.parse(bytes);

// Metadata
print(result.metadata.patientName);   // "JOHN DOE"
print(result.metadata.modality);      // "CT"
print(result.metadata.studyDate);     // "20260731"

// Pixel info
print('${result.width} × ${result.height}');
print(result.pixelData.rescaleSlope);
print(result.pixelData.rescaleIntercept);

// Generate RGBA bytes (for custom display)
final rgba = await DicomToolkit.createTexture(result);

2. Fast metadata-only extraction #

// Reads only group 0002/0008/0010/0020/0028 — skips pixel data
final metadata = await readDicomInfo('/path/to/image.dcm');
print(metadata.patientName);
print(metadata.pixelSpacing);         // [0.5, 0.5] mm

3. Render to a Flutter ui.Image #

final image = await DicomToolkit.render(
  result,
  windowCenter: 120.0,   // optional: overrides the file's default
  windowWidth: 200.0,
  colorMap: DicomColorMap.hotIron,
  rotationDegrees: 90,
);

4. Load a directory of DICOM files (multi-series) #

final controller = DicomViewerController();

// Scan directory, group by Series Instance UID, sort by instance number
await controller.loadSeriesFromDirectory(
  '/path/to/CT_study',
  onProgress: (current, total) {
    print('Loading $current / $total');
  },
);

print('Found ${controller.allSeries.length} series');
for (final series in controller.allSeries) {
  print(series.displayLabel);
  // e.g. "Series 1 - CT Chest (267 slices)"
}

// Navigate series
await controller.nextSeries();
await controller.previousSeries();
await controller.loadSeriesByIndex(2);

// Navigate slices within the current series
await controller.nextSlice();
await controller.previousSlice();
await controller.goToSlice(50);
await controller.firstSlice();
await controller.lastSlice();

5. Interactive Viewer Widget #

class MyDicomPage extends StatefulWidget {
  const MyDicomPage({super.key});

  @override
  State<MyDicomPage> createState() => _MyDicomPageState();
}

class _MyDicomPageState extends State<MyDicomPage> {
  late final DicomViewerController _controller;
  final TransformationController _transform = TransformationController();

  @override
  void initState() {
    super.initState();
    _controller = DicomViewerController();
    _controller.loadSeriesFromDirectory('/path/to/CT_study');
  }

  @override
  void dispose() {
    _controller.dispose();
    _transform.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: DicomViewer(
        controller: _controller,
        transformationController: _transform,
        onWheelScroll: (delta) {
          // Ctrl + wheel → slice navigation (default behavior)
          // Override here if you need custom handling
        },
      ),
    );
  }
}

Viewer Gestures & Interactions #

Input Action
Mouse wheel (no modifier) Zoom in/out on the image
Ctrl + mouse wheel Navigate to next/previous slice in the series
Single-finger drag (1× scale) PACS-style window/level adjustment (horizontal → width, vertical → center)
Single-finger drag (zoomed) Pan the image
Two-finger pinch Zoom in/out
interactive: false All gestures disabled — parent takes over

The viewer uses a custom Transform + ClipRect pipeline (not Flutter's InteractiveViewer), so wheel events are fully under our control — Ctrl+wheel navigation never triggers accidental zoom.

6. Window Presets #

// Apply a named preset
controller.setWindow(40, 400);    // Soft tissue
controller.setWindow(200, 2000);  // Bone
controller.setWindow(40, 100);    // Brain
controller.setWindow(-600, 1500); // Lung

// Incremental adjustment (for drag gestures)
controller.adjustWindow(dCenter: 5.0, dWidth: 10.0);

7. Measurement Tools #

final ruler = DicomRuler(result.pixelData);
final distance = ruler.measure(
  DicomPoint(100, 200),
  DicomPoint(300, 400),
);
print('Distance: ${distance.toStringAsFixed(2)} mm');

// ROI statistics
final roi = DicomRoi(result);
final stats = roi.measure(DicomPoint(50, 50), 100, 100);
print('Min: ${stats?.min}, Max: ${stats?.max}, Mean: ${stats?.mean}');

8. Export to PNG #

final exporter = const DicomExport();
final pngBytes = await exporter.toPng(
  result,
  windowCenter: 120.0,
  windowWidth: 200.0,
  colorMap: DicomColorMap.grayscale,
  rotation: Rotation.deg90,
);

await File('output.png').writeAsBytes(pngBytes);

9. Advanced: Pluggable Decoder #

The decoder is injectable. To use a custom (e.g. FFI-based) decoder:

class MyCustomDecoder implements DicomDecoder {
  const MyCustomDecoder();

  @override
  Future<DicomParseResult> decode(Uint8List bytes, {bool skipPixels = false}) async {
    // Custom decode logic
  }
}

final parser = DicomParser(decoder: MyCustomDecoder());
final result = await parser.parse(bytes);

API Reference #

Core Classes #

Class Description
DicomToolkit Top-level static facade. init(), createTexture(), render()
DicomParser Parses DICOM bytes into DicomParseResult. Accepts a pluggable DicomDecoder
DicomParseResult Complete parse output: metadata + pixelData. Provides computeRoi()
DicomMetadata 35+ typed getters for patient/study/series/equipment tags
DicomPixelData Sealed hierarchy: DicomInt16PixelData, DicomUint8PixelData, DicomRgbPixelData, DicomPalettePixelData
DicomSlice Single image within a series: file path + pre-parsed metadata
DicomSeries A series of slices grouped by Series Instance UID; provides displayLabel, sliceCount, []
DicomSeriesLoader Scans a directory, parses each .dcm, groups into DicomSeries, sorts by instance number
DicomTagId Constants for 50+ common DICOM tags

Viewer Classes #

Class Description
DicomViewer Interactive viewer widget with wheel zoom, Ctrl+wheel slice nav, pan, window/level drag
DicomViewerController ValueNotifier controller: loadBytes, loadSeriesFromDirectory, nextSlice, nextSeries, setWindow, rotate, reset
DicomViewerState Immutable state snapshot: result, window params, color map, rotation, series, sliceIndex, totalSlices

Tools #

Class Description
DicomRuler Linear distance measurement in physical millimeters
DicomRoi Region-of-interest statistics (min/max/mean/stdDev/median)
DicomExport PNG export with full rendering pipeline options

Enums #

Enum Values
DicomColorMap grayscale, hotIron, pet, rainbow, cool, bone
Rotation deg0, deg90, deg180, deg270

Architecture #

┌─────────────────────────────────────────────────────────────────┐
│                     Application Layer                            │
│  DicomViewer · DicomViewerController · DicomToolkit              │
├─────────────────────────────────────────────────────────────────┤
│                       Tools Layer                                │
│  DicomRuler · DicomRoi · DicomExport                             │
├─────────────────────────────────────────────────────────────────┤
│                      Render Layer                                │
│  DicomRenderer · Windowing · ColorLUT · Rotation · ImageEncoder  │
├─────────────────────────────────────────────────────────────────┤
│                       Core Layer                                 │
│  DicomParser · DicomMetadata · DicomPixelData · DicomTagId       │
│  DicomSeries · DicomSlice · DicomSeriesLoader · RoiStatistics    │
├─────────────────────────────────────────────────────────────────┤
│                     Decoder Layer                                │
│  DicomDecoder · UncompressedDecoder · RleDecoder ·               │
│  JpegBaselineDecoder · PureDartDicomDecoder                      │
├─────────────────────────────────────────────────────────────────┤
│                     Parser Layer                                 │
│  ByteReader · DataElement · Dataset · SequenceReader ·           │
│  TransferSyntax · VR · TagDictionary                             │
├─────────────────────────────────────────────────────────────────┤
│                     File Format                                  │
│  DicomFileFormat · DeflateCodec                                  │
└─────────────────────────────────────────────────────────────────┘

Data flow for a directory load:

Directory
   │
   ▼
DicomSeriesLoader.loadFromDirectory()
   │  (scan *.dcm, parse metadata only)
   ▼
Group by Series Instance UID
   │  (sort by InstanceNumber)
   ▼
List<DicomSeries>
   │
   ▼
DicomViewerController.loadSeriesByIndex(0)
   │  (lazy-load pixel data for slice 0)
   ▼
DicomParseResult
   │
   ▼
DicomToolkit.render() → ui.Image → DicomViewer

Integration with GetX (Example App) #

The example/ directory contains a complete Flutter app demonstrating integration with GetX for state management, routing, internationalization, and theming. Key patterns:

class DicomController extends GetxController {
  final DicomViewerController viewerController = DicomViewerController();
  final TransformationController transformationController =
      TransformationController();

  // Rx bridging — expose ValueNotifier as GetX observables
  late final Rx<DicomViewerState> state;
  final RxList<DicomSeries> seriesList = <DicomSeries>[].obs;
  final RxInt currentSeriesIndex = 0.obs;

  @override
  void onInit() {
    super.onInit();
    state = viewerController.value.obs;
    viewerController.addListener(_syncState);
  }

  void _syncState() {
    state.value = viewerController.value;
    seriesList.assignAll(viewerController.allSeries);
    currentSeriesIndex.value = viewerController.currentSeriesIndex;
  }

  // Zoom controls (also bound to Ctrl+/Ctrl-/Ctrl+0 keyboard shortcuts)
  void zoomIn() { /* scale × 1.1 */ }
  void zoomOut() { /* scale ÷ 1.1 */ }
  void resetZoom() {
    transformationController.value = Matrix4.identity();
  }
}

See example/ for the full implementation including:

  • Directory picker (file_picker) integration
  • Series sidebar with selection
  • Slice navigator slider + prev/next/first/last buttons
  • Window width/center sliders + preset chips (Bone/Brain/Lung/etc.)
  • Keyboard shortcuts (arrows, Ctrl++/-/0, R, W/S, N/M, Home/End)
  • Metadata panel with patient/study/series info
  • Internationalization (zh_CN / en_US) via GetX Translations

Backend Integration (Network Loading) #

When the DICOM dataset comes from a backend (PACS / DICOMweb-style server) rather than the local file system, the toolkit supports a URL lazy-loading pipeline:

  1. The backend returns a Study JSON describing the series structure and the per-slice download URLs (no pixel data).
  2. The client parses the JSON into DicomSeries / DicomSlice objects — each slice carries only its url and a DicomMetadata.empty placeholder.
  3. A custom DicomSliceResolver (e.g. UrlSliceResolver) is injected into the DicomViewerController; it downloads slices on demand and caches them (LRU).
  4. As the user navigates, the viewer fetches each slice's URL, parses the real DICOM bytes, and backfills the series metadata from the parsed tags.

This mirrors a real QIDO-RS (query structure) + WADO-RS (fetch instance) workflow.

Study JSON Structure #

{
  "name": "demo-study",
  "studyId": "demo-study-id",
  "studyInstanceUid": "1.2.840.113619.2.55.3.604688119.971.1449590254.1",
  "studyDescription": "CT CHEST WITHOUT CONTRAST",
  "patientName": "Anonymized^^",
  "patientId": "0",
  "studyDate": "20151207",
  "series": [
    {
      "name": "scout",
      "seriesId": "1.2.840.113619.2.55.3.604688119.971.1449590254.2",
      "seriesNumber": 1,
      "modality": "CT",
      "bodyPartExamined": "CHEST",
      "slices": [
        { "url": "https://host:port/dicom/study-id/series-00000/image-00000" },
        { "url": "https://host:port/dicom/study-id/series-00000/image-00001" }
      ]
    },
    {
      "name": "native",
      "seriesId": "1.2.840.113619.2.55.3.604688119.971.1449590254.3",
      "seriesNumber": 2,
      "modality": "CT",
      "bodyPartExamined": "CHEST",
      "slices": [
        { "url": "https://host:port/dicom/study-id/series-00001/image-00000" },
        { "url": "https://host:port/dicom/study-id/series-00001/image-00001" }
      ]
    }
  ]
}

Field reference

Study-level (applies to the whole study; copied onto every DicomSeries):

Field Type Required Description
name String Yes Study display name (shown in the status bar)
studyId String Yes Study business ID
studyInstanceUid String No DICOM Study Instance UID (unique identifier)
studyDescription String No Study description (read directly from JSON by the app)
patientName String No Patient name (defaults to Unknown)
patientId String No Patient ID (defaults to Unknown)
studyDate String No Study date YYYYMMDD (defaults to Unknown)

Note: patientName / patientId / studyDate must use these exact keys (case-sensitive). studyDescription and studyInstanceUid are study-level fields the app reads directly from the JSON — they are not stored on DicomSeries (which is a series-level model).

Series-level (each entry in series[]):

Field Type Required Description
name String Yes Series description → DicomSeries.seriesDescription
seriesId String Yes Series Instance UID → DicomSeries.seriesInstanceUid (unique key for grouping/caching)
seriesNumber Number No Series number (defaults to the 1-based index)
modality String No Modality CT/MR/... (defaults to Unknown) — DICOM treats this as a Series-level attribute
bodyPartExamined String No Body part examined
slices Array Yes Slice list (see below)

Slice-level (each entry in slices[]):

Field Type Required Description
url String Yes Download URL of the .dcm instance (fetched lazily by the resolver)

Integration Steps #

// 1. Fetch the study JSON from the backend (structure only, no pixel data).
//    In production replace with: final res = await http.get(Uri.parse('$baseUrl/studies/$studyId'));
final studyJson = await _fetchStudyJson();

// 2. Parse the JSON into DicomSeries/DicomSlice.
//    Each slice carries only its `url`; metadata is `DicomMetadata.empty`
//    until the slice is actually downloaded & parsed.
final seriesList = _parseStudyJson(studyJson);

// 3. Inject a URL-aware resolver that downloads by URL and caches (LRU).
final resolver = UrlSliceResolver();
final controller = DicomViewerController(resolver: resolver);

// 4. Hand the series to the viewer. The first slice downloads on demand;
//    navigation to other slices triggers lazy fetch + cache.
controller.allSeries.addAll(seriesList);
controller.currentSeriesIndex = 0;
await controller.loadSeries(seriesList.first, index: 0);

Metadata Backfill #

Because slices start with DicomMetadata.empty, the viewer backfills real metadata after the first slice of each series is downloaded and parsed:

Stage Source What's available
Initial (before any download) Study JSON modality, bodyPartExamined, seriesNumber, patientName, patientId, studyDate — series list shows these immediately
After first slice parsed Real DICOM tags Overwrites the above with actual values (if present in the file)
Image-level (always from DICOM) Parsed tags rows, columns, windowCenter, windowWidth, sliceThickness, sliceLocation, photometricInterpretation — these cannot come from JSON, only from the parsed file

The backfill is handled automatically by DicomViewerController._maybeBackfillSeries — it triggers only when a series was constructed with placeholder metadata (modality == 'Unknown'), so file-system/bytes-based loading is unaffected.

Caching #

UrlSliceResolver keeps an LRU cache of downloaded slice bytes. Revisiting a previously loaded slice is instant (no re-download). Cache size is exposed via resolver.cacheSize for display (e.g. a chip in the status bar). Call resolver.clearCache() when the study is closed to release memory.

Prefetching (Background Warm-up) #

To make slice navigation feel instant, the resolver can prefetch slices in the background after the first slice of a series is displayed:

// After the first slice is rendered, prefetch the rest of the series.
// Downloads are serial (bandwidth-friendly) and skip already-cached slices.
resolver.prefetch(series.slices.skip(1));

Behavior:

  • Serial downloads — one slice at a time, so prefetch never saturates the network.
  • Shared in-flight dedup — prefetch and the user's resolve() calls share the same in-flight map, so the same URL is never downloaded twice. A user navigating to a slice being prefetched just awaits the in-flight download.
  • Cancellation — calling prefetch() again (e.g. when the user switches series) or cancelPrefetch() cancels the previous job via a generation counter; the in-flight slice finishes but no further prefetch downloads start.
  • Best-effort — prefetch errors are swallowed silently; if the user later opens a failed slice, resolve() re-downloads and surfaces the error.
  • Progress — prefetch(slices, onProgress: (done, total) => ...) reports progress for a status-bar chip.

The example app wires this up automatically: NetworkDemoController prefetches the current series' remaining slices right after the first slice renders, and re-prefetches on series switch (cancelling the old job first).

See example/lib/app/modules/network_demo/ for a complete, runnable demo of this pipeline.

Why Pure Dart? #

Concern Pure Dart (This Library) FFI/Native (dicom_toolkit)
License MIT — safe for commercial App Store distribution GPL v3 — requires entire app to be open-sourced
Build complexity flutter pub get only Requires Rust toolchain + native compilation per platform
Platform support All Flutter platforms (mobile, web, desktop) Mobile + desktop only (no web)
Size ~200 KB Dart source ~5–10 MB native binary per architecture
Debugging Full Dart stack traces Mixed Dart/Rust stack traces
Hot reload Works Requires full restart

Roadmap #

  • ✅ DICOM Part 10 file parsing (implicit/explicit VR, LE/BE, deflated)
  • ✅ Uncompressed pixel decoding (16-bit, 8-bit, RGB, Palette)
  • ✅ RLE Lossless pixel decoding
  • ✅ JPEG Baseline (Process 1) pixel decoding
  • ✅ Metadata extraction (35+ typed getters)
  • ✅ CPU rendering pipeline (windowing, color LUT, rotation)
  • ✅ Interactive viewer widget with gesture control
  • ✅ Multi-series directory loading & slice navigation
  • ✅ Mouse wheel zoom + Ctrl+wheel slice navigation
  • ✅ Measurement tools (ruler, ROI statistics)
  • ✅ PNG export
  • ❌ JPEG Extended / Sequential / Lossless transfer syntaxes
  • ❌ JPEG 2000 transfer syntax
  • ❌ Multi-frame support (currently decodes frame 0)
  • ❌ DICOM network protocols (C-STORE/C-FIND/C-MOVE)
  • ❌ DICOMweb (WADO-RS / STOW-RS / QIDO-RS)
  • ❌ 3D volume rendering (MIP, VR, SSD)

License #

This project is licensed under the MIT License — see the LICENSE file for details.

Acknowledgments #

  • DCMTK for the reference implementation and PS3.5/PS3.7 specifications
  • dicom_toolkit for the API design this library is compatible with
  • image package for pure-Dart JPEG/PNG codec support
1
likes
150
points
161
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Pure-Dart DICOM toolkit with an API compatible with `dicom_toolkit`. Parse, decode, render and interactively view DICOM images — no native code, MIT-licensed.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

archive, flutter, image, meta, path

More

Packages that depend on dicom_toolkit_dart