dart_tensor_preprocessing

CI Dart License PyTorch Pub Likes Pub Monthly Downloads Pub Points

Tensor preprocessing library for Flutter/Dart. NumPy-like transforms pipeline for ONNX Runtime, TFLite, and other AI inference engines.

Features

  • PyTorch Compatible: Matches PyTorch/torchvision tensor operations
  • Non-blocking: Isolate-based async execution prevents UI jank
  • Type-safe: ONNX-compatible tensor types (Float32, Int64, Uint8, etc.)
  • Zero-copy: View/stride manipulation for reshape/transpose operations
  • Declarative: Chain operations into reusable pipelines
  • SIMD Accelerated: Float32/Float64 vectorized operations for 2-4x speedup
  • Memory Efficient: Buffer pooling, uninitialized allocation, fused operations

Installation

dependencies:
  dart_tensor_preprocessing: ^1.0.0

Quick Start

import 'dart:typed_data';
import 'package:dart_tensor_preprocessing/dart_tensor_preprocessing.dart';

// Create a tensor from image data (HWC format, Uint8)
final imageData = Uint8List.fromList([/* RGB pixel data */]);
final tensor = TensorBuffer.fromUint8List(imageData, [height, width, 3]);

// Use a preset pipeline for ImageNet models
final pipeline = PipelinePresets.imagenetClassification();
final result = await pipeline.runAsync(tensor);

// result.shape: [1, 3, 224, 224] (NCHW, Float32, normalized)

Pipeline Presets

Preset Output Shape Use Case
imagenetClassification() 1, 3, 224, 224 ResNet, VGG, etc.
objectDetection() 1, 3, 640, 640 YOLO, SSD
faceRecognition() 1, 3, 112, 112 ArcFace, FaceNet
clip() 1, 3, 224, 224 CLIP models
mobileNet() 1, 3, 224, 224 MobileNet family

Custom Pipeline

final pipeline = TensorPipeline([
  ToTensorOp(normalize: true),  // HWC -> CHW, scale to [0,1]
  ResizeOp(height: 224, width: 224, antialias: true),
  NormalizeOp.imagenet(),       // ImageNet mean/std
  UnsqueezeOp.batch(),          // Add batch dimension
]);

// Sync execution
final result = pipeline.run(input);

// Async execution (runs in isolate)
final result = await pipeline.runAsync(input);

// Async with custom isolate threshold (default: 100,000 elements)
// Small tensors skip isolate overhead and run synchronously
final result = await pipeline.runAsync(input, isolateThreshold: 50000);

Available Operations

Resize & Crop

  • ResizeOp - Resize to fixed dimensions (nearest, bilinear, bicubic, area, lanczos) with ONNX-compatible coordinate transform modes
  • ResizeShortestOp - Resize preserving aspect ratio
  • CenterCropOp - Center crop to fixed dimensions
  • ClipOp - Element-wise value clamping (presets: unit, symmetric, uint8)
  • PadOp - Padding with multiple modes (constant, reflect, replicate, circular)
  • SliceOp - Python-like tensor slicing with negative index support

Normalization

  • NormalizeOp - Channel-wise normalization (presets: ImageNet, CIFAR-10, symmetric)
  • ScaleOp - Scale values (e.g., 0-255 to 0-1)
  • BatchNormOp - Batch normalization for CNN inference (PyTorch compatible)
  • LayerNormOp - Layer normalization for Transformer inference (presets: BERT, BERT-Large)
  • GroupNormOp - Group normalization for modern CNNs (PyTorch compatible)
  • InstanceNormOp - Instance normalization for style transfer and GANs (PyTorch compatible)
  • RMSNormOp - Root Mean Square normalization for LLMs (LLaMA, Gemma)

Layout

  • PermuteOp - Axis reordering (e.g., HWC to CHW)
  • ToTensorOp - HWC uint8 to CHW float32 with optional scaling
  • ToImageOp - CHW float32 to HWC uint8

Data Augmentation

  • RandomCropOp - Random cropping with deterministic seed support
  • GaussianBlurOp - Gaussian blur using separable convolution
  • RandomHorizontalFlipOp / RandomVerticalFlipOp - Probabilistic flip augmentation
  • HorizontalFlipOp / VerticalFlipOp - Deterministic flip operations
  • RandomErasingOp - Random erasing (cutout) augmentation
  • ColorJitterOp - Random brightness, contrast, saturation, and hue jitter
  • AdjustBrightnessOp / AdjustContrastOp / AdjustSaturationOp / AdjustHueOp - Individual color adjustments

