niftiread niftiwrite cycle inappropriately changes image orientation
Show older comments
Hello,
I have neuroimaging files which I have exported from Slicer as NIFTI files.
The following code snippet should produce outputImage.nii as an exact copy of inputImage.nii, but it does not.
filename = 'inputImage.nii'
im = niftiread(filename);
info = niftiinfo(filename);
niftiwrite(im,'outputImage.nii',info)
When I load outputImage.nii in Slicer, it is shown in a different orientation and location as inputImage.nii. I have found Slicer to be extraordinarily reliable for importing and exporting medical imaging data properly, so I do not think there is any error in Slicer. Even if there were, MATLAB should not have introduced any changes between inputImage.nii and outputImage.nii, so there is still evidence of a problem in MATLAB's functions even if there were a problem with Slicer.
Answers (2)
Devineni Aslesha
on 23 Mar 2020
2 votes
Hi Randolph,
I have heard that the issue with niftiwrite function is known and the concerned parties may be investigating further.
2 Comments
Ignacio
on 14 Sep 2020
Hi Devineni, has there been an update on this?
Devineni Aslesha
on 15 Sep 2020
Hi Ignacio,
niftiwrite orientation issue might be solved in MATLAB R2020b.
Rafael
on 3 Jul 2026 at 14:01
0 votes
Hi,
I was also facing this problem when writing a NIfTI file using the niftiwrite function together with a header obtained from niftiinfo in MATLAB R2024b. The issue appears to be that the info.raw substructure (which contains vox_offset and other parameters) is not fully written to the new file.
A simple workaround is to edit the niftiwrite.m script (...\MATLAB\R2024b\toolbox\images\iptformats\niftiwrite.m) and add the following line of code at line 97:
params.Info = varargin{1}.raw;
However, I cannot guarantee that this solution will work for every NIfTI file. Please make backups of your files before modifying the MATLAB source code. Good luck! :)
Below is the complete updated version of niftiwrite.m:
++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++
function niftiwrite(V, filename, varargin)
%NIFTIWRITE Write images as NIfTI files.
% NIFTIWRITE(V, FILENAME) writes a '.nii' file using the image data from
% V. By default, NIFTIWRITE creates a 'combined' NIfTI file that contains
% both metadata and volumetric data, giving it the '.nii' file extension.
% NIFTIWRITE populates the file metadata using appropriate default values
% and volume properties like size and data type.
%
% NIFTIWRITE(V, FILENAME, METADATA) writes a '.nii' file using the image
% data from V and metadata from METADATA. NIFTIWRITE creates a 'combined'
% NIFTI file, giving it the file extension '.nii'. If the metadata does
% not match the image contents and size, NIFTIWRITE returns an error.
%
% NIFTIWRITE(____, Name, Value) writes a '.nii' file using options
% specified in the Name Value pairs, described below.
%
% Optional Name-Value parameters include:
%
% 'Combined' - true (default) or false. If true, NIFTIWRITE
% creates one file with the file extension '.nii'.
% If false, NIFTIWRITE creates two files. One file
% contains metadata and has the file extension
% '.hdr'. The other files contains the volumetric
% data and has the file extension '.img'.
% NIFTIWRITE uses the file name you specified in
% FILENAME for both files.
%
% 'Compressed' - false(default) or true. If true, NIFTIWRITE
% compresses the generated file (or files) using
% gzip encoding, giving the file the .gz file
% extension as well as the NIFTI file extension.
%
% 'Endian' - 'little' (default) or 'big'.
% Controls the endianness of the data NIFTIWRITE
% writes to the file.
%
% 'Version' - 'NIfTI1' or 'NIfTI2'. Specifies the NIfTI format
% the data is to be written in. Default value
% depends on the maximum value of the dimensions
% of the volumetric data. The default value is
% 'NIfTI1' unless the maximum size in any
% dimension is more than 32767.
%
% References:
% -----------
% [1] Cox, R.W., Ashburner, J., Breman, H., Fissell, K., Haselgrove, C.,
% Holmes, C.J., Lancaster, J.L., Rex, D.E., Smith, S.M., Woodward, J.B.
% and Strother, S.C., 2004. A (sort of) new image data format standard:
% Nifti-1. Neuroimage, 22, p.e1440.
%
% Example 1
% ---------
% % This example illustrates writing a median filtered volume to a .nii
% % file.
%
% % Load a NIfTI image using its .nii file name.
% V = niftiread('brain.nii');
%
% % Filter the image in 3D using a 3-by-3-by-3 median filter.
% V = medfilt3(V);
%
% % Visualize the volume
% volshow(V)
%
% % Write the image to a .nii file. This uses default header values.
% niftiwrite(V, 'outbrain.nii');
%
% Example 2
% ---------
% % This example illustrates modifying the header structure and re-saving
% % a .nii file.
%
% % Load a NIfTI image using its .nii file name.
% info = niftiinfo('brain.nii');
% V = niftiread(info);
%
% % Edit the description of the file.
% info.Description = 'Modified using MATLAB R2017b';
%
% % Write the image to a .nii file.
% niftiwrite(V, 'outbrain.nii', info);
%
% See also NIFTIINFO, NIFTIREAD.
% Copyright 2016-2023 The MathWorks, Inc.
matlab.images.internal.errorIfgpuArray(V, filename, varargin{:});
[V, path, filename, params, isCompressed] = parseInputs(V, filename, varargin{:});
if strcmp(params.Endian, 'little')
machineFmt = 'ieee-le';
else
machineFmt = 'ieee-be';
end
params.Info = varargin{1}.raw; % Here is the change
headerStruct = params.Info;
% Convert strings to chars for low-level write
for f=fieldnames(headerStruct)'
if isstring(headerStruct.(f{1}))
headerStruct.(f{1}) = convertStringsToChars(headerStruct.(f{1}));
end
end
if params.Combined
NV = images.internal.nifti.niftiImage(headerStruct);
fid = fopen([filename '.nii'], 'w', machineFmt);
% write header.
[fid, headerBytes] = NV.writeHeader(fid, machineFmt);
assert(headerBytes == 348||headerBytes == 540);
% Write empty data until vox_offset
skipBytes = double(headerStruct.vox_offset) - headerBytes;
fwrite(fid, zeros(1,skipBytes), 'uint8');
% write image data.
fid = NV.writeImage(V, fid, machineFmt);
fclose(fid);
if params.Compressed || isCompressed
gzip([filename '.nii'], path);
delete([filename '.nii']);
end
else
%headerStruct.vox_offset = 0; % pixels start from first byte.
NV = images.internal.nifti.niftiImage(headerStruct);
headerfid = fopen([filename '.hdr'], 'w', machineFmt);
% write header.
[headerfid, headerBytes] = NV.writeHeader(headerfid, machineFmt);
assert(headerBytes == 348||headerBytes == 540);
fclose(headerfid);
% write image data.
imagefid = fopen([filename '.img'], 'w', machineFmt);
imagefid = NV.writeImage(V, imagefid, machineFmt);
fclose(imagefid);
if params.Compressed
gzip([filename '.hdr'], path);
gzip([filename '.img'], path);
delete([filename '.hdr']);
delete([filename '.img']);
end
end
end
%--------------------------------------------------------------------------
% Input Parsing
%--------------------------------------------------------------------------
function [V, fPath, fName, params, isCompressed] = parseInputs(V, fName, varargin)
varargin = matlab.images.internal.stringToChar(varargin);
% V has to be numeric, and of specific data types.
if ~isnumeric(V)
error(message('images:nifti:volumeMustBeNumeric'))
end
% filename needs to be a string or a character vector.
fName = matlab.images.internal.stringToChar(fName);
if ~ischar(fName)
error(message('images:nifti:filenameMustBeStringOrChar'))
end
[fPath, filenameOnly, ext] = fileparts(fName);
if isequal(lower(ext), ".gz")
isCompressed = true;
[~,tmp, ext] = fileparts(filenameOnly);
if isequal(lower(ext), ".nii")
filenameOnly = tmp;
end
else
isCompressed = false;
end
fName = fullfile(fPath, filenameOnly);
% Parse the PV pairs
parser = inputParser;
parser.addParameter('Combined', true, @(x)canBeLogical(x));
parser.addParameter('Compressed', false, @(x)canBeLogical(x));
parser.addParameter('Endian', 'little', @(x)ischar(x));
parser.addParameter('Version', 'NIfTI1', @(x)validateVersion(x));
parser.addOptional('Info', [], @(x)validateHeader(V,x));
parser.parse(varargin{:});
params.Combined = parser.Results.Combined ~= 0;
params.Compressed = parser.Results.Compressed ~= 0;
params.Endian = validatestring(parser.Results.Endian, {'little', 'big'});
params.Version = validatestring(parser.Results.Version, {'NIfTI1', 'NIfTI2'});
% If Version is not specified but Info is present
if any(strcmp(parser.UsingDefaults,'Version'))&&~isempty(parser.Results.Info)
params.Version = parser.Results.Info.Version;
end
%Default value of Version for size greater than 32767 is 'NIfTI2'
if any(strcmp(parser.UsingDefaults,'Version'))
if any(size(V)>32767)
params.Version = 'NIfTI2';
end
end
if strcmp(params.Version,'NIfTI1')
if any(size(V)>32767)
error(message('images:nifti:sizeTooBigNIfTI1'));
end
end
if isempty(parser.Results.Info)
params.Info = images.internal.nifti.niftiImage.niftiDefaultHeader(...
V, params.Combined, params.Version);
else
params.Info = images.internal.nifti.niftiImage.toRawStruct(...
parser.Results.Info, params.Combined, params.Version);
end
end
function T = validateVersion(version)
if ~ischar(version)
error(message('images:nifti:invalidVersion'));
else
T = true;
end
end
function TF = canBeLogical(input)
if isnumeric(input) || islogical(input)
TF = true;
else
TF = false;
end
end
function isHeader = validateHeader(V, simpleStruct)
if isstruct(simpleStruct)
if ~(isequal(simpleStruct.Datatype, class(V)))
error(message('images:nifti:volumeClassMustMatchHeader'))
end
% Allow trailing singleton dimensions in info.
dimsWithSingletonExpansion = max(ndims(V), numel(simpleStruct.ImageSize));
if ~(isequal(simpleStruct.ImageSize, size(V, 1:dimsWithSingletonExpansion)))
error(message('images:nifti:volumeSizeMustMatchHeader'))
end
if ~(length(simpleStruct.Description) <= 80)
error(message('images:nifti:descriptionLessThan80'))
end
if isfield(simpleStruct, 'AuxiliaryFile') && ...
~(length(simpleStruct.AuxiliaryFile) <= 24)
error(message('images:nifti:auxiliaryFileLessThan24'))
end
if isfield(simpleStruct, 'IntentDescription') && ...
~(length(simpleStruct.IntentDescription) <= 16)
error(message('images:nifti:intentMustBeLessThan16'))
end
isHeader = true;
else
isHeader = false;
end
end
Categories
Find more on Read and Write Image Data from Files in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!