Vision
Toro.Vision provides composable image transforms, image I/O (backed by TorchVision), and bitmap-level preprocessing via SkiaSharp. Open the namespace to get started:
open Toro
open Toro.Vision
Image I/O
The Image module provides image loading and saving via TorchVision, plus SKBitmap-Tensor conversion via SkiaSharp.
Loading
// Load from file as [3, H, W] float32 in [0, 1]:
let img = Image.load "photo.jpg" Cpu
// Load from stream (uses SkiaSharp internally):
use stream = File.OpenRead("photo.png")
let img = Image.loadStream stream Cpu
Saving
// Save a [3, H, W] tensor as JPEG:
Image.save tensor "output.jpg" Jpeg 0
// Save as PNG:
Image.save tensor "output.png" Png 0
The quality parameter is reserved for future use.
Saving a Grid
Save a batch of images as a grid (wraps torchvision.utils.save_image):
// Save [N, C, H, W] batch as a grid with 8 images per row:
Image.saveGrid batch "grid.png" Png 0 8
Bitmap-Tensor Conversion
Convert between SkiaSharp's SKBitmap and Toro Tensor directly:
open SkiaSharp
// SKBitmap → Tensor [3, H, W] float32 [0, 1]:
let bitmap = SKBitmap.Decode("photo.jpg")
let tensor = Image.toTensor bitmap Cpu
// Tensor → SKBitmap:
let bmp = Image.fromTensor tensor
SkiaTransform (Bitmap-Level Pipeline)
The SkiaTransform module provides spatial transforms operating directly on SKBitmap.
Use this for CPU-side preprocessing before tensor conversion — SkiaSharp's rasterizer handles resize and crop without allocating GPU tensors.
open SkiaSharp
let bitmap = SKBitmap.Decode("photo.jpg")
let resized = SkiaTransform.resize 256 256 bitmap
let cropped = SkiaTransform.centerCrop 224 224 resized
let flipped = SkiaTransform.flipH cropped
let tensor = Image.toTensor flipped Cpu
Functions include resize, centerCrop, randomCrop, flipH, flipV, and pipeline.
See the SkiaTransform API reference for signatures.
Pipeline
Chain bitmap transforms and convert to tensor in one call. Intermediate bitmaps are disposed automatically:
let tensor =
SkiaTransform.pipeline
[ SkiaTransform.resize 256 256; SkiaTransform.flipH ]
bitmap
Cpu
When to Use SkiaTransform vs. ITransform
SkiaTransform operates on SKBitmap (CPU pixels) and is best for heavy spatial transforms before tensorization.
ITransform operates on Tensor (CPU or GPU) and is better for augmentations in the training loop.
Both approaches can be combined: apply bitmap transforms first, convert to tensor, then apply tensor transforms.
ITransform (Tensor Transforms)
All tensor transforms implement ITransform and expose a direct apply member:
type ITransform =
abstract apply: Tensor -> Tensor
Each transform record has a public member apply — you can call it directly without casting:
let norm = Normalize.imageNet
let result = norm.apply tensor // no cast needed
Input tensors are expected in [C, H, W] or [B, C, H, W] format with float32 values.
Available Transforms
Normalize
Channel-wise normalization: .
let norm = Normalize.imageNet
let out = norm.apply img
Resize
Resize spatial dimensions using bilinear interpolation:
let resize = Resize.create 224 224
let out = resize.apply img
RandomHorizontalFlip
Flip the image horizontally with a given probability:
let flip = RandomHorizontalFlip.create 0.5
let flip = RandomHorizontalFlip.defaultFlip
RandomVerticalFlip
Flip the image vertically with a given probability:
let flip = RandomVerticalFlip.create 0.5
let flip = RandomVerticalFlip.defaultFlip
RandomCrop
Randomly crop the image to the specified size:
let crop = RandomCrop.create 224 224
Throws if the input is smaller than the crop size.
CenterCrop
Deterministically crop the center region of the image:
let crop = CenterCrop.create 224 224
Throws if the input is smaller than the crop size.
ToGrayscale
Convert an RGB image to grayscale using ITU-R BT.601 luminance weights (). Output can be single-channel or three identical channels:
let gray = ToGrayscale.single
let gray = ToGrayscale.triple
Input must have exactly 3 channels.
ConvertImageDType
Convert image tensor dtype with automatic value scaling between integer [0, 255] and float [0.0, 1.0] ranges:
let toFloat = ConvertImageDType.create F32
let toInt = ConvertImageDType.create U8
Compose
Chain multiple transforms into a single pipeline with Compose.apply.
Items in the list must be typed as ITransform:
let img = Image.load "photo.jpg" Cpu
let transforms: ITransform list = [
Resize.create 224 224
RandomHorizontalFlip.defaultFlip
Normalize.imageNet
]
let processed = Compose.apply transforms img
Training vs. Inference Pipeline
let trainTransforms: ITransform list = [
Resize.create 256 256
RandomCrop.create 224 224
RandomHorizontalFlip.defaultFlip
RandomVerticalFlip.defaultFlip
Normalize.imageNet
]
let evalTransforms: ITransform list = [
Resize.create 256 256
CenterCrop.create 224 224
Normalize.imageNet
]
Full Example: Load, Transform, Classify
open Toro
open Toro.Vision
open SkiaSharp
// Bitmap preprocessing (CPU, no tensor allocation):
let bitmap = SKBitmap.Decode("cat.jpg")
let resized = SkiaTransform.resize 256 256 bitmap
// Convert to tensor and apply tensor transforms:
let tensor = Image.toTensor resized Cpu
let cropped = (CenterCrop.create 224 224).apply tensor
let normalized = Normalize.imageNet.apply cropped
// Feed to model...