Color Space Conversion

  • RgbToGrayscaleOp - RGB to grayscale using ITU-R BT.601 coefficients (torchvision Grayscale, tf.image.rgb_to_grayscale)
  • RgbToHsvOp - RGB to HSV color space conversion (tf.image.rgb_to_hsv)
  • HsvToRgbOp - HSV to RGB color space conversion (tf.image.hsv_to_rgb)

Fused Operations

  • ResizeNormalizeFusedOp - Combines resize + normalize in single pass (eliminates intermediate tensor)

Activation Functions

  • ReLUOp - Rectified Linear Unit (SIMD accelerated)
  • LeakyReLUOp - Leaky ReLU with configurable slope (SIMD accelerated)
  • GELUOp - Gaussian Error Linear Unit (Transformers: BERT, GPT, ViT)
  • SiLUOp / SwishOp - Sigmoid Linear Unit (EfficientNet, YOLOv5)
  • HardsigmoidOp - Hardware-efficient sigmoid (MobileNetV3)
  • HardswishOp - Hardware-efficient swish (MobileNetV3)
  • MishOp - Self-regularizing activation (YOLOv4+)
  • ELUOp - Exponential Linear Unit
  • SELUOp - Scaled Exponential Linear Unit
  • GLUOp - Gated Linear Unit
  • SigmoidOp - Sigmoid activation
  • TanhOp - Hyperbolic tangent activation
  • SoftmaxOp - Softmax along specified axis

Math Operations

  • AbsOp - Absolute value (SIMD accelerated)
  • NegOp - Negation (SIMD accelerated)
  • SqrtOp - Square root (SIMD accelerated)
  • ExpOp - Exponential (e^x)
  • LogOp - Natural logarithm
  • PowOp - Power operation
  • FloorOp / CeilOp / RoundOp - Element-wise rounding operations
  • SinOp / CosOp / TanOp - Trigonometric functions
  • AsinOp / AcosOp / AtanOp / Atan2Op - Inverse trigonometric functions

Arithmetic Operations

  • AddOp / SubOp - Element-wise addition/subtraction (SIMD accelerated)
  • MulOp / DivOp - Element-wise multiplication/division (SIMD accelerated)

Normalization (continued)

  • LpNormalizeOp - Lp normalization (L1, L2, Linf) along a dimension

Tensor Manipulation

  • tensorWhere() / WhereOp - Element-wise conditional selection
  • MaskedFillOp - Fill tensor positions where mask is true
  • GatherOp - Gather elements along a dimension by index
  • TopKOp - Select k largest/smallest values and indices along an axis (quickselect)
  • ArgMaxOp / ArgMinOp - Index of max/min value along an axis (ONNX ArgMax/ArgMin opset 13)
  • TileOp - Tile/repeat tensor contents
  • RepeatOp - Repeat tensor (PyTorch .repeat() semantics)
  • RollOp - Circular shift along dimensions
  • PositionalEncodingOp - Transformer positional encoding

Utility

  • concat() - Concatenates tensors along specified axis
  • stack() - Stacks tensors along a new dimension
  • split() / chunk() - Split tensor into parts along a dimension

Shape

  • UnsqueezeOp - Add dimension
  • SqueezeOp - Remove size-1 dimensions
  • ReshapeOp - Reshape tensor (supports -1 for inference)
  • FlattenOp - Flatten dimensions

Type

  • TypeCastOp - Convert between data types

Core Classes

TensorBuffer

Tensor with shape and stride metadata over physical storage.

// Create tensors
final zeros = TensorBuffer.zeros([3, 224, 224]);
final ones = TensorBuffer.ones([3, 224, 224], dtype: DType.float32);
final fromData = TensorBuffer.fromFloat32List(data, [3, 224, 224]);

// Access elements
final value = tensor[[0, 100, 100]];

// Zero-copy operations
final transposed = tensor.transpose([2, 0, 1]);  // Changes strides only
final squeezed = tensor.squeeze();

// Copy operations
final contiguous = tensor.contiguous();  // Force contiguous memory
final cloned = tensor.clone();

DType

ONNX-compatible data types with onnxId for runtime integration.

DType.float32  // ONNX ID: 1
DType.int64    // ONNX ID: 7
DType.uint8    // ONNX ID: 2

BufferPool

Memory pooling for buffer reuse, reducing GC pressure in hot paths.

final pool = BufferPool.instance;

// Acquire buffer (reuses from pool if available)
final buffer = pool.acquireFloat32(1000);

// ... use buffer ...

// Release back to pool for reuse
pool.release(buffer);

