# Image Processing Example
# Demonstrates convolution, padding, and shape operations on image-like data
# Run with: mix run examples/image_processing.exs
alias ExCubecl.Backend, as: B
IO.puts("=== Image Processing Operations ===")
IO.puts("")
# Create a small 5x5 "image" (single channel)
image = Nx.tensor([
[10, 20, 30, 40, 50],
[60, 70, 80, 90, 100],
[110, 120, 130, 140, 150],
[160, 170, 180, 190, 200],
[210, 220, 230, 240, 250]
], backend: B)
IO.puts("Original image (5×5):")
IO.puts(" Shape: #{inspect(Nx.shape(image))}")
IO.puts(" Values: #{inspect(Nx.to_flat_list(image))}")
IO.puts("")
# Add batch dimension: {1, 5, 5}
batched = Nx.reshape(image, {1, 5, 5})
IO.puts("Batched shape: #{inspect(Nx.shape(batched))}")
IO.puts("")
# --- Padding ---
IO.puts("--- Padding ---")
# Pad with 1 pixel on each side
padded = Nx.pad(image, 0.0, [{1, 1, 0}, {1, 1, 0}])
IO.puts("Padded shape: #{inspect(Nx.shape(padded))}")
IO.puts("Padded values:")
Nx.to_flat_list(padded)
|> Enum.chunk_every(7)
|> Enum.each(fn row ->
IO.puts(" #{inspect(row)}")
end)
IO.puts("")
# --- Edge Detection Kernel ---
IO.puts("--- Edge Detection (Sobel-like) ---")
# 3x3 kernel for horizontal edge detection
kernel = Nx.tensor([
[-1.0, 0.0, 1.0],
[-2.0, 0.0, 2.0],
[-1.0, 0.0, 1.0]
], backend: B)
IO.puts("Kernel shape: #{inspect(Nx.shape(kernel))}")
# Apply convolution
batched_padded = Nx.reshape(padded, {1, 7, 7})
result = Nx.conv(batched_padded, kernel)
IO.puts("Conv result shape: #{inspect(Nx.shape(result))}")
IO.puts("")
# --- Blur Kernel ---
IO.puts("--- Box Blur ---")
blur_kernel = Nx.tensor([
[1.0, 1.0, 1.0],
[1.0, 1.0, 1.0],
[1.0, 1.0, 1.0]
], backend: B)
blur_result = Nx.conv(batched_padded, blur_kernel)
IO.puts("Blur result shape: #{inspect(Nx.shape(blur_result))}")
IO.puts("")
# --- Window Operations (Pooling) ---
IO.puts("--- Max Pooling (2×2) ---")
small = Nx.tensor([
[1.0, 3.0, 2.0, 4.0],
[5.0, 6.0, 1.0, 2.0],
[3.0, 2.0, 7.0, 8.0],
[1.0, 0.0, 4.0, 3.0]
], backend: B)
IO.puts("Input (4×4):")
Nx.to_flat_list(small)
|> Enum.chunk_every(4)
|> Enum.each(fn row -> IO.puts(" #{inspect(row)}") end)
pooled = Nx.window_max(small, [2, 2], [])
IO.puts("Max pooled (2×2):")
Nx.to_flat_list(pooled)
|> Enum.chunk_every(2)
|> Enum.each(fn row -> IO.puts(" #{inspect(row)}") end)
IO.puts("")
# --- Average via Window Sum ---
IO.puts("--- Average Pooling (2×2) ---")
sum_pooled = Nx.window_sum(small, [2, 2], [])
# Divide by 4 to get average
avg_pooled = Nx.divide(sum_pooled, Nx.tensor(4.0))
IO.puts("Average pooled (2×2):")
Nx.to_flat_list(avg_pooled)
|> Enum.chunk_every(2)
|> Enum.each(fn row -> IO.puts(" #{inspect(row)}") end)
IO.puts("")
# --- Slice (Crop) ---
IO.puts("--- Image Cropping ---")
cropped = Nx.slice(image, [1, 1], [3, 3], [1, 1])
IO.puts("Cropped (3×3 from center):")
Nx.to_flat_list(cropped)
|> Enum.chunk_every(3)
|> Enum.each(fn row -> IO.puts(" #{inspect(row)}") end)
IO.puts("")
# --- Strided Slice (Downsample) ---
IO.puts("--- Strided Slice (Downsample 2x) ---")
downsampled = Nx.slice(image, [0, 0], [3, 3], [2, 2])
IO.puts("Downsampled (every other pixel):")
Nx.to_flat_list(downsampled)
|> Enum.chunk_every(3)
|> Enum.each(fn row -> IO.puts(" #{inspect(row)}") end)
IO.puts("")
IO.puts("=== Done ===")