niftiread niftiwrite cycle inappropriately changes image orientation

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)

Hi Randolph,
I have heard that the issue with niftiwrite function is known and the concerned parties may be investigating further.

2 Comments

Hi Devineni, has there been an update on this?
Hi Ignacio,
niftiwrite orientation issue might be solved in MATLAB R2020b.

Sign in to comment.

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

Products

Release

R2019a

Asked:

on 20 Mar 2020

Answered:

on 3 Jul 2026 at 14:01

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!