// Monitor pool usage
print('Pooled: ${pool.pooledCount} buffers, ${pool.pooledBytes} bytes');

Zero-Copy View Operations

TensorBuffer extension methods for zero-copy tensor manipulation:

// Slice along first dimension (batch slicing)
final batch = tensor.sliceFirst(2, 5);  // Views elements 2..4

// Split tensor into views
final items = tensor.unbind(0);  // List of views along dim 0

// Select single index (reduces rank)
final first = tensor.select(0, 0);  // First item, shape reduced

// Narrow dimension
final narrowed = tensor.narrow(0, 1, 3);  // 3 elements starting at 1

// Format conversion without copying
final nhwc = nchwTensor.toChannelsLast();   // NCHW -> NHWC view
final nchw = nhwcTensor.toChannelsFirst();  // NHWC -> NCHW view

// Flatten to 1D view
final flat = tensor.flatten();

In-Place Operations

Many operations support in-place modification to avoid allocation overhead:

// In-place operations (modify tensor directly)
ReLUOp().applyInPlace(tensor);
NormalizeOp.imagenet().applyInPlace(tensor);
ClipOp(min: 0, max: 1).applyInPlace(tensor);
BatchNormOp(...).applyInPlace(tensor);

// Query operation capabilities
final op = ReLUOp();
print(op.capabilities.supportsInPlace);    // true
print(op.capabilities.requiresContiguous); // true
print(op.capabilities.preservesShape);     // true

Operations supporting in-place: ReLUOp, LeakyReLUOp, SigmoidOp, TanhOp, AbsOp, NegOp, SqrtOp, ExpOp, LogOp, PowOp, AddOp, SubOp, MulOp, DivOp, ClipOp, NormalizeOp, ScaleOp, BatchNormOp, LayerNormOp, GroupNormOp, InstanceNormOp, RMSNormOp, SELUOp, LpNormalizeOp, MaskedFillOp, RandomErasingOp.

Memory Formats

Format Layout Strides (for 1,3,224,224)
contiguous NCHW 150528, 50176, 224, 1
channelsLast NHWC 150528, 1, 672, 3

PyTorch Compatibility

Compatibility is checked against pinned CPU PyTorch 2.10.0 / torchvision 0.25.0 goldens. Floating-point results use documented absolute/relative tolerances, not bitwise equality. See fixture provenance and reproduction. The table maps APIs to their reference operations; it does not imply that every PyTorch dtype, input rank or option is supported.

Migration to 1.0.0

NormalizeOp and ResizeNormalizeFusedOp copy and freeze their mean/std lists. Recreate the operation to change statistics. Gaussian blur, random erasing and color adjustments now reject invalid non-finite parameters; shape inference performs the same rank/channel checks as execution. PadMode.reflect keeps edge-inclusive symmetric boundaries, including repeated reflection for large padding. Color and random augmentation contracts that intentionally differ from torchvision are listed in COMPATIBILITY.md.

Low-level mutation dispatch rejects non-contiguous destinations; copy to contiguous storage first. SIMD binary kernels validate equal lengths in release builds. BufferPool release transfers ownership: stop using the buffer and its aliases until it is acquired again.

LayoutConvertOp.toNhwc() explicitly expects NCHW input; toNchw() expects NHWC input. Both always permute logical axes, independently of physical memoryFormat. Previously toNchw could silently skip conversion. Physical channels-last storage retains logical CHW/NCHW shape; it does not label a tensor as logically HWC/NHWC. Use PermuteOp or these directional helpers for axis changes.

Tensor shapes and strides are now immutable copies. Constructors reject views outside storage, negative strides and inconsistent stride counts. Zero strides remain valid for read-only broadcast views. Rank-zero and empty tensors remain unsupported: squeezing a single element now retains [1], so clone and reshape continue to work. Squeeze/unsqueeze accept negative axes and reject invalid axes.

eye, linspace and arange now allocate the requested dtype. Sequence factories use double arithmetic and truncate integer outputs toward zero; their parameters must be finite and produce a nonempty sequence. TypeCastOp retains its legacy half-away rounding/clamping rules, but integer-to-integer casts no longer lose precision by converting through double.

Axis reductions no longer silently cast every output to float32: float32/64 remain their input dtype, integer sum promotes to int64, and min/max retain the input dtype. Integer axis mean is rejected; cast to float first. Scalar-valued sum/mean/min/max still explicitly return double, and tensor reductions with no remaining dimensions return shape [1]. Empty multi-axis lists remain an identity operation. Argmin/argmax compare integers exactly and return the first NaN index when present. Top-k treats NaN as largest; tied indices are unordered. Gather requires integer indices (including int32, a package extension) and validates non-gather dimensions. Tile/repeat require one positive count per axis.

