matlab/matlab-normalize-image
> Normalize images to [0,1] using im2double with proper validation and edge-case detection. Use when reading images with imread and converting to double for processing, displaying images with imshow, normalizing for ML training, brightening/adjusting pixel values, or any imread→process→imwrite workflow. white, image appears all white, image appears all black, read and process images, batch normalize, brighten image, pixel value scaling.
npx skills add https://github.com/matlab/matlab-agentic-toolkit --skill matlab-normalize-image
Detect and prevent unexpected normalization results when converting images to [0,1]
using base MATLAB image I/O functions.
imread and converting to double for processingimshow (especially if result is all-white)imread → process → imwrite workflow involving type conversionimageDatastore with ReadFcn — handles normalization internallyFollow this pipeline for every image normalization task:
imfinfoBefore reading, check what you're dealing with:
info = imfinfo(filePath);
fprintf('ColorType: %s, BitDepth: %d, Class will be: ', info.ColorType, info.BitDepth);
What to look for:
| info.ColorType | Action |
|-----------------|--------|
| 'indexed' | Use [X, cmap] = imread(f) then ind2rgb(X, cmap) (core MATLAB) — single-output imread returns indices, not pixel colors |
| 'truecolor' | Standard RGB — proceed normally |
| 'grayscale' | Single channel — proceed normally |
| info.BitDepth per channel | Action |
|----------------------------|--------|
| 8 | Standard uint8, im2double divides by 255 |
| 16 | Standard uint16, im2double divides by 65535 |
| 12, 10, 14 | Bit-depth mismatch — data stored in uint16 container. im2double divides by 65535 but actual max is 2^BitDepth-1. Correct with double(img) / (2^BitDepth - 1). |
| 32, 64 | Likely float-class — im2double will be a no-op. Use rescale after reading. |
img = imread(filePath);
imgNorm = im2double(img);
Always check the result immediately after im2double — if correction is needed, apply it and explain to the user why:
maxVal = max(imgNorm(:));
minVal = min(imgNorm(:));
if maxVal > 1.0
imgNorm = rescale(imgNorm, 0, 1);
end
if maxVal < 0.1
info = imfinfo(filePath);
bitsPerChannel = info.BitDepth / size(imgNorm, 3);
if bitsPerChannel < 16
imgNorm = double(img) / (2^bitsPerChannel - 1);
end
end
This single validation step catches the majority of failures. If im2double produced values outside [0,1] (float-class no-op) or in a narrow sub-range (bit-depth mismatch), apply the correct normalization and report to the user which alternative path was taken and why.
For relative operations (brighten, contrast), work in [0,1] domain:
processed = imgNorm * factor;
processed = min(max(processed, 0), 1); % Explicit clamp
For absolute operations (subtract background count), stay in native domain:
img = imread(filePath);
result = double(img) - backgroundValue;
result = max(result, 0); % Prevent underflow
Domain choice rule:
if isfloat(imgNorm) && max(imgNorm(:)) > 1.0
imshow(rescale(imgNorm, 0, 1)); % Normalize for display (RGB and grayscale)
else
imshow(imgNorm); % Standard display for data in [0,1]
end
imshow expects double images in [0,1]. Values > 1.0 clip to white — the entire image appears white. Note: imshow(img, []) only auto-scales grayscale images; for RGB float data it is silently ignored. Use rescale before display instead.
For writing to integer formats (JPEG requires uint8, TIFF supports uint16):
if max(imgNorm(:)) > 1.0
imgNorm = rescale(imgNorm, 0, 1); % Ensure [0,1] before conversion
end
% With IPT: output = im2uint8(imgNorm);
% Without IPT: output = uint8(round(imgNorm * 255));
imwrite(imgNorm, outputPath); % Some formats accept double [0,1] directly (PNG, TIFF)
| Function | Purpose | Source | Behavior on float input |
|----------|---------|--------|------------------------|
| im2double | Convert to double [0,1] | Core MATLAB | No-op on double/single — passes values through unchanged |
| imfinfo | Read image metadata | Core MATLAB | Returns BitDepth, ColorType, size |
| imread | Read image data | Core MATLAB | Returns uint8, uint16, or double depending on file |
| imwrite | Write image data | Core MATLAB | Format-specific class requirements |
| imshow | Display image | Core MATLAB | Expects double in [0,1]; clips values > 1 to white. [] auto-range works only for grayscale, not RGB. |
| Mistake | Why It's Wrong | What To Do |
|---------|---------------|------------|
| im2double(img) on float-class image without validation | im2double is a no-op on double/single inputs — values pass through unchanged (e.g., [0, 946] stays [0, 946]) | Check max(result(:)) <= 1.0 after calling. If violated, apply rescale(img, 0, 1). |
| Bare imshow(img) on float data with values > 1 | imshow clips double values > 1.0 to white — entire image appears white | Use imshow(rescale(img, 0, 1)) for correct display. Note: imshow(img, []) only works for grayscale — it is silently ignored for RGB. |
| Hardcoded double(img) / 255 | Only works for uint8. Breaks silently on uint16 (divides by 255 instead of 65535) | Use im2double(img) — it handles class dispatch automatically |
| Reporting mean=240.9 as "normalized" | If mean exceeds 1.0, the data is NOT normalized — im2double was a no-op | Always validate range before reporting or using results |
| Normalizing AFTER processing | img * 1.2 then / max(img(:)) undoes the scaling — brighten has no visible effect | Normalize FIRST, then process, then validate |
| double(img) / double(intmax(class(img))) | Reinvents im2double but breaks on float input — intmax('double') = 1.8e308 | Use im2double for integer classes. It exists for this purpose. |
| Not checking imfinfo before imread | Indexed images return colormap indices, not pixel values. Alpha channels are silently lost with single-output imread. | Call imfinfo first. Check ColorType and BitDepth. |
| 12-bit image appears near-black after im2double | im2double divides uint16 by 65535 (container max), but 12-bit data only reaches 4095. Result max = 0.0625. | Detect via imfinfo BitDepth. Apply double(img) / (2^bitsPerChannel - 1) for full-range normalization. |
When im2double produces values > 1.0, apply correct normalization and inform the user:
img = imread(filePath);
imgNorm = im2double(img);
if max(imgNorm(:)) > 1.0
% im2double is a no-op on float-class inputs — apply rescale instead
imgNorm = rescale(imgNorm, 0, 1);
end
Alternative normalization approaches:
rescale(img, 0, 1) — core MATLAB (R2017b+, no toolbox needed)mat2gray(img) — requires Image Processing Toolboximg / max(img(:)) — manual scaling to [0,1]When 12-bit data in a 16-bit container produces near-black normalized results, correct and inform the user:
info = imfinfo(filePath);
img = imread(filePath);
imgNorm = im2double(img);
bitsPerChannel = info.BitDepth / size(img, 3);
if bitsPerChannel < 16 && isa(img, 'uint16')
% im2double divided by 65535 but actual range is 2^bitsPerChannel - 1
imgNorm = double(img) / (2^bitsPerChannel - 1);
end
img = imread(filePath);
if isfloat(img) && max(img(:)) > 1.0
imshow(rescale(img, 0, 1));
title(sprintf('%s — rescaled [%.0f, %.0f]', fileName, min(img(:)), max(img(:))));
else
imgDisp = im2double(img);
imshow(imgDisp);
title(fileName);
end
im2double — check max(result(:)) <= 1.0 and if violated, correct and explain to the user why an alternative path was takenimfinfo first when processing unknown or mixed-format imagesdouble(img) for normalization — it preserves raw values without scalingimshow(rescale(img, 0, 1)) for float data with values outside [0,1] — imshow(img, []) only works for grayscale, not RGB| Step | Error | Recovery |
|------|-------|----------|
| imfinfo | "Unable to determine file format from filename." — file has wrong or missing extension | Retry with explicit format argument: imfinfo(f, 'tif'). If still fails, report to the user: "The file format could not be determined. Verify the file extension matches the actual format, or run imformats to see the list of supported formats. The file cannot be processed." Skip the file. |
| imfinfo / imread | "Could not read IFD ... the file may be corrupt." or "Unable to open file" | File is corrupt or inaccessible. Report to the user: "The file appears corrupt or incomplete — pixel data cannot be recovered. Verify file integrity (check file size, re-download if from a remote source, or inspect with a hex editor)." Skip file, log path, continue batch processing. |
| imfinfo / imread | "Can't read URL ... Location must have read access and be opened through a working network connection." | Remote URL is unreachable or access is denied. Download the file locally first using websave(localPath, url), then pass the local path to imfinfo/imread. |
| imread | Warning: "Corrupt JPEG data: bad Huffman code" — imread succeeds but data may be partially invalid | Check lastwarn after imread. Report the warning to the user: the image was read but may contain visual artifacts from corrupt regions. Proceed with normalization but flag the result as potentially unreliable. |
| imfinfo | ColorType returns unexpected value (e.g., -1) due to missing TIFF tags | Metadata is incomplete. Do not use the ColorType lookup table. Fall back to class-based normalization: use class(img) and max(img(:)) to infer correct scaling. Report to the user that the file has missing metadata tags and caveat the result as potentially unreliable — MATLAB may have guessed the pixel interpretation incorrectly without the required tag. |
| imread | Returns valid array but imfinfo BitDepth or ColorType fields are missing | Metadata is incomplete. Fall back to class-based normalization: use class(img) and max(img(:)) to infer correct scaling instead of relying on BitDepth. |
| im2double → validate | max == min (constant-value image) — rescale produces all zeros | Normalization is undefined for constant data — (x - min) / (max - min) divides by zero. Report to the user: "Warning: Image has uniform intensity (all pixels = <value>). Normalization to [0,1] is undefined for constant data. The result will be all zeros." Explain that a meaningful [0,1] range requires at least two distinct pixel values. |
| im2double → validate | Bit-depth correction still produces values outside [0,1] | imfinfo BitDepth does not match actual data range (metadata mismatch). Fall back to rescale(img, 0, 1) which uses observed min/max regardless of metadata. |
See references/normalization-decision-tree.md for the complete decision tree covering all edge cases — consult when handling mixed-format batches, indexed images, or images with alpha channels.
----
Copyright 2026 The MathWorks, Inc.
----
Take matlab/matlab-normalize-image from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.