Image Normalization Guide
4 minute read
When you pass PyTorch tensors or NumPy arrays to wandb.Image, the pixel values are automatically normalized to the range [0, 255] unless you set normalize=False. This guide explains how image normalization works and how to control it.
When normalization is applied
| Input Type | Format | Normalization Applied | Notes |
|---|---|---|---|
| PyTorch tensors | (channel, height, width) |
✅ Yes | Automatically normalized to [0, 255] range |
| NumPy arrays | (height, width, channel) |
✅ Yes | Automatically normalized to [0, 255] range |
| PIL Images | PIL Image object | ❌ No | Passed as-is without modification |
| File paths | String path to image file | ❌ No | Loaded as-is without modification |
Normalization algorithm
The normalization algorithm automatically detects the input range and applies the appropriate transformation:
-
If data is in range [0, 1]: Values are multiplied by 255 and converted to uint8
normalized_data = (data * 255).astype(np.uint8) -
If data is in range [-1, 1]: Values are rescaled to [0, 255] using:
normalized_data = (255 * 0.5 * (data + 1)).astype(np.uint8) -
For any other range: Values are clipped to [0, 255] and converted to uint8
normalized_data = data.clip(0, 255).astype(np.uint8)
Examples of normalization effects
Example 1: [0, 1] range data
import torch
import wandb
# Create tensor with values in [0, 1] range
tensor_0_1 = torch.rand(3, 64, 64) # Random values between 0 and 1
# This will multiply all values by 255
image = wandb.Image(tensor_0_1, caption="Normalized from [0,1] range")
Example 2: [-1, 1] range data
import torch
import wandb
# Create tensor with values in [-1, 1] range
tensor_neg1_1 = torch.rand(3, 64, 64) * 2 - 1 # Random values between -1 and 1
# This will rescale: -1 → 0, 0 → 127.5, 1 → 255
image = wandb.Image(tensor_neg1_1, caption="Normalized from [-1,1] range")
Note on visual contrast: The [-1, 1] normalization creates higher visual contrast compared to [0, 1] normalization. This is because:
- Negative values (like -0.8) become very dark (around 25)
- Positive values (like 0.8) become very bright (around 230)
- Values near 0 become mid-gray (127.5)
This “stretches” the visual range, making differences between pixel values more pronounced. This is particularly useful for highlighting subtle patterns in machine learning data, but if you want less contrast, consider preprocessing your data to a [0, 1] range before logging.
Example 3: Avoid normalization with PIL Images
Normalization is not applied to PIL Images.
import torch
from PIL import Image as PILImage
import wandb
# Create tensor with values in [0, 1] range
tensor_0_1 = torch.rand(3, 64, 64)
# Convert to PIL Image to avoid normalization
pil_image = PILImage.fromarray((tensor_0_1.permute(1, 2, 0).numpy() * 255).astype('uint8'))
image = wandb.Image(pil_image, caption="No normalization applied")
Example 4: Using normalize=False
To explicitly turn off image normalization without converting the image, set normalize=False.
import torch
import wandb
# Create tensor with values in [0, 1] range
tensor_0_1 = torch.rand(3, 64, 64)
# Disable normalization - values will be clipped to [0, 255]
image = wandb.Image(tensor_0_1, normalize=False, caption="Normalization disabled")
When to use different approaches
Use PIL conversion when:
- You want complete control over pixel values
- You need custom preprocessing (filters, brightness adjustments, etc.)
- You want to use PIL’s image processing capabilities
- You’re debugging and want to see exact values being logged
Use normalize=False when:
- You want to see raw tensor values as they are
- Your data is already in the correct range (like [0, 255] integers)
- You’re debugging normalization issues
- Quick testing without additional processing steps
Use automatic normalization when:
- You want consistent behavior across different input types
- Your data is in standard ranges ([0, 1] or [-1, 1])
- You want W&B to handle the conversion automatically
Troubleshooting
Best practices
- For consistent results: Pre-process your data to the expected [0, 255] range before logging
- To avoid normalization: Convert tensors to PIL Images using
PILImage.fromarray() - For debugging: Use
normalize=Falseto see the raw values (they will be clipped to [0, 255]) - For precise control: Use PIL Images when you need exact pixel values
- For highlighting subtle patterns: Use [-1, 1] normalization to increase visual contrast
- For natural-looking images: Use [0, 1] normalization or preprocess to [0, 255] range
- For custom processing: Use PIL conversion when you need to apply filters or adjustments
Common issues and solutions
- Unexpected brightness: If your tensor values are in [0, 1] range, they will be multiplied by 255, making the image much brighter. Solution: Preprocess to [0, 255] range or use PIL conversion.
- Data loss: Values outside the [0, 255] range will be clipped, potentially losing information. Solution: Check your data range and preprocess appropriately.
- Inconsistent behavior: Different input types (tensor vs PIL vs file path) may produce different results. Solution: Use consistent input types or understand the normalization behavior for each type.
Testing your code
You can test the normalization behavior using our Image Normalization Demo Notebook which demonstrates all the examples above with visual output.
Feedback
Was this page helpful?
Glad to hear it! If you have more to say, please let us know.
Sorry to hear that. Please tell us how we can improve.