random and randn support float32/float64 and reject integer dtypes. Seeds are reproducible within this package, not equivalent to PyTorch seeds. Correcting the uniform endpoint and Box-Muller math changes seeded outputs from 0.9.0. LpNormalizeOp now divides by max(norm, eps) and accepts positive p (including infinity) with finite positive eps. Other normalization eps values must also be finite and positive. int64/uint64 clone and contiguous copies preserve exact integers; the explicitly double-valued indexing API still returns double.

  • Presets accept RGB HWC/NHWC uint8 pixels or float32/float64 values already in 0,1. Outputs are float32. Grayscale and RGBA require explicit RGB conversion.
  • Presets convert to CHW/NCHW before resizing, preserve an existing batch, and antialias bilinear/bicubic resizing. tflite() returns NHWC; custom(toChw: false) returns channel-last output after normalization.
  • Low-level ResizeOp still defaults to antialias: false. Its default nearest mode now follows PyTorch nearest (asymmetric coordinates); an explicit coordinateMode preserves access to ONNX-style alternatives. Antialias accepts float32/64 with half-pixel or align-corners coordinates. Integer antialias inputs must first be converted to float.
  • Bicubic uses PyTorch's coefficient -0.75 without antialias and -0.5 with antialias. Area uses adaptive-average bins. Shortest-edge sizes truncate; center crops use round-to-even and zero-pad oversized requests.
  • These numerical corrections can change model inputs compared with 0.9.0. Presets are explicit tensor recipes. They do not guarantee parity with every pretrained weight configuration or Pillow-based processor; object detection uses direct resizing, without letterboxing.

Reference operations

Operation PyTorch Equivalent
TensorBuffer.zeros() torch.zeros()
TensorBuffer.ones() torch.ones()
tensor.transpose() tensor.permute()
tensor.reshape() tensor.reshape()
tensor.squeeze() tensor.squeeze()
tensor.unsqueeze() tensor.unsqueeze()
tensor.sum() / sumAxis() tensor.sum()
tensor.sumAxes([...]) tensor.sum(dim=[...])
tensor.mean() / meanAxis() tensor.mean()
tensor.meanAxes([...]) tensor.mean(dim=[...])
tensor.min() / max() tensor.min() / max()
tensor.minAxes([...]) tensor.amin(dim=[...])
tensor.maxAxes([...]) tensor.amax(dim=[...])
NormalizeOp.imagenet() transforms.Normalize(mean, std)
ResizeOp(mode: bilinear) F.interpolate(mode='bilinear')
ResizeOp(mode: area) F.interpolate(mode='area')
ResizeOp(mode: lanczos) Lanczos3 interpolation
ResizeOp(coordinateMode: halfPixel) ONNX Resize half_pixel
ResizeOp(coordinateMode: asymmetric) ONNX Resize asymmetric (TF default)
ResizeOp(coordinateMode: pytorchHalfPixel) ONNX Resize pytorch_half_pixel
ToTensorOp() transforms.ToTensor()
ClipOp(min, max) torch.clamp(min, max)
PadOp(mode: reflect) F.pad(mode='reflect')
SliceOp([(start, end, step)]) tensor[start:end:step]
concat(tensors, axis) torch.cat(tensors, dim)
stack(tensors, dim) torch.stack(tensors, dim)
RandomCropOp transforms.RandomCrop()
GaussianBlurOp transforms.GaussianBlur()
AddOp / SubOp torch.add() / torch.sub()
MulOp / DivOp torch.mul() / torch.div()
PowOp torch.pow()
AbsOp / NegOp torch.abs() / torch.neg()
SqrtOp / ExpOp / LogOp torch.sqrt() / exp() / log()
FloorOp / CeilOp / RoundOp torch.floor() / ceil() / round()
SinOp / CosOp / TanOp torch.sin() / cos() / tan()
AsinOp / AcosOp / AtanOp torch.asin() / acos() / atan()
Atan2Op torch.atan2()
ReLUOp / LeakyReLUOp F.relu() / F.leaky_relu()
GELUOp F.gelu()
SiLUOp / SwishOp F.silu()
HardsigmoidOp F.hardsigmoid()
HardswishOp F.hardswish()
MishOp F.mish()
ELUOp F.elu()
SigmoidOp / TanhOp torch.sigmoid() / torch.tanh()
SoftmaxOp F.softmax()
SELUOp F.selu()
GLUOp F.glu()
BatchNormOp torch.nn.BatchNorm2d (inference)
LayerNormOp torch.nn.LayerNorm
GroupNormOp torch.nn.GroupNorm
InstanceNormOp torch.nn.InstanceNorm2d
RMSNormOp torch.nn.RMSNorm (PyTorch 2.4+)
TensorBuffer.full() torch.full()
TensorBuffer.random() torch.rand()
TensorBuffer.randn() torch.randn()
TensorBuffer.eye() torch.eye()
TensorBuffer.linspace() torch.linspace()
TensorBuffer.arange() torch.arange()
tensor.select(dim, index) tensor.select(dim, index)
tensor.narrow(dim, start, len) tensor.narrow(dim, start, len)
tensor.unbind(dim) tensor.unbind(dim)
tensor.flatten() tensor.flatten()
LpNormalizeOp F.normalize()
tensorWhere() / WhereOp torch.where()
MaskedFillOp Tensor.masked_fill_()
GatherOp torch.gather()
TopKOp / tensor.topk() torch.topk()
tensor.argmax() / tensor.argmin() tensor.argmax() / tensor.argmin()
ArgMaxOp / ArgMinOp torch.argmax() / torch.argmin()
split() / chunk() torch.split() / torch.chunk()
TileOp Tensor.repeat() / ONNX Tile
RepeatOp Tensor.repeat()
RollOp torch.roll()
RandomHorizontalFlipOp transforms.RandomHorizontalFlip()
RandomVerticalFlipOp transforms.RandomVerticalFlip()
RandomErasingOp transforms.RandomErasing()
ColorJitterOp transforms.ColorJitter()
PositionalEncodingOp Transformer positional encoding
RgbToGrayscaleOp torchvision.transforms.Grayscale() / tf.image.rgb_to_grayscale
RgbToHsvOp tf.image.rgb_to_hsv
HsvToRgbOp tf.image.hsv_to_rgb
ResizeNormalizeFusedOp F.interpolate() + transforms.Normalize() (fused)

