nhdr_nrrd_write.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. % Write NHDR/NRRD files
  2. %
  3. % headerInfo = nhdr_nrrd_write(nrrdFileName, headerInfo, bWriteData)
  4. %
  5. % Inputs:
  6. % * nrrdFilename (char): path to NHDR/NRRD file(s), either a detached header,
  7. % a detached header pointing to detached data files or a nrrd standalone
  8. % depending on what is specified in headerInfo
  9. % * headerInfo (struct): structure containing all the required and optional
  10. % NRRD fields, such as produced by nhdr_nrrd_read.m, as well as the data.
  11. % List of accepted structure field names with corresponding NRRD fields in
  12. % parentheses:
  13. % - byteskip (byte skip: <desc>): scalar integer. For now, can only
  14. % accept -1 with raw encoding and 0 with all other encodings;
  15. % - content (content: <desc>): string;
  16. % - data (~): numerical array containing the raw data;
  17. % - datafiles (data file: <desc>): cell array of strings containing the
  18. % paths to detached data files relative to the header file. This can
  19. % be a simple string if there is only one detached data file;
  20. % - detached_header (~): Matlab structure containing all the fields
  21. % required to write valid detached NRRD files containing the data.
  22. % All detached NRRD data files are assumed to have the same header;
  23. % - dimension (dimension: <desc>): scalar integer;
  24. % - encoding (encoding: <desc>): string;
  25. % - kinds (kinds: <desc>): cell array of strings;
  26. % - lineskip (line skip: <desc>): scalar integer. For now, can only be
  27. % set to zero unless raw encoding is used and byte skip is set to -1
  28. % (which cancels the effect of line skip altogether);
  29. % - measurementframe (measurement frame: <desc>): matrix of size
  30. % [Ns, Ns], where Ns in the number of space dimensions;
  31. % - sizes (sizes: <desc>): matrix of size [1, d] or [d, 1] where d is the
  32. % dimension;
  33. % - space (space: <desc>): string;
  34. % - spacedimension (space dimension: <desc>): scalar integer;
  35. % - spacedirections (space directions: <desc>): cell array of strings
  36. % for code compatibility, e.g.
  37. % {'(1,0,0)' '(0,2,0.1)' 'none' '(0.1,0.1,1.1)'} for 3
  38. % world space dimensions;
  39. % - spacedirections_matrix (~): matrix of size [Ns, Ns] where Ns is the
  40. % number of space dimensions. Should match the string description
  41. % contained in spacedirections, if specified;
  42. % - spaceorigin (space origin: <desc>): matrix of size [1, Ns] or [Ns, 1]
  43. % where Ns is the number os space dimensions;
  44. % - spaceunits (space units: <desc>): cell array of strings;
  45. % - type (type: <desc>): string.
  46. %
  47. % - bvalue (bvalue:=<value>): floating-point scalar (nominal b-value);
  48. % - modality (modality:=<value>): string
  49. % - gradients (DWMRI_gradient_%04d:=<value>): matrix of size [Ng, 3]
  50. % where Ng is the number of gradients in a PGSE-type
  51. % diffusion-weighted MRI protocol. Note that the b-value associated
  52. % with each gradient [g_x, g_y, g_z] is computed as
  53. % b = b_nom*(g_x^2 + g_y^2 + g_z^2), where b_nom is the
  54. % nominal b-value, so the gradients must be scaled accordingly;
  55. % * bWriteData (bool): set to true to write the data specified in
  56. % headerInfo.data, set to false to just write the header file without the
  57. % data
  58. %
  59. % Outputs:
  60. % * headerInfo (struct): same structure as that passed as argument with
  61. % additional (potentially mandatory) fields if inferrable from the data,
  62. % such as 'dimension', 'sizes', etc.
  63. %
  64. % The function creates all the required NHDR/NRRD file(s).
  65. %
  66. % See nhdr_nrrd_read.m for more information.
  67. %
  68. %
  69. % % Format definition:
  70. % http://teem.sourceforge.net/nrrd/format.html
  71. %
  72. % Date: August 2018
  73. % Author: Gaetan Rensonnet
  74. function headerInfo = nhdr_nrrd_write(nrrdFileName, headerInfo, bWriteData)
  75. [path_main_header, ~, fext] = fileparts(nrrdFileName);
  76. if isfield(headerInfo, 'datafiles')
  77. assert(strcmpi(fext, '.nhdr'), sprintf('Invalid filename extension ''%s''. A detached nrrd header (headerInfo structure contains ''datafiles'' field) should have extension ''.nhdr''.\n',fext));
  78. else
  79. assert(strcmpi(fext, '.nrrd'), sprintf('Invalid filename extension ''%s''. A standalone nrrd file (no ''datafiles'' field in headerInfo structure) should have extension ''.nrrd''.\n',fext));
  80. end
  81. header_fnames = fieldnames(headerInfo); % cell array of strings
  82. fnames_parsed = false(1,length(header_fnames)); % flags field names from header successfully parsed
  83. % --- Checking availability of required information before opening files ---
  84. % Guess as much from the data as possible
  85. % Check required 'sizes' information
  86. if isfield(headerInfo, 'sizes')
  87. if isfield(headerInfo, 'data')
  88. assert(nrrd_size_check(size(headerInfo.data), headerInfo.sizes),...
  89. sprintf('Sizes mismatch: [%s] in headerInfo structure is not compatible with the size of the data array [%s].\n',...
  90. sprintf('%d ',headerInfo.sizes), sprintf('%d ',size(headerInfo.data)) ));
  91. end
  92. else
  93. assert( isfield(headerInfo, 'data'), sprintf('Missing required field ''sizes'' in headerInfo structure, cannot be deduced from data because no data was provided.\n'));
  94. headerInfo.sizes = size(headerInfo.data);
  95. end
  96. % Check required 'dimension' information
  97. if isfield(headerInfo, 'dimension')
  98. assert( isequal(headerInfo.dimension, length(headerInfo.sizes)), sprintf('Dimension mismatch: %d in headerInfo structure is not compatible with detected data size [%s].\n',...
  99. headerInfo.dimension, sprintf('%d ', headerInfo.sizes)));
  100. else
  101. headerInfo.dimension = length(headerInfo.sizes);
  102. end
  103. % Check required 'type' information
  104. if isfield(headerInfo, 'type')
  105. matlabdatatype = nrrd_getMatlabDataType(headerInfo.type);
  106. if isfield(headerInfo, 'data')
  107. assert( isequal(class(headerInfo.data), matlabdatatype), ...
  108. sprintf('Type mismatch: %s in headerInfo structure is not compatible with the Matlab type of the data array (%s).\nYou may want to look at nrrd_getMatlabDataType and at Matlab''s cast functions.',headerInfo.type, class(headerInfo.data)));
  109. end
  110. else
  111. assert( isfield(headerInfo, 'data'), sprintf('Missing required field ''type'' in headerInfo structure, cannot be deduced from data because no data was provided.\n'));
  112. headerInfo.type = get_nrrd_datatype(class(headerInfo.data));
  113. end
  114. % Check required 'encoding' information
  115. if ~isfield(headerInfo, 'encoding')
  116. headerInfo.encoding = 'raw';
  117. end
  118. % Set endianness to current platform's endianness
  119. [~,~,endian] = computer();
  120. if (isequal(endian, 'B'))
  121. headerInfo.endian = 'big';
  122. else
  123. headerInfo.endian = 'little';
  124. end
  125. fidw = fopen( nrrdFileName, 'w');
  126. if fidw == -1
  127. error('nhdr_nrrd_writer: could not open file %s.\n',nrrdFileName);
  128. end
  129. % if an error is thrown while parsing, close open file before exiting
  130. try
  131. % --- Traditional header and required fields ---
  132. fprintf( fidw, 'NRRD0005\n' );
  133. fprintf( fidw, '# Complete NRRD file format specification at:\n' );
  134. fprintf( fidw, '# http://teem.sourceforge.net/nrrd/format.html\n' );
  135. if isfield(headerInfo,'content')
  136. fprintf( fidw, 'content: %s\n', headerInfo.content); % always optional
  137. fnames_parsed(strcmpi('content', header_fnames)) = true;
  138. end
  139. fprintf( fidw, 'type: %s\n', headerInfo.type );
  140. fnames_parsed(strcmpi('type', header_fnames)) = true;
  141. fprintf( fidw, 'encoding: %s\n', headerInfo.encoding);
  142. fnames_parsed(strcmpi('encoding', header_fnames)) = true;
  143. fprintf( fidw, 'dimension: %d\n', headerInfo.dimension );
  144. fnames_parsed(strcmpi('dimension', header_fnames)) = true;
  145. fprintf( fidw, 'sizes: ' );
  146. for iI=1:length( headerInfo.sizes )
  147. fprintf( fidw, '%d', headerInfo.sizes(iI) );
  148. if ( iI~=length( headerInfo.sizes ) )
  149. fprintf( fidw, ' ' );
  150. end
  151. end
  152. fprintf( fidw, '\n' );
  153. fnames_parsed(strcmpi('sizes', header_fnames)) = true;
  154. fprintf( fidw, 'endian: %s\n', headerInfo.endian );
  155. fnames_parsed(strcmpi('endian', header_fnames)) = true;
  156. % ---- Defining orientation (always optional) -----
  157. define_orientation = 0;
  158. if isfield(headerInfo,'space')
  159. fprintf(fidw, 'space: %s\n', headerInfo.space );
  160. fnames_parsed(strcmpi('space', header_fnames)) = true;
  161. define_orientation = 1;
  162. num_sp_dim = nrrd_getSpaceDimensions(headerInfo.space);
  163. elseif isfield (headerInfo,'spacedimension')
  164. fprintf(fidw, 'space dimension: %d\n', headerInfo.spacedimension);
  165. fnames_parsed(strcmpi('spacedimension', header_fnames)) = true;
  166. define_orientation = 1;
  167. num_sp_dim = headerInfo.spacedimension;
  168. end
  169. % Space and space dimension fields cannot be specified simultaneously
  170. if isfield(headerInfo,'space') && isfield(headerInfo, 'spacedimension')
  171. error('The always optional fields ''space'' and ''spacedimension'' defining orientation cannot be specified simultaneously in headerInfo structure');
  172. end
  173. if define_orientation
  174. % So far the number of spatial dimensions should be 3 or 4
  175. % (this is important for writing space directions, space origin,
  176. % measurement frame)
  177. assert(num_sp_dim>=3, sprintf('Number of space dimensions should be at least 3 in current nrrd format, detected %d instead in headerInfo structure.\n',num_sp_dim));
  178. % check for necessary field 'space direction' when orientation defined
  179. if ~isfield(headerInfo, 'spacedirections')
  180. error('Missing field in headerInfo structure: spacedirections should always be specified if orientation is defined via ''space'' or ''space directions'' ');
  181. end
  182. assert(iscell(headerInfo.spacedirections), ...
  183. ['spacedirections field in headerInfo should be a cell array of ' ...
  184. 'strings for code compatibility, e.g. {''(1,0,0)'' ''(0,2,0.1)'' ''none'' ''(0.1,0.1,1.1)''}' ...
  185. ' for 3 world space dimensions.'])
  186. % Check format and clean up when possible
  187. for i = 1:length(headerInfo.spacedirections)
  188. none_match = regexp(headerInfo.spacedirections{i}, 'none', 'match');
  189. if ~isempty(none_match)
  190. % none for non space axis
  191. if ~strcmpi(headerInfo.spacedirections{i}, 'none')
  192. fprintf(['nhdr_nrrd_write WARNING: detected %s instead of ' ...
  193. 'expected none in header.spacedirections{%d}.\n Wrote clean version to output file(s).\n'],...
  194. headerInfo.spacedirections{i}, i)
  195. end
  196. headerInfo.spacedirections{i} = 'none';
  197. else
  198. % numerical vector for space (world) axis
  199. btw_parentheses = headerInfo.spacedirections{i}(1) == '(' ...
  200. && headerInfo.spacedirections{i}(end) == ')';
  201. if ~btw_parentheses
  202. fprintf(['nhdr_nrrd_write WARNING: space vector in header.spacedirections{%d} ' ...
  203. 'should contain comma-separated values enclosed in single parentheses.\n ' ...
  204. 'At least one parenthesis missing here. Wrote clean version to output file(s).\n'],...
  205. i)
  206. end
  207. headerInfo.spacedirections{i} = ['(' regexprep(headerInfo.spacedirections{i}, '[()]', ''), ')'];
  208. end
  209. end
  210. spacedir_str = strjoin(headerInfo.spacedirections, ' ');
  211. % Write to file
  212. fprintf(fidw, 'space directions: %s\n', spacedir_str);
  213. % Flag as parsed
  214. fnames_parsed(strcmpi('spacedirections', header_fnames)) = true;
  215. % Data defining space direction matrix
  216. SD_data = strrep(spacedir_str, 'none', '');
  217. SD_data = extractNumbersWithout(SD_data, {'(',')',',', '"', ''''}); % this fails if non numerical entries were not previously removed
  218. assert(length(SD_data) == num_sp_dim^2, sprintf('expected spacedirections field to contain %d numbers (square of %d, the world space dimension). Found %d instead.\nspacedirections should be a cell array of strings containing vectors of the form (dx,dy,dz) or none entries.\n',...
  219. num_sp_dim^2, num_sp_dim, length(SD_data)))
  220. % Check spacedirections_matrix field (internal, not a NRRD field)
  221. if isfield(headerInfo, 'spacedirections_matrix')
  222. assert(isequal(headerInfo.spacedirections_matrix(:), SD_data(:)), ...
  223. sprintf('Numeric data in spacedirections and spacedirections_matrix fields of headerInfo do not match. They should contain %d identical numbers in the same order.\n', num_sp_dim^2))
  224. else
  225. % add it to output headerInfo structure
  226. headerInfo.spacedirections_matrix = reshape(SD_data(:), [num_sp_dim, num_sp_dim]);
  227. end
  228. fnames_parsed(strcmpi('spacedirections_matrix', header_fnames)) = true;
  229. end
  230. % --- optional options for the optional definition of orientation ---
  231. % space origin
  232. if isfield(headerInfo,'spaceorigin')
  233. if define_orientation
  234. so = headerInfo.spaceorigin;
  235. assert(length(so)==num_sp_dim, ...
  236. sprintf('Field ''spaceorigin'' in headerInfo structure expected vector with %d entries to match the defined orientation, detected %d entries instead.\n',num_sp_dim, length(so)));
  237. fprintf( fidw, 'space origin: (%f%s)\n', so(1), sprintf(',%f',so(2:end)) );
  238. fnames_parsed(strcmpi('spaceorigin', header_fnames)) = true;
  239. else
  240. error('Field ''spaceorigin'' in headerInfo structure cannot be specified if neither ''space'' nor ''spacedimension'' was specified.');
  241. end
  242. end
  243. % measurement frame
  244. if isfield(headerInfo,'measurementframe')
  245. if define_orientation
  246. mf = headerInfo.measurementframe;
  247. assert(size(mf,1)==num_sp_dim && size(mf,2)==num_sp_dim,...
  248. sprintf('Field ''measurementframe'' in headerInfo structure expected a %d-by-%d matrix to match the defined orientation, detected size [%s] instead.\n',...
  249. num_sp_dim, num_sp_dim, sprintf('%d ',size(mf))));
  250. fprintf( fidw, 'measurement frame:');
  251. for imf = 1:size(mf,2)
  252. fprintf(fidw,' (%f%s)', mf(1,imf), sprintf(',%f',mf(2:end,imf)));
  253. end
  254. fprintf(fidw, '\n');
  255. fnames_parsed(strcmpi('measurementframe', header_fnames)) = true;
  256. else
  257. error('Field ''measurementframe'' in headerInfo structure cannot be specified if neither ''space'' nor ''spacedimension'' were specified.');
  258. end
  259. end
  260. % spaceunits
  261. if isfield(headerInfo,'spaceunits')
  262. if define_orientation
  263. fprintf( fidw, 'space units: ' );
  264. for iI=1:length( headerInfo.spaceunits )
  265. fprintf( fidw, '\"%s\"', char(headerInfo.spaceunits(iI)) );
  266. if ( iI~=length( headerInfo.spaceunits ) )
  267. fprintf( fidw, ' ' );
  268. end
  269. end
  270. fprintf( fidw, '\n' );
  271. fnames_parsed(strcmpi('spaceunits', header_fnames)) = true;
  272. else
  273. error('Field ''spaceunits'' in headerInfo structure cannot be specified if neither ''space'' nor ''spacedimension'' were specified.');
  274. end
  275. end
  276. % --- end of orientation definition -----
  277. % --- Optional fields "<field>: <desc>" ----
  278. % kinds
  279. if isfield(headerInfo, 'kinds')
  280. assert(length(headerInfo.kinds)==headerInfo.dimension, sprintf('Detected %d kinds instead of expected nrrd dimension %d.\n',length(headerInfo.kinds), headerInfo.dimension));
  281. fprintf( fidw, 'kinds: ' );
  282. for iI=1:length( headerInfo.kinds )
  283. fprintf( fidw, '%s', headerInfo.kinds{iI} );
  284. if ( iI~=length( headerInfo.kinds ) )
  285. fprintf( fidw, ' ' );
  286. end
  287. end
  288. fprintf( fidw, '\n' );
  289. fnames_parsed(strcmpi('kinds', header_fnames)) = true;
  290. end
  291. % line skip
  292. if isfield(headerInfo, 'lineskip')
  293. assert(headerInfo.lineskip >= 0, sprintf('Field ''lineskip'' in headerInfo structure should be non-negative, detected %d.\n', headerInfo.lineskip));
  294. fprintf(fidw, 'lineskip: %d\n', headerInfo.lineskip);
  295. fnames_parsed(strcmpi('lineskip', header_fnames)) = true;
  296. end
  297. % byte skip
  298. if isfield(headerInfo, 'byteskip')
  299. assert(headerInfo.byteskip >= -1, sprintf('Field ''byteskip'' should be -1 or a non-negative integer, detected %d.\n', headerInfo.byteskip));
  300. if headerInfo.byteskip == -1
  301. assert(strcmpi(headerInfo.encoding, 'raw'), sprintf('byte skip value of -1 is only valid with raw encoding.\n'));
  302. end
  303. fprintf(fidw, 'byteskip: %d\n', headerInfo.byteskip);
  304. fnames_parsed(strcmpi('byteskip', header_fnames)) = true;
  305. end
  306. % --- "<key>:=<value>" pairs (always optional)
  307. % Modality
  308. if isfield(headerInfo, 'modality')
  309. fprintf( fidw, 'modality:=%s\n',headerInfo.modality);
  310. fnames_parsed( strcmpi('modality', header_fnames)) = true;
  311. end
  312. % b-value
  313. if isfield(headerInfo, 'bvalue')
  314. fprintf( fidw, 'DWMRI_b-value:=%9.8f\n', headerInfo.bvalue);
  315. fnames_parsed( strcmpi('bvalue', header_fnames)) = true;
  316. end
  317. % DW-MRI gradient
  318. if isfield(headerInfo, 'gradients')
  319. Ngrads = size(headerInfo.gradients, 1);
  320. for igrad = 1:Ngrads
  321. strprint = strcat('DWMRI_gradient_',sprintf('%04d',igrad-1)) ; % The 0 flag in the %04d format specifier requests leading zeros in the output and sets minimum width of the printed value to 4
  322. strprint = [strprint,':='] ;
  323. strprint = [strprint, sprintf('%9.8f ', headerInfo.gradients(igrad,:))] ;
  324. fprintf(fidw,[strprint '\n']) ;
  325. end
  326. fnames_parsed(strcmpi('gradients', header_fnames)) = true;
  327. % FIXME: make it more general than numbering limited to 4 digits?
  328. end
  329. % --- External datafiles (should be performed in LIST mode) ---
  330. if isfield(headerInfo, 'datafiles')
  331. % This is a detached header, not a standalone nrrd file
  332. % 'Convert' to cell array for code compatibility. This only works
  333. % with one filename and fails with a comma-separated list of
  334. % filenames for instance.
  335. if ischar(headerInfo.datafiles)
  336. headerInfo.datafiles = { headerInfo.datafiles};
  337. end
  338. if length(headerInfo.datafiles)==1
  339. % data file: <filename>
  340. fprintf(fidw, 'data file: %s\n',headerInfo.datafiles{1}); % path relative to detached header
  341. else
  342. % data file: LIST [<subdim>]
  343. % FIXME: add support for subdim argument instead of ignoring it
  344. fprintf(fidw, 'data file: LIST\n');
  345. for i = 1:length(headerInfo.datafiles)
  346. fprintf(fidw,'%s\n', headerInfo.datafiles{i});
  347. end
  348. end
  349. fnames_parsed(strcmpi('datafiles', header_fnames)) = true;
  350. end
  351. catch me
  352. fclose(fidw);
  353. rethrow(me);
  354. end
  355. %% Write data
  356. if bWriteData
  357. if isfield(headerInfo, 'datafiles')
  358. % This is a detached header, detached data files need to be
  359. % written to
  360. fclose(fidw); % close main file
  361. N_data_tot = prod(headerInfo.sizes);
  362. % Read data chunk by chunk from detached data files
  363. N_data_files = length(headerInfo.datafiles);
  364. assert(mod(N_data_tot, N_data_files)==0, sprintf('Number of detected data files (%d) does not divide total number of values contained in data %d obtained from prod(sizes=[%s]).\n',...
  365. N_data_files, N_data_tot, sprintf('%d ',headerInfo.sizes)));
  366. N_data_per_file = N_data_tot/N_data_files;
  367. for i = 1:N_data_files
  368. % Check type of detached data file
  369. [~,fname_data,ext_data] = fileparts(headerInfo.datafiles{i});
  370. data_ind = (i-1)*N_data_per_file+1:i*N_data_per_file; % indices of data to be written
  371. if strcmpi(ext_data,'.nhdr')
  372. error('datafile %di/%d: nhdr file should not be used as detached data file.\n',i,length(headerInfo.datafiles));
  373. elseif strcmpi(ext_data, '.nrrd')
  374. % Detached nrrd file
  375. assert(isfield(headerInfo, 'detached_header'), sprintf('Missing field ''detached_header'' in headerInfo structure.\nThis is only required for detached headers with data stored in detached data files of type nrrd.\nShould contain a Matlab structure describing the chunk of data stored in the detached nrrd file (the data field, if present, will be overwritten).\nIt is assumed that the header is identical for all detached nrrd data files.\n'));
  376. % we have to check for all i's because detached nrrd files may be
  377. % interspersed with detached .raw or .hex files
  378. info_detached_data = headerInfo.detached_header; % change headerInfo! put only part of the data, change dimensions, remove datalists
  379. fnames_parsed(strcmpi('detached_header', header_fnames)) = true;
  380. % Add data chunk to be written to new file
  381. info_detached_data.data = headerInfo.data(data_ind);
  382. info_detached_data.data = reshape(info_detached_data.data, info_detached_data.sizes); % for code compatibility
  383. % Write detached nrrd file using a recursive call
  384. bWrite_Detached_Data = true;
  385. tmp_struct = nhdr_nrrd_write(fullfile(path_main_header, [fname_data, ext_data]), info_detached_data, bWrite_Detached_Data); % recursive call
  386. else
  387. % e.g., detached .raw or .hex file
  388. fid_data = fopen( fullfile(path_main_header, [fname_data, ext_data]), 'w');
  389. if( fid_data < 1 )
  390. error('Detached data file number %d/%d (%s) could not be opened.\n', i, N_data_files, headerInfo.datafiles{i});
  391. end
  392. try
  393. outtype = nrrd_getMatlabDataType(headerInfo.type);
  394. writeData(fid_data, headerInfo.data(data_ind), outtype, headerInfo.encoding);
  395. fclose(fid_data);
  396. catch me_detached
  397. fclose(fid_data);
  398. rethrow(me_detached);
  399. end
  400. end
  401. end
  402. else
  403. % Standalone nrrd file with data included after header
  404. try
  405. % After the header, there is a single blank line containing zero
  406. % characters to separate it from data
  407. fprintf(fidw, '\n');
  408. outtype = nrrd_getMatlabDataType(headerInfo.type);
  409. writeData(fidw, headerInfo.data, outtype, headerInfo.encoding);
  410. fclose(fidw);
  411. catch me_data
  412. fclose(fidw);
  413. rethrow(me_data);
  414. end
  415. end
  416. else
  417. fclose(fidw);
  418. end
  419. % Issue warnings for unknown/unsupported fields (after reading data)
  420. for i = 1:length(header_fnames)
  421. if ~fnames_parsed(i) && ~strcmpi(header_fnames{i},'data')
  422. fprintf('nhdr_nrrd_write WARNING: could not parse and write field %s from headerInfo structure.\n', header_fnames{i});
  423. end
  424. end
  425. end
  426. % --- Auxiliary functions ------------
  427. % ========================================================================
  428. % Store in an array the list of numbers separated by the tokens listed in
  429. % the withoutTokens cell array
  430. % ========================================================================
  431. function iNrs = extractNumbersWithout( inputString, withoutTokens )
  432. auxStr = inputString;
  433. for iI=1:length( withoutTokens )
  434. auxStr = strrep( auxStr, withoutTokens{iI}, ' ' );
  435. end
  436. iNrs = sscanf( auxStr, '%f' );
  437. end
  438. % ========================================================================
  439. % Determine the nrrd datatype : from matlab datatype to outtype (NRRD)
  440. % ========================================================================
  441. function nrrddatatype = get_nrrd_datatype(matlab_metaType)
  442. % Determine the datatype
  443. switch (matlab_metaType)
  444. case {'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64',...
  445. 'uint64', 'double'}
  446. nrrddatatype = matlab_metaType;
  447. case {'single'}
  448. nrrddatatype = 'float';
  449. case {'block'}
  450. error('Data type ''block'' is currently not supported.\n');
  451. otherwise
  452. error('Unknown matlab datatype %s', matlab_metaType)
  453. end
  454. end
  455. % ========================================================================
  456. % writeData -->
  457. % fidIn is the open file we're overwriting
  458. % matrix - data that have to be written
  459. % datatype - type of data: int8, string, double...
  460. % encoding - raw, gzip, txt/ascii
  461. % ========================================================================
  462. function ok = writeData(fidIn, matrix, datatype, encoding)
  463. switch (encoding)
  464. case {'raw'}
  465. ok = fwrite(fidIn, matrix(:), datatype);
  466. case {'gzip', 'gz'}
  467. % Store in a raw file before compressing
  468. tmpBase = tempname(pwd);
  469. fidTmpRaw = fopen(tmpBase, 'w');
  470. assert(fidTmpRaw > 3, 'Could not open temporary file for GZIP compression');
  471. try
  472. fwrite(fidTmpRaw, matrix(:), datatype);
  473. fclose(fidTmpRaw);
  474. catch me
  475. fclose(fidTmpRaw);
  476. delete(tmpBase);
  477. rethrow(me);
  478. end
  479. % Now we gzip our raw file
  480. tmpFile = [tmpBase '.gz'];
  481. try
  482. gzip(tmpBase); % this actually creates tmpFile
  483. catch me
  484. delete(tmpBase);
  485. rethrow(me);
  486. end
  487. delete(tmpBase);
  488. % Finally, we put this info into our nrrd file (fidIn)
  489. fidTmpRaw = fopen(tmpFile, 'r'); % should this be in a try catch as well?
  490. assert(fidTmpRaw > 3, 'Could not open temporary file for writing to nrrd file during gzip compression.');
  491. try
  492. % tmp = fread(fidTmpRaw, Inf, [datatype '=>' datatype]); % precision argument : from datatype (source) to datatype, how about just byte by byte ?
  493. tmp = fread(fidTmpRaw, Inf, 'uint8=>uint8'); % this seems to be more robust
  494. fclose(fidTmpRaw) ;
  495. catch me
  496. fclose(fidTmpRaw);
  497. delete(tmpFile);
  498. rethrow(me);
  499. end
  500. % ok = fwrite (fidIn, tmp, datatype); % why not byte by byte here?
  501. ok = fwrite(fidIn, tmp, 'uint8'); % this seems to be more robust
  502. delete (tmpFile);
  503. case {'text', 'txt', 'ascii'}
  504. ok = fprintf(fidIn,'%u ', matrix(:)); % FIX: better with %g ?
  505. %ok = fprintf(fidIn,matrix(:), class(matrix));
  506. otherwise
  507. error('Unsupported encoding %s', encoding)
  508. end
  509. end
  510. % ========================================================================
  511. % Compare a Matlab-type size vector (no trailing ones, minimum length 2) to
  512. % the size vector specified in a nrrd file/header which may contain
  513. % trailing ones and may have only one dimension.
  514. % ========================================================================
  515. function sizesok = nrrd_size_check(matlab_size, nrrd_size)
  516. % Make row vectors
  517. matlab_size = matlab_size(:)';
  518. nrrd_size = nrrd_size(:)';
  519. % nrrd sizes may contain only one dimension
  520. if length(nrrd_size)==1
  521. sizesok = prod(matlab_size)==nrrd_size;
  522. elseif length(nrrd_size)>=2
  523. ind_max = find(nrrd_size~=1,1,'last'); % find last non-1 entry
  524. % ones are ok in the first two dimensions:
  525. if isempty(ind_max)
  526. ind_max = 2;
  527. else
  528. ind_max = max(2, ind_max); % does not work if ind_max is empty
  529. end
  530. % Compare matlab size to nrrd size without trailing ones after from
  531. % third entry on.
  532. sizesok = isequal(matlab_size, nrrd_size(1:ind_max));
  533. else
  534. error('Argument nrrd_size is an empty matrix');
  535. end
  536. end