Pipelines and Integration

Sowilo operations are pure functions on Rune tensors. They compose naturally with |>, work in batches, and integrate with Kaun training loops.

Composing Operations

Chain operations with the pipe operator:

open Sowilo

let process img =
  img
  |> to_float
  |> to_grayscale
  |> gaussian_blur ~sigma:1.0
  |> threshold 0.5

let edges img =
  img
  |> to_float
  |> to_grayscale
  |> canny ~low:0.2 ~high:0.6

Since every operation takes a tensor and returns a tensor, you can define reusable pipeline functions and combine them:

open Sowilo

let preprocess img =
  img
  |> to_float
  |> resize ~height:256 ~width:256
  |> center_crop ~height:224 ~width:224

let enhance img =
  img
  |> adjust_contrast 1.2
  |> unsharp_mask ~sigma:1.0 ~amount:0.5

let full_pipeline img = img |> preprocess |> enhance

Batch Processing

All operations handle both single images [H; W; C] and batches [N; H; W; C]. Stack images into a batch, process in one call:

open Sowilo

let process_batch paths =
  (* Load and stack into [N; H; W; C] *)
  let images =
    List.map
      (fun p -> Nx_io.load_image p|> to_float)
      paths
  in
  let batch = Nx.stack ~axis:0 images in

  (* All operations broadcast over the batch dimension *)
  let processed =
    batch
    |> resize ~height:224 ~width:224
    |> to_grayscale
    |> gaussian_blur ~sigma:1.0
  in
  processed

Deep Learning Preprocessing

Prepare images for neural networks with standard preprocessing:

open Sowilo

(* ImageNet preprocessing *)
let imagenet_preprocess img =
  img
  |> to_float
  |> resize ~height:256 ~width:256
  |> center_crop ~height:224 ~width:224
  |> normalize
      ~mean:[0.485; 0.456; 0.406]
      ~std:[0.229; 0.224; 0.225]

Differentiable Augmentation

Since most sowilo operations are differentiable, you can use them as augmentations inside a training loop and gradients will flow through:

open Sowilo

(* Differentiable augmentation pipeline *)
let augment img =
  img
  |> adjust_brightness 1.1
  |> adjust_contrast 0.9
  |> adjust_saturation 1.1
  |> gaussian_blur ~sigma:0.3

(* Use in a loss function - gradients flow through augmentation *)
let loss params img label =
  let preprocessed = imagenet_preprocess (augment img) in
  let logits = model params preprocessed in
  cross_entropy logits label

(* Rune.grad differentiates through augment + preprocess + model *)

Operations that break the gradient (canny, median_blur) should not be used inside differentiable pipelines. All other operations -- blurs, color adjustments, geometric transforms, morphology, threshold, sobel, scharr, laplacian -- support Rune.grad.

Integration with Kaun

Use sowilo preprocessing in Kaun data pipelines:

open Sowilo
open Kaun

let preprocess img =
  img
  |> Sowilo.to_float
  |> Sowilo.resize ~height:224 ~width:224
  |> Sowilo.normalize
      ~mean:[0.485; 0.456; 0.406]
      ~std:[0.229; 0.224; 0.225]

let train_data =
  Data.prepare ~shuffle:rngs ~batch_size:32 (images, labels)
  |> Data.map (fun (x, y) ->
    (preprocess x, fun logits -> Loss.cross_entropy_sparse logits y))

Feature Extraction

Combine edge detection with morphological operations to extract features:

open Sowilo

let extract_features img =
  let gray = to_grayscale img in

  (* Edge features *)
  let gx, gy = sobel gray in
  let magnitude = Nx.sqrt (Nx.add (Nx.mul gx gx) (Nx.mul gy gy)) in

  (* Morphological features *)
  let kernel = structuring_element Rect (3, 3) in
  let gradient = morphological_gradient ~kernel gray in

  (* Stack as multi-channel feature map *)
  Nx.concatenate ~axis:(-1) [ gray; magnitude; gradient ]

Visualization

Display processing results side by side with Hugin:

open Sowilo

let visualize_pipeline img =
  let gray = to_grayscale img in
  let blurred = gaussian_blur ~sigma:2.0 gray in
  let edges = canny ~low:0.2 ~high:0.6 gray in

  let fig = Hugin.figure ~width:1200 ~height:400 () in

  let ax1 = Hugin.subplot ~nrows:1 ~ncols:3 ~index:1 fig in
  ignore
    (ax1
    |> Hugin.Plotting.imshow ~data:gray
         ~cmap:Hugin.Artist.Colormap.gray
    |> Hugin.Axes.set_title "Grayscale");

  let ax2 = Hugin.subplot ~nrows:1 ~ncols:3 ~index:2 fig in
  ignore
    (ax2
    |> Hugin.Plotting.imshow ~data:blurred
         ~cmap:Hugin.Artist.Colormap.gray
    |> Hugin.Axes.set_title "Gaussian Blur");

  let ax3 = Hugin.subplot ~nrows:1 ~ncols:3 ~index:3 fig in
  ignore
    (ax3
    |> Hugin.Plotting.imshow ~data:edges
         ~cmap:Hugin.Artist.Colormap.gray
    |> Hugin.Axes.set_title "Canny Edges");

  Hugin.show fig

Color Space Manipulation

Adjust colors through HSV for more precise control:

open Sowilo

(* Selective color manipulation via HSV *)
let make_warmer img =
  let hsv = rgb_to_hsv img in
  (* Shift hue slightly toward warm tones *)
  let adjusted = adjust_hue 0.02 img in
  (* Boost saturation *)
  adjust_saturation 1.2 adjusted

(* Grayscale with tint *)
let sepia img =
  img
  |> to_grayscale
  |> fun gray ->
     (* Expand back to 3 channels and tint *)
     let rgb = Nx.concatenate ~axis:(-1) [ gray; gray; gray ] in
     adjust_saturation 0.3 (adjust_hue 0.05 rgb)