Performance Benchmarks

Run benchmarks with dart run benchmark/run_all.dart.

SIMD Acceleration

Operations with Float32x4/Float64x2 SIMD vectorization:

Operation SIMD Throughput Speedup
ClipOp ~6.2 GE/s (Float32) ~4x
AbsOp ~6.2 GE/s (Float32) ~4x
SqrtOp ~6.2 GE/s (Float32) ~4x
NormalizeOp ~6.2 GE/s (Float32) ~4x
ReLUOp / LeakyReLUOp ~6.2 GE/s (Float32) ~4x
ScaleOp ~6.2 GE/s (Float32) ~4x
AddOp / SubOp / MulOp / DivOp ~6.2 GE/s (Float32) ~4x

GE/s = Giga Elements per second. Float64 SIMD achieves ~53% of Float32 performance due to Float64x2 vs Float32x4.

Operation Complexity

Operation Time Complexity Space Complexity
ResizeOp (bilinear) O(C × H × W) O(C × H × W)
ResizeOp (bicubic) O(C × H × W × 16) O(C × H × W)
ResizeOp (lanczos) O(C × H × W × 36) O(C × H × W)
NormalizeOp O(n) O(n) or O(1) in-place
BatchNormOp O(n) O(n) or O(1) in-place
LayerNormOp O(n) O(n) or O(1) in-place
GaussianBlurOp O(C × H × W × k) O(C × H × W)
ResizeNormalizeFusedOp O(C × H × W) O(C × H × W)

Zero-Copy Operations (O(1))

Operation Time Ops/sec
transpose() ~1µs 700K+
reshape() ~1µs 1.6M+
squeeze() <1µs 3.2M+
unsqueeze() ~1µs 780K+

Pipeline Performance

Pipeline Input Shape Time
Simple (Normalize + Unsqueeze) 3, 224, 224 ~3.4ms
ImageNet Classification 3, 224, 224 ~3.0ms
Object Detection 3, 640, 640 ~25ms

Sync vs Async

Execution 224x224 640x640
run() (sync) ~3.5ms ~29ms
runAsync() (isolate) ~11ms ~93ms
Isolate overhead ~7ms ~64ms

Note: Use runAsync() for large tensors or when UI responsiveness is critical.

CI

This project uses GitHub Actions for continuous integration:

Job Description Dart SDK
Static Analysis dart analyze --fatal-infos stable
Format Check dart format --set-exit-if-changed stable
Tests dart test 3.9.0, stable

Requirements

  • Dart SDK ^3.9.0

License

MIT

Libraries

dart_tensor_preprocessing
A high-performance tensor preprocessing library for Flutter/Dart.