1 |
|
|
/* |
2 |
|
|
Teem: Tools to process and visualize scientific data and images . |
3 |
|
|
Copyright (C) 2015, 2014, 2013, 2012, 2011, 2010, 2009 University of Chicago |
4 |
|
|
Copyright (C) 2008, 2007, 2006, 2005 Gordon Kindlmann |
5 |
|
|
Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998 University of Utah |
6 |
|
|
|
7 |
|
|
This library is free software; you can redistribute it and/or |
8 |
|
|
modify it under the terms of the GNU Lesser General Public License |
9 |
|
|
(LGPL) as published by the Free Software Foundation; either |
10 |
|
|
version 2.1 of the License, or (at your option) any later version. |
11 |
|
|
The terms of redistributing and/or modifying this software also |
12 |
|
|
include exceptions to the LGPL that facilitate static linking. |
13 |
|
|
|
14 |
|
|
This library is distributed in the hope that it will be useful, |
15 |
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 |
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 |
|
|
Lesser General Public License for more details. |
18 |
|
|
|
19 |
|
|
You should have received a copy of the GNU Lesser General Public License |
20 |
|
|
along with this library; if not, write to Free Software Foundation, Inc., |
21 |
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 |
|
|
*/ |
23 |
|
|
|
24 |
|
|
#include "nrrd.h" |
25 |
|
|
#include "privateNrrd.h" |
26 |
|
|
|
27 |
|
|
/*** |
28 |
|
|
**** Info about string handling for fields |
29 |
|
|
|
30 |
|
|
The Nrrd format can include strings in different fields, with different |
31 |
|
|
rules for each one; see http://teem.sourceforge.net/nrrd/format.html Below |
32 |
|
|
is some documentation of how the strings are handled, which is mostly a |
33 |
|
|
documentation of old (long-established) code, the gathering of which |
34 |
|
|
uncovered some problems and led to some new code (as of Thu Aug 23 09:32:11 |
35 |
|
|
CDT 2012). Conceptual inconsistencies in the different handlings of strings |
36 |
|
|
merit further review, including updating the file format spec (e.g. what |
37 |
|
|
non-ASCII characters can be allowed where? Unicode? what encoding? etc). |
38 |
|
|
This all also highlights the need for having a completely uniform way of |
39 |
|
|
setting these fields at one-time via the nrrd library (e.g. can use |
40 |
|
|
nrrdAxisInfoSet for "units" but there is no API for setting "space units"). |
41 |
|
|
Should that API flag as error if you try to include characters that can't |
42 |
|
|
be losslessly saved, or should it silently transform things? |
43 |
|
|
|
44 |
|
|
** Comments: |
45 |
|
|
On disk, delimited by the NRRD_COMMENT_CHAR ('#') and the |
46 |
|
|
end of the line, but the format spec doesn't address any escaping. |
47 |
|
|
Input comments processed via: |
48 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_read() |
49 |
|
|
--> nrrd/parseNrrd.c/_nrrdReadNrrdParse_comment() |
50 |
|
|
--> nrrd/comment.c/nrrdCommentAdd() |
51 |
|
|
--> air/string.c/airOneLinify() |
52 |
|
|
On write, output comments processed via: |
53 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_write() |
54 |
|
|
--> air/string.c/airOneLinify() |
55 |
|
|
==> There is no escaping of anything: white-space is compressed into |
56 |
|
|
a single ' '. Probably justified justified given format spec. |
57 |
|
|
|
58 |
|
|
** Content: On disk, finished with the end of line. No mention |
59 |
|
|
of escaping in the format spec. Input content processed via: |
60 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_read() |
61 |
|
|
--> nrrd/parseNrrd.c/_nrrdReadNrrdParse_content() |
62 |
|
|
which does NO processing, just airStrdup |
63 |
|
|
On write, output content processed via: |
64 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_write() |
65 |
|
|
--> nrrd/write.c/_nrrdSprintFieldInfo() (maybe via _nrrdFprintFieldInfo()) |
66 |
|
|
--> air/string.c/airOneLinify() |
67 |
|
|
==> not only is there no escaping, but there's some assymmetry in the |
68 |
|
|
use of airOneLinify. If information is being encoded in the number of |
69 |
|
|
contiguous spaces in the content, its preserved on input but not on |
70 |
|
|
output. Still, there's no chance of writing a broken file. |
71 |
|
|
|
72 |
|
|
** key/value pairs: The keys and values are separated by ":=", and the |
73 |
|
|
format spec says (string) "\n" means (character) '\n' and "\\" means '\\'. |
74 |
|
|
On input, both keys and values processed via: |
75 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_read() |
76 |
|
|
--> nrrd/parseNrrd.c/_nrrdReadNrrdParse_keyvalue() |
77 |
|
|
--> air/string.c/airUnescape(), which deals with "\n" and "\\" ONLY |
78 |
|
|
(not quotes, not other whitespace), and then |
79 |
|
|
--> nrrd/keyvalue.c/nrrdKeyValueAdd(), |
80 |
|
|
which only does an airStrdup |
81 |
|
|
On output, keys and values processed via |
82 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_write() |
83 |
|
|
--> nrrd/keyvalue.c/_nrrdKeyValueWrite() |
84 |
|
|
--> nrrd/keyvalue.c/_nrrdWriteEscaped() |
85 |
|
|
which is invoked to escape \n and \, |
86 |
|
|
and (NOTE!) to convert all other whitespace to ' ' |
87 |
|
|
Aside from the file format spec, the nrrd *library* does not really have |
88 |
|
|
any strictures about the characters that are allowed at run-time in key/values |
89 |
|
|
(and indeed nrrdKeyValueAdd just does an airStrdup). But without |
90 |
|
|
converting or escaping, say, '\r', you'll generate a broken NRRD file, |
91 |
|
|
hence the new handling of converting other whitespace to ' '. |
92 |
|
|
|
93 |
|
|
** labels and units: A "-delimited string per axis. |
94 |
|
|
Format spec is very specific for labels, and implies units are the same: |
95 |
|
|
"Within each label, double quotes may be included by escaping them |
96 |
|
|
(\"), but no other form of escaping is supported". On input: |
97 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_read() |
98 |
|
|
--> nrrd/parseNrrd.c/_nrrdReadNrrdParse_labels() |
99 |
|
|
or _nrrdReadNrrdParse_units() |
100 |
|
|
--> nrrd/parseNrrd.c/_nrrdGetQuotedString() |
101 |
|
|
which does the work of unescaping \" |
102 |
|
|
On output: |
103 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_write() |
104 |
|
|
--> nrrd/write.c/_nrrdSprintFieldInfo() (maybe via _nrrdFprintFieldInfo()) |
105 |
|
|
--> nrrd/keyvalue.c/_nrrdWriteEscaped() |
106 |
|
|
which is invoked to escape ", |
107 |
|
|
and (NOTE!) to convert all other whitespace to ' ' |
108 |
|
|
Same concern above about characters that when written would generate a |
109 |
|
|
bad NRRD file, but which are not documented as escape-able in label or unit |
110 |
|
|
|
111 |
|
|
** space units: A "-delimited string per axis of *world-space* (NOT |
112 |
|
|
the same a per-axis field, like units). Format is sadly silent on issue of |
113 |
|
|
escaping for these; so we might as well treat them like labels & units |
114 |
|
|
units. On input: |
115 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_read() |
116 |
|
|
--> nrrd/parseNrrd.c/_nrrdReadNrrdParse_space_units |
117 |
|
|
--> nrrd/parseNrrd.c/_nrrdGetQuotedString() |
118 |
|
|
which does the work of unescaping \" |
119 |
|
|
On output: |
120 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_write() |
121 |
|
|
--> nrrd/write.c/_nrrdSprintFieldInfo() (maybe via _nrrdFprintFieldInfo()) |
122 |
|
|
--> nrrd/keyvalue.c/_nrrdWriteEscaped() |
123 |
|
|
which is invoked to escape ", |
124 |
|
|
and (NOTE!) to convert all other whitespace to ' ' |
125 |
|
|
|
126 |
|
|
** sample units: like content and comments, not a quoted string. On input: |
127 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_read() |
128 |
|
|
--> nrrd/parseNrrd.c/_nrrdReadNrrdParse_sample_units() |
129 |
|
|
which does nothing except a strdup |
130 |
|
|
On output: |
131 |
|
|
nrrd/formatNrrd.c/_nrrdFormatNRRD_write() |
132 |
|
|
--> nrrd/write.c/_nrrdSprintFieldInfo() (maybe via _nrrdFprintFieldInfo()) |
133 |
|
|
--> air/string.c/airOneLinify() |
134 |
|
|
|
135 |
|
|
**** |
136 |
|
|
***/ |
137 |
|
|
|
138 |
|
|
#define MAGIC "NRRD" |
139 |
|
|
#define MAGIC0 "NRRD00.01" |
140 |
|
|
#define MAGIC1 "NRRD0001" |
141 |
|
|
#define MAGIC2 "NRRD0002" |
142 |
|
|
#define MAGIC3 "NRRD0003" |
143 |
|
|
#define MAGIC4 "NRRD0004" |
144 |
|
|
#define MAGIC5 "NRRD0005" |
145 |
|
|
#define MAGIC6 "NRRD0006" |
146 |
|
|
|
147 |
|
|
const char * |
148 |
|
|
_nrrdFormatURLLine0 = "Complete NRRD file format specification at:"; |
149 |
|
|
const char * |
150 |
|
|
_nrrdFormatURLLine1 = "http://teem.sourceforge.net/nrrd/format.html"; |
151 |
|
|
|
152 |
|
|
void |
153 |
|
|
nrrdIoStateDataFileIterBegin(NrrdIoState *nio) { |
154 |
|
|
|
155 |
|
68 |
nio->dataFNIndex = 0; |
156 |
|
34 |
return; |
157 |
|
|
} |
158 |
|
|
|
159 |
|
|
/* this macro suggested by Bryan Worthen */ |
160 |
|
|
/* if str = '-', strcmp() is 0, && short circuits, return false |
161 |
|
|
** else str != '-' |
162 |
|
|
** if str[1] = ':', its probably a windows full path, != is 0, return false |
163 |
|
|
** else str[1] != ':' |
164 |
|
|
** if str[0] = '/', its a normal full path, return false |
165 |
|
|
*/ |
166 |
|
|
#define _NEED_PATH(str) (strcmp("-", (str)) \ |
167 |
|
|
&& ':' != (str)[1] \ |
168 |
|
|
&& '/' != (str)[0]) |
169 |
|
|
|
170 |
|
|
/* |
171 |
|
|
** this is responsible for the header-relative path processing |
172 |
|
|
** |
173 |
|
|
** NOTE: if the filename is "-", then because it does not start with '/', |
174 |
|
|
** it would normally be prefixed by nio->path, so it needs special handling |
175 |
|
|
** |
176 |
|
|
** NOTE: this should work okay with nio->headerStringRead, I think ... |
177 |
|
|
*/ |
178 |
|
|
int |
179 |
|
|
nrrdIoStateDataFileIterNext(FILE **fileP, NrrdIoState *nio, int reading) { |
180 |
|
|
static const char me[]="nrrdIoStateDataFileIterNext"; |
181 |
|
|
char *fname=NULL; |
182 |
|
|
int ii, needPath; |
183 |
|
|
unsigned int num, fi; |
184 |
|
|
size_t maxl; |
185 |
|
|
airArray *mop; |
186 |
|
|
|
187 |
|
136 |
mop = airMopNew(); |
188 |
|
68 |
airMopAdd(mop, (void*)fileP, (airMopper)airSetNull, airMopOnError); |
189 |
|
|
|
190 |
✗✓ |
68 |
if (!fileP) { |
191 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
192 |
|
|
airMopError(mop); return 1; |
193 |
|
|
} |
194 |
✗✓ |
68 |
if (!_nrrdDataFNNumber(nio)) { |
195 |
|
|
biffAddf(NRRD, "%s: there appear to be zero datafiles!", me); |
196 |
|
|
airMopError(mop); return 1; |
197 |
|
|
} |
198 |
|
|
|
199 |
✓✓ |
68 |
if (nio->dataFNIndex >= _nrrdDataFNNumber(nio)) { |
200 |
|
|
/* there is no next data file, but we don't make that an error |
201 |
|
|
(though as of Tue Oct 2 22:53:14 CDT 2012, GLK can't remember |
202 |
|
|
why this condition would ever occur) */ |
203 |
|
34 |
nio->dataFNIndex = _nrrdDataFNNumber(nio); |
204 |
|
34 |
airMopOkay(mop); |
205 |
|
34 |
*fileP = NULL; |
206 |
|
34 |
return 0; |
207 |
|
|
} |
208 |
|
|
|
209 |
|
|
/* HEY: some of this error checking is done far more often than needed */ |
210 |
✓✗✓✓
|
68 |
if (nio->dataFNFormat || nio->dataFNArr->len) { |
211 |
|
|
needPath = AIR_FALSE; |
212 |
|
|
maxl = 0; |
213 |
✗✓ |
6 |
if (nio->dataFNFormat) { |
214 |
|
|
needPath = _NEED_PATH(nio->dataFNFormat); |
215 |
|
|
/* assuming 10-digit integers is plenty big */ |
216 |
|
|
maxl = 10 + strlen(nio->dataFNFormat); |
217 |
|
|
} else { |
218 |
✓✓ |
24 |
for (fi=0; fi<nio->dataFNArr->len; fi++) { |
219 |
✓✗✓✗
|
24 |
needPath |= _NEED_PATH(nio->dataFN[fi]); |
220 |
✗✓ |
18 |
maxl = AIR_MAX(maxl, strlen(nio->dataFN[fi])); |
221 |
|
|
} |
222 |
|
|
} |
223 |
✓✗✗✓
|
12 |
if (needPath && !airStrlen(nio->path)) { |
224 |
|
|
biffAddf(NRRD, "%s: need nio->path for header-relative datafiles", me); |
225 |
|
|
airMopError(mop); return 1; |
226 |
|
|
} |
227 |
|
6 |
fname = (char*)malloc(airStrlen(nio->path) + strlen("/") + maxl + 1); |
228 |
✗✓ |
6 |
if (!fname) { |
229 |
|
|
biffAddf(NRRD, "%s: couldn't allocate filename buffer", me); |
230 |
|
|
airMopError(mop); return 1; |
231 |
|
|
} |
232 |
|
6 |
airMopAdd(mop, fname, airFree, airMopAlways); |
233 |
|
6 |
} |
234 |
|
|
|
235 |
✗✓ |
34 |
if (nio->dataFNFormat) { |
236 |
|
|
/* ---------------------------------------------------------- */ |
237 |
|
|
/* --------- base.%d <min> <max> <step> [<dim>] ------------- */ |
238 |
|
|
/* ---------------------------------------------------------- */ |
239 |
|
|
num = 0; |
240 |
|
|
for (ii = nio->dataFNMin; |
241 |
|
|
((nio->dataFNStep > 0 && ii <= nio->dataFNMax) |
242 |
|
|
|| (nio->dataFNStep < 0 && ii >= nio->dataFNMax)); |
243 |
|
|
ii += nio->dataFNStep) { |
244 |
|
|
if (num == nio->dataFNIndex) { |
245 |
|
|
break; |
246 |
|
|
} |
247 |
|
|
num += 1; |
248 |
|
|
} |
249 |
|
|
if (_NEED_PATH(nio->dataFNFormat)) { |
250 |
|
|
strcpy(fname, nio->path); |
251 |
|
|
strcat(fname, "/"); |
252 |
|
|
sprintf(fname + strlen(nio->path) + strlen("/"), nio->dataFNFormat, ii); |
253 |
|
|
} else { |
254 |
|
|
sprintf(fname, nio->dataFNFormat, ii); |
255 |
|
|
} |
256 |
✓✓ |
34 |
} else if (nio->dataFNArr->len) { |
257 |
|
|
/* ---------------------------------------------------------- */ |
258 |
|
|
/* ------------------- LIST or single ----------------------- */ |
259 |
|
|
/* ---------------------------------------------------------- */ |
260 |
✓✗✓✗ ✓✗ |
18 |
if (_NEED_PATH(nio->dataFN[nio->dataFNIndex])) { |
261 |
|
6 |
sprintf(fname, "%s/%s", nio->path, nio->dataFN[nio->dataFNIndex]); |
262 |
|
6 |
} else { |
263 |
|
|
strcpy(fname, nio->dataFN[nio->dataFNIndex]); |
264 |
|
|
} |
265 |
|
|
} |
266 |
|
|
/* else data file is attached */ |
267 |
|
|
|
268 |
✓✗✓✓
|
68 |
if (nio->dataFNFormat || nio->dataFNArr->len) { |
269 |
|
6 |
*fileP = airFopen(fname, reading ? stdin : stdout, reading ? "rb" : "wb"); |
270 |
✗✓ |
6 |
if (!(*fileP)) { |
271 |
|
|
biffAddf(NRRD, "%s: couldn't open \"%s\" (data file %u of %u) for %s", |
272 |
|
|
me, fname, nio->dataFNIndex+1, _nrrdDataFNNumber(nio), |
273 |
|
|
reading ? "reading" : "writing"); |
274 |
|
|
airMopError(mop); return 1; |
275 |
|
|
} |
276 |
|
|
} else { |
277 |
|
|
/* data file is attached */ |
278 |
✗✓ |
28 |
if (nio->headerStringRead) { |
279 |
|
|
/* except we were never reading from a file to begin with, but this |
280 |
|
|
isn't an error */ |
281 |
|
|
*fileP = NULL; |
282 |
|
|
} else { |
283 |
|
28 |
*fileP = nio->headerFile; |
284 |
|
|
} |
285 |
|
|
} |
286 |
|
|
|
287 |
|
34 |
nio->dataFNIndex++; |
288 |
|
34 |
airMopOkay(mop); |
289 |
|
34 |
return 0; |
290 |
|
68 |
} |
291 |
|
|
|
292 |
|
|
/* |
293 |
|
|
** we try to use the oldest format that will hold the nrrd |
294 |
|
|
*/ |
295 |
|
|
int |
296 |
|
|
_nrrdFormatNRRD_whichVersion(const Nrrd *nrrd, NrrdIoState *nio) { |
297 |
|
|
int ret; |
298 |
|
|
|
299 |
✗✓ |
33 |
if (nrrdEncodingZRL == nio->encoding |
300 |
✓✗ |
22 |
|| nrrdSpaceRightUp == nrrd->space |
301 |
✓✗ |
22 |
|| nrrdSpaceRightDown == nrrd->space) { |
302 |
|
|
ret = 6; |
303 |
✓✓ |
11 |
} else if (_nrrdFieldInteresting(nrrd, nio, nrrdField_measurement_frame)) { |
304 |
|
|
ret = 5; |
305 |
✗✓ |
13 |
} else if (_nrrdFieldInteresting(nrrd, nio, nrrdField_thicknesses) |
306 |
✓✗ |
6 |
|| _nrrdFieldInteresting(nrrd, nio, nrrdField_space) |
307 |
✓✗ |
6 |
|| _nrrdFieldInteresting(nrrd, nio, nrrdField_space_dimension) |
308 |
✓✓ |
5 |
|| _nrrdFieldInteresting(nrrd, nio, nrrdField_sample_units) |
309 |
✓✗✓✗
|
6 |
|| airStrlen(nio->dataFNFormat) || nio->dataFNArr->len > 1) { |
310 |
|
|
ret = 4; |
311 |
✓✓ |
3 |
} else if (_nrrdFieldInteresting(nrrd, nio, nrrdField_kinds)) { |
312 |
|
|
ret = 3; |
313 |
✗✓ |
2 |
} else if (nrrdKeyValueSize(nrrd)) { |
314 |
|
|
ret = 2; |
315 |
|
|
} else { |
316 |
|
|
ret = 1; |
317 |
|
|
} |
318 |
|
11 |
return ret; |
319 |
|
|
} |
320 |
|
|
|
321 |
|
|
static int |
322 |
|
|
_nrrdFormatNRRD_available(void) { |
323 |
|
|
|
324 |
|
48 |
return AIR_TRUE; |
325 |
|
|
} |
326 |
|
|
|
327 |
|
|
static int |
328 |
|
|
_nrrdFormatNRRD_nameLooksLike(const char *filename) { |
329 |
|
|
|
330 |
|
36 |
return (airEndsWith(filename, NRRD_EXT_NRRD) |
331 |
✓✓ |
25 |
|| airEndsWith(filename, NRRD_EXT_NHDR)); |
332 |
|
|
} |
333 |
|
|
|
334 |
|
|
static int |
335 |
|
|
_nrrdFormatNRRD_fitsInto(const Nrrd *nrrd, const NrrdEncoding *encoding, |
336 |
|
|
int useBiff) { |
337 |
|
|
static const char me[]="_nrrdFormatNRRD_fitsInto"; |
338 |
|
|
|
339 |
✗✓ |
22 |
if (!( nrrd && encoding )) { |
340 |
|
|
biffMaybeAddf(useBiff, NRRD, "%s: got NULL nrrd (%p) or encoding (%p)", |
341 |
|
|
me, AIR_CVOIDP(nrrd), AIR_CVOIDP(encoding)); |
342 |
|
|
return AIR_FALSE; |
343 |
|
|
} |
344 |
|
|
|
345 |
|
|
/* everything fits in a nrrd */ |
346 |
|
11 |
return AIR_TRUE; |
347 |
|
11 |
} |
348 |
|
|
|
349 |
|
|
static int |
350 |
|
|
_nrrdFormatNRRD_contentStartsLike(NrrdIoState *nio) { |
351 |
|
|
|
352 |
|
144 |
return (!strcmp(MAGIC0, nio->line) |
353 |
✓✗ |
96 |
|| !strcmp(MAGIC1, nio->line) |
354 |
✓✓ |
88 |
|| !strcmp(MAGIC2, nio->line) |
355 |
✓✗ |
80 |
|| !strcmp(MAGIC3, nio->line) |
356 |
✓✓ |
76 |
|| !strcmp(MAGIC4, nio->line) |
357 |
✓✓ |
66 |
|| !strcmp(MAGIC5, nio->line) |
358 |
✓✓ |
80 |
|| !strcmp(MAGIC6, nio->line) |
359 |
|
|
); |
360 |
|
|
} |
361 |
|
|
|
362 |
|
|
/* |
363 |
|
|
** _nrrdHeaderCheck() |
364 |
|
|
** |
365 |
|
|
** minimal consistency checks on relationship between fields of nrrd, |
366 |
|
|
** only to be used after the headers is parsed, and before the data is |
367 |
|
|
** read, to make sure that information required for reading data is in |
368 |
|
|
** fact known. |
369 |
|
|
** |
370 |
|
|
** NOTE: this is not the place to do the sort of checking done by |
371 |
|
|
** nrrdCheck(), because it includes I/O-specific stuff |
372 |
|
|
** |
373 |
|
|
*/ |
374 |
|
|
int |
375 |
|
|
_nrrdHeaderCheck(Nrrd *nrrd, NrrdIoState *nio, int checkSeen) { |
376 |
|
|
static const char me[]="_nrrdHeaderCheck"; |
377 |
|
|
int i; |
378 |
|
|
|
379 |
✓✗ |
46 |
if (checkSeen) { |
380 |
✓✓ |
1518 |
for (i=1; i<=NRRD_FIELD_MAX; i++) { |
381 |
✓✓✗✓
|
828 |
if (_nrrdFieldRequired[i] && !nio->seen[i]) { |
382 |
|
|
biffAddf(NRRD, "%s: didn't see required field: %s", |
383 |
|
|
me, airEnumStr(nrrdField, i)); |
384 |
|
|
return 1; |
385 |
|
|
} |
386 |
|
|
} |
387 |
|
|
} |
388 |
✗✓✗✗
|
23 |
if (nrrdTypeBlock == nrrd->type && !nrrd->blockSize) { |
389 |
|
|
biffAddf(NRRD, "%s: type is %s, but missing field: %s", me, |
390 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock), |
391 |
|
|
airEnumStr(nrrdField, nrrdField_block_size)); |
392 |
|
|
return 1; |
393 |
|
|
} |
394 |
✗✓ |
23 |
if (!nrrdElementSize(nrrd)) { |
395 |
|
|
biffAddf(NRRD, "%s: nrrd reports zero element size!", me); |
396 |
|
|
return 1; |
397 |
|
|
} |
398 |
|
|
/* _nrrdReadNrrdParse_sizes() checks axis[i].size, which completely |
399 |
|
|
determines the return of nrrdElementNumber() */ |
400 |
✗✗ |
23 |
if (airEndianUnknown == nio->endian |
401 |
✗✓ |
23 |
&& nio->encoding->endianMatters |
402 |
|
|
&& 1 != nrrdElementSize(nrrd)) { |
403 |
|
|
biffAddf(NRRD, "%s: type (%s) and encoding (%s) require %s info", me, |
404 |
|
|
airEnumStr(nrrdType, nrrd->type), |
405 |
|
|
nio->encoding->name, |
406 |
|
|
airEnumStr(nrrdField, nrrdField_endian)); |
407 |
|
|
return 1; |
408 |
|
|
} |
409 |
|
|
|
410 |
|
|
/* we don't really try to enforce consistency with the |
411 |
|
|
min/max/center/size information on each axis, other than the |
412 |
|
|
value checking done by the _nrrdReadNrrdParse_* functions, |
413 |
|
|
because we only really care that we know each axis size. Past |
414 |
|
|
that, if the user messes it up, its not really our problem ... */ |
415 |
|
|
|
416 |
|
23 |
return 0; |
417 |
|
23 |
} |
418 |
|
|
|
419 |
|
|
/* |
420 |
|
|
** NOTE: currently, this will read, without complaints or errors, |
421 |
|
|
** newer NRRD format features from older NRRD files (as indicated by |
422 |
|
|
** magic), such as key/value pairs from a NRRD0001 file, even though |
423 |
|
|
** strictly speaking these are violations of the format. |
424 |
|
|
** |
425 |
|
|
** NOTE: by giving a NULL "file", you can make this function basically |
426 |
|
|
** do the work of reading in datafiles, without any header parsing |
427 |
|
|
*/ |
428 |
|
|
static int |
429 |
|
|
_nrrdFormatNRRD_read(FILE *file, Nrrd *nrrd, NrrdIoState *nio) { |
430 |
|
|
static const char me[]="_nrrdFormatNRRD_read"; |
431 |
|
|
/* Dynamically allocated for space reasons. */ |
432 |
|
|
/* MWC: These strlen usages look really unsafe. */ |
433 |
|
|
int ret; |
434 |
|
46 |
unsigned int llen; |
435 |
|
|
size_t valsPerPiece; |
436 |
|
|
char *data; |
437 |
|
23 |
FILE *dataFile=NULL; |
438 |
|
|
|
439 |
|
|
/* record where the header is being read from for the sake of |
440 |
|
|
nrrdIoStateDataFileIterNext() */ |
441 |
|
23 |
nio->headerFile = file; |
442 |
|
|
|
443 |
|
|
/* GLK forgets the context in which file might be reasonably NULL |
444 |
|
|
but on Fri Sep 23 09:48:41 EDT 2005 this was "if (file) { ..." */ |
445 |
|
|
/* nio->headerStringRead is NULL whenever IO from string is not being done */ |
446 |
✗✓✗✗
|
23 |
if (file || nio->headerStringRead) { |
447 |
✗✓ |
23 |
if (!_nrrdFormatNRRD_contentStartsLike(nio)) { |
448 |
|
|
biffAddf(NRRD, "%s: this doesn't look like a %s file", me, |
449 |
|
|
nrrdFormatNRRD->name); |
450 |
|
|
return 1; |
451 |
|
|
} |
452 |
|
|
/* parse all the header lines */ |
453 |
|
|
do { |
454 |
|
266 |
nio->pos = 0; |
455 |
✗✓ |
266 |
if (_nrrdOneLine(&llen, nio, file)) { |
456 |
|
|
biffAddf(NRRD, "%s: trouble getting line of header", me); |
457 |
|
|
return 1; |
458 |
|
|
} |
459 |
✓✓ |
266 |
if (llen > 1) { |
460 |
|
243 |
ret = _nrrdReadNrrdParseField(nio, AIR_TRUE); |
461 |
✗✓ |
243 |
if (!ret) { |
462 |
|
|
biffAddf(NRRD, "%s: trouble parsing NRRD field identifier from " |
463 |
|
|
"in \"%s\"", me, nio->line); |
464 |
|
|
return 1; |
465 |
|
|
} |
466 |
|
|
/* comments and key/values are allowed multiple times */ |
467 |
|
243 |
if (nio->seen[ret] |
468 |
✗✓ |
243 |
&& !(ret == nrrdField_comment || ret == nrrdField_keyvalue)) { |
469 |
|
|
biffAddf(NRRD, "%s: already set field %s", me, |
470 |
|
|
airEnumStr(nrrdField, ret)); |
471 |
|
|
return 1; |
472 |
|
|
} |
473 |
✗✓ |
243 |
if (nrrdFieldInfoParse[ret](file, nrrd, nio, AIR_TRUE)) { |
474 |
|
|
biffAddf(NRRD, "%s: trouble parsing %s info |%s|", me, |
475 |
|
|
airEnumStr(nrrdField, ret), nio->line + nio->pos); |
476 |
|
|
return 1; |
477 |
|
|
} |
478 |
|
243 |
nio->seen[ret] = AIR_TRUE; |
479 |
|
243 |
} |
480 |
✓✓ |
266 |
} while (llen > 1); |
481 |
|
|
/* either |
482 |
|
|
0 == llen: we're at EOF (or end of nio->headerStringRead), or |
483 |
|
|
1 == llen: we just read the empty line separating header from data */ |
484 |
✗✓ |
29 |
if (0 == llen |
485 |
✓✓ |
29 |
&& !nio->headerStringRead |
486 |
✓✗ |
12 |
&& !nio->dataFNFormat |
487 |
✓✗ |
12 |
&& 0 == nio->dataFNArr->len) { |
488 |
|
|
/* we're at EOF, we're not reading from a string, but there's |
489 |
|
|
apparently no separate data file */ |
490 |
|
|
biffAddf(NRRD, "%s: hit end of header, but no \"%s\" given", me, |
491 |
|
|
airEnumStr(nrrdField, nrrdField_data_file)); |
492 |
|
|
return 1; |
493 |
|
|
} |
494 |
|
|
} |
495 |
✗✓ |
23 |
if (_nrrdHeaderCheck(nrrd, nio, !!file)) { |
496 |
|
|
biffAddf(NRRD, "%s: %s", me, |
497 |
|
|
(llen ? "finished reading header, but there were problems" |
498 |
|
|
: "hit EOF before seeing a complete valid header")); |
499 |
|
|
return 1; |
500 |
|
|
} |
501 |
|
|
|
502 |
|
|
/* we seemed to have read in a valid header; now allocate the memory. |
503 |
|
|
For directIO-compatible allocation we need to get the first datafile */ |
504 |
|
23 |
nrrdIoStateDataFileIterBegin(nio); |
505 |
|
|
/* NOTE: if nio->headerStringRead, this may set dataFile to NULL */ |
506 |
✗✓ |
23 |
if (nrrdIoStateDataFileIterNext(&dataFile, nio, AIR_TRUE)) { |
507 |
|
|
biffAddf(NRRD, "%s: couldn't open the first datafile", me); |
508 |
|
|
return 1; |
509 |
|
|
} |
510 |
✗✓ |
23 |
if (nio->skipData) { |
511 |
|
|
nrrd->data = NULL; |
512 |
|
|
data = NULL; |
513 |
|
|
} else { |
514 |
✗✓ |
23 |
if (_nrrdCalloc(nrrd, nio, dataFile)) { |
515 |
|
|
biffAddf(NRRD, "%s: couldn't allocate memory for data", me); |
516 |
|
|
return 1; |
517 |
|
|
} |
518 |
|
23 |
data = (char*)nrrd->data; |
519 |
|
|
} |
520 |
|
|
|
521 |
|
|
/* iterate through datafiles and read them in */ |
522 |
|
|
/* NOTE: you have to open dataFile even in the case of skipData, because |
523 |
|
|
caller might have set keepNrrdDataFileOpen, in which case you need to |
524 |
|
|
do any line or byte skipping if it is specified */ |
525 |
|
23 |
valsPerPiece = nrrdElementNumber(nrrd)/_nrrdDataFNNumber(nio); |
526 |
✓✓ |
69 |
while (dataFile) { |
527 |
|
|
/* ---------------- skip, if need be */ |
528 |
✗✓ |
23 |
if (nrrdLineSkip(dataFile, nio)) { |
529 |
|
|
biffAddf(NRRD, "%s: couldn't skip lines", me); |
530 |
|
|
return 1; |
531 |
|
|
} |
532 |
✓✗ |
23 |
if (!nio->encoding->isCompression) { |
533 |
|
|
/* bytes are skipped here for non-compression encodings, but are |
534 |
|
|
skipped within the decompressed stream for compression encodings */ |
535 |
✗✓ |
23 |
if (nio->dataFSkip) { |
536 |
|
|
/* this error checking is clearly done unnecessarily repeated, |
537 |
|
|
but it was logically the simplest place to add it */ |
538 |
|
|
if (nio->byteSkip) { |
539 |
|
|
biffAddf(NRRD, "%s: using per-list-line skip, " |
540 |
|
|
"but also set global byte skip %ld", me, nio->byteSkip); |
541 |
|
|
return 1; |
542 |
|
|
} |
543 |
|
|
/* wow, the meaning of nio->dataFNIndex is a little confusing */ |
544 |
|
|
if (_nrrdByteSkipSkip(dataFile, nrrd, nio, nio->dataFSkip[nio->dataFNIndex-1])) { |
545 |
|
|
biffAddf(NRRD, "%s: couldn't skip %ld bytes on for list line %u", |
546 |
|
|
me, nio->dataFSkip[nio->dataFNIndex-1], nio->dataFNIndex-1); |
547 |
|
|
return 1; |
548 |
|
|
} |
549 |
|
|
} else { |
550 |
✗✓ |
23 |
if (nrrdByteSkip(dataFile, nrrd, nio)) { |
551 |
|
|
biffAddf(NRRD, "%s: couldn't skip bytes", me); |
552 |
|
|
return 1; |
553 |
|
|
} |
554 |
|
|
} |
555 |
|
|
} |
556 |
|
|
/* ---------------- read the data itself */ |
557 |
✗✓ |
23 |
if (2 <= nrrdStateVerboseIO) { |
558 |
|
|
fprintf(stderr, "(%s: reading %s data ... ", me, nio->encoding->name); |
559 |
|
|
fflush(stderr); |
560 |
|
|
} |
561 |
✓✗ |
23 |
if (!nio->skipData) { |
562 |
✗✓ |
23 |
if (nio->encoding->read(dataFile, data, valsPerPiece, nrrd, nio)) { |
563 |
|
|
if (2 <= nrrdStateVerboseIO) { |
564 |
|
|
fprintf(stderr, "error!\n"); |
565 |
|
|
} |
566 |
|
|
biffAddf(NRRD, "%s:", me); |
567 |
|
|
return 1; |
568 |
|
|
} |
569 |
|
|
} |
570 |
✗✓ |
23 |
if (2 <= nrrdStateVerboseIO) { |
571 |
|
|
fprintf(stderr, "done)\n"); |
572 |
|
|
} |
573 |
|
|
/* ---------------- go to next data file */ |
574 |
✗✓✗✗
|
23 |
if (nio->keepNrrdDataFileOpen && _nrrdDataFNNumber(nio) == 1) { |
575 |
|
|
nio->dataFile = dataFile; |
576 |
|
|
} else { |
577 |
✓✓ |
23 |
if (dataFile != nio->headerFile) { |
578 |
|
6 |
dataFile = airFclose(dataFile); |
579 |
|
6 |
} |
580 |
|
|
} |
581 |
|
23 |
data += valsPerPiece*nrrdElementSize(nrrd); |
582 |
✓✗ |
23 |
if (nrrdIoStateDataFileIterNext(&dataFile, nio, AIR_TRUE)) { |
583 |
|
|
biffAddf(NRRD, "%s: couldn't get the next datafile", me); |
584 |
|
|
return 1; |
585 |
|
|
} |
586 |
|
|
} |
587 |
|
|
|
588 |
✓✗✓✗
|
46 |
if (airEndianUnknown != nio->endian && nrrd->data) { |
589 |
|
|
/* we positively know the endianness of data just read */ |
590 |
✗✓ |
46 |
if (1 < nrrdElementSize(nrrd) |
591 |
✓✗ |
46 |
&& nio->encoding->endianMatters |
592 |
✓✗ |
46 |
&& nio->endian != airMyEndian()) { |
593 |
|
|
/* endianness exposed in encoding, and its wrong */ |
594 |
|
|
if (2 <= nrrdStateVerboseIO) { |
595 |
|
|
fprintf(stderr, "(%s: fixing endianness ... ", me); |
596 |
|
|
fflush(stderr); |
597 |
|
|
} |
598 |
|
|
nrrdSwapEndian(nrrd); |
599 |
|
|
if (2 <= nrrdStateVerboseIO) { |
600 |
|
|
fprintf(stderr, "done)\n"); |
601 |
|
|
fflush(stderr); |
602 |
|
|
} |
603 |
|
|
} |
604 |
|
|
} |
605 |
|
|
|
606 |
|
23 |
return 0; |
607 |
|
23 |
} |
608 |
|
|
|
609 |
|
|
static int |
610 |
|
|
_nrrdFormatNRRD_write(FILE *file, const Nrrd *nrrd, NrrdIoState *nio) { |
611 |
|
|
static const char me[]="_nrrdFormatNRRD_write"; |
612 |
|
22 |
char strbuf[AIR_STRLEN_MED], *strptr, *tmp; |
613 |
|
|
int ii; |
614 |
|
|
unsigned int jj; |
615 |
|
|
airArray *mop; |
616 |
|
11 |
FILE *dataFile=NULL; |
617 |
|
|
size_t valsPerPiece; |
618 |
|
|
char *data; |
619 |
|
|
|
620 |
|
11 |
mop = airMopNew(); |
621 |
|
|
|
622 |
✗✗ |
11 |
if (!(file |
623 |
✗✓ |
11 |
|| nio->headerStringWrite |
624 |
|
|
|| nio->learningHeaderStrlen)) { |
625 |
|
|
biffAddf(NRRD, "%s: have no file or string to write to, nor are " |
626 |
|
|
"learning header string length", me); |
627 |
|
|
airMopError(mop); return 1; |
628 |
|
|
} |
629 |
✗✓✗✗
|
11 |
if (nrrdTypeBlock == nrrd->type && nrrdEncodingAscii == nio->encoding) { |
630 |
|
|
biffAddf(NRRD, "%s: can't write nrrd type %s with %s encoding", me, |
631 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock), |
632 |
|
|
nrrdEncodingAscii->name); |
633 |
|
|
airMopError(mop); return 1; |
634 |
|
|
} |
635 |
|
|
|
636 |
|
|
/* record where the header is being written to for the sake of |
637 |
|
|
nrrdIoStateDataFileIterNext(). This may be NULL if |
638 |
|
|
nio->headerStringWrite is non-NULL */ |
639 |
|
11 |
nio->headerFile = file; |
640 |
|
|
|
641 |
|
|
/* we have to make sure that the data filename information is set |
642 |
|
|
(if needed), so that it can be printed by _nrrdFprintFieldInfo */ |
643 |
✗✗ |
11 |
if (nio->detachedHeader |
644 |
✗✓ |
11 |
&& !nio->dataFNFormat |
645 |
|
|
&& 0 == nio->dataFNArr->len) { |
646 |
|
|
/* NOTE: this means someone requested a detached header, but we |
647 |
|
|
don't already have implicit (via dataFNFormat) or explicit |
648 |
|
|
(via dataFN[]) information about the data file */ |
649 |
|
|
/* NOTE: whether or not nio->skipData, we have to contrive a filename to |
650 |
|
|
say in the "data file" field, which is stored in nio->dataFN[0], |
651 |
|
|
because the data filename will be "interesting", according to |
652 |
|
|
_nrrdFieldInteresting() */ |
653 |
|
|
/* NOTE: Fri Feb 4 01:42:20 EST 2005 the way this is now set up, having |
654 |
|
|
a name in dataFN[0] will trump the name implied by nio->{path,base}, |
655 |
|
|
which is a useful way for the user to explicitly set the output |
656 |
|
|
data filename (as with unu make -od) */ |
657 |
|
|
if (!( !!airStrlen(nio->path) && !!airStrlen(nio->base) )) { |
658 |
|
|
biffAddf(NRRD, "%s: can't create data file name: nio's " |
659 |
|
|
"path and base empty", me); |
660 |
|
|
airMopError(mop); return 1; |
661 |
|
|
} |
662 |
|
|
tmp = (char*)malloc(strlen(nio->base) |
663 |
|
|
+ strlen(".") |
664 |
|
|
+ strlen(nio->encoding->suffix) + 1); |
665 |
|
|
if (!tmp) { |
666 |
|
|
biffAddf(NRRD, "%s: couldn't allocate data filename", me); |
667 |
|
|
airMopError(mop); return 1; |
668 |
|
|
} |
669 |
|
|
airMopAdd(mop, tmp, airFree, airMopOnError); |
670 |
|
|
sprintf(tmp, "%s.%s", nio->base, nio->encoding->suffix); |
671 |
|
|
jj = airArrayLenIncr(nio->dataFNArr, 1); |
672 |
|
|
if (!nio->dataFNArr->data) { |
673 |
|
|
biffAddf(NRRD, "%s: can't increase dataFNArr storage", me); |
674 |
|
|
airMopError(mop); return 1; |
675 |
|
|
} |
676 |
|
|
nio->dataFN[jj] = tmp; |
677 |
|
|
} |
678 |
|
|
|
679 |
|
|
/* the magic is in fact the first thing to be written */ |
680 |
✓✗ |
11 |
if (file) { |
681 |
|
11 |
fprintf(file, "%s%04d\n", MAGIC, _nrrdFormatNRRD_whichVersion(nrrd, nio)); |
682 |
✗✗ |
11 |
} else if (nio->headerStringWrite) { |
683 |
|
|
sprintf(nio->headerStringWrite, "%s%04d\n", |
684 |
|
|
MAGIC, _nrrdFormatNRRD_whichVersion(nrrd, nio)); |
685 |
|
|
} else { |
686 |
|
|
nio->headerStrlen = AIR_CAST(unsigned int, strlen(MAGIC) + strlen("0000")) + 1; |
687 |
|
|
} |
688 |
|
|
|
689 |
|
|
/* write the advertisement about where to get the file format */ |
690 |
✓✗ |
11 |
if (!nio->skipFormatURL) { |
691 |
✓✗ |
11 |
if (file) { |
692 |
|
11 |
fprintf(file, "# %s\n", _nrrdFormatURLLine0); |
693 |
|
11 |
fprintf(file, "# %s\n", _nrrdFormatURLLine1); |
694 |
✗✗✗✗
|
11 |
} else if (nio->headerStringWrite) { |
695 |
|
|
sprintf(strbuf, "# %s\n", _nrrdFormatURLLine0); |
696 |
|
|
strcat(nio->headerStringWrite, strbuf); |
697 |
|
|
sprintf(strbuf, "# %s\n", _nrrdFormatURLLine1); |
698 |
|
|
strcat(nio->headerStringWrite, strbuf); |
699 |
|
|
} else { |
700 |
|
|
nio->headerStrlen += sprintf(strbuf, "# %s\n", _nrrdFormatURLLine0); |
701 |
|
|
nio->headerStrlen += sprintf(strbuf, "# %s\n", _nrrdFormatURLLine1); |
702 |
|
|
} |
703 |
|
|
} |
704 |
|
|
|
705 |
|
|
/* this is where the majority of the header printing happens */ |
706 |
✓✓ |
726 |
for (ii=1; ii<=NRRD_FIELD_MAX; ii++) { |
707 |
✓✓ |
352 |
if (_nrrdFieldInteresting(nrrd, nio, ii)) { |
708 |
✓✗ |
116 |
if (file) { |
709 |
|
116 |
_nrrdFprintFieldInfo(file, "", nrrd, nio, ii, AIR_FALSE); |
710 |
✗✗✗✗
|
116 |
} else if (nio->headerStringWrite) { |
711 |
|
|
_nrrdSprintFieldInfo(&strptr, "", nrrd, nio, ii, AIR_FALSE); |
712 |
|
|
if (strptr) { |
713 |
|
|
strcat(nio->headerStringWrite, strptr); |
714 |
|
|
strcat(nio->headerStringWrite, "\n"); |
715 |
|
|
free(strptr); |
716 |
|
|
strptr = NULL; |
717 |
|
|
} |
718 |
|
|
} else { |
719 |
|
|
_nrrdSprintFieldInfo(&strptr, "", nrrd, nio, ii, AIR_FALSE); |
720 |
|
|
if (strptr) { |
721 |
|
|
nio->headerStrlen += AIR_CAST(unsigned int, strlen(strptr)); |
722 |
|
|
nio->headerStrlen += AIR_CAST(unsigned int, strlen("\n")); |
723 |
|
|
free(strptr); |
724 |
|
|
strptr = NULL; |
725 |
|
|
} |
726 |
|
|
} |
727 |
|
|
} |
728 |
|
|
} |
729 |
|
|
|
730 |
|
|
/* comments and key/value pairs handled differently */ |
731 |
✓✓ |
28 |
for (jj=0; jj<nrrd->cmtArr->len; jj++) { |
732 |
|
|
char *strtmp; |
733 |
|
3 |
strtmp = airOneLinify(airStrdup(nrrd->cmt[jj])); |
734 |
✓✗ |
3 |
if (file) { |
735 |
|
3 |
fprintf(file, "%c %s\n", NRRD_COMMENT_CHAR, strtmp); |
736 |
✗✗✗✗
|
3 |
} else if (nio->headerStringWrite) { |
737 |
|
|
strptr = (char*)malloc(1 + strlen(" ") |
738 |
|
|
+ strlen(strtmp) + strlen("\n") + 1); |
739 |
|
|
sprintf(strptr, "%c %s\n", NRRD_COMMENT_CHAR, strtmp); |
740 |
|
|
strcat(nio->headerStringWrite, strptr); |
741 |
|
|
free(strptr); |
742 |
|
|
strptr = NULL; |
743 |
|
|
} else { |
744 |
|
|
nio->headerStrlen += (1 + AIR_CAST(unsigned int, strlen(" ") |
745 |
|
|
+ strlen(strtmp) |
746 |
|
|
+ strlen("\n")) + 1); |
747 |
|
|
} |
748 |
|
3 |
airFree(strtmp); |
749 |
|
|
} |
750 |
✓✓ |
28 |
for (jj=0; jj<nrrd->kvpArr->len; jj++) { |
751 |
✓✗ |
3 |
if (file) { |
752 |
|
3 |
_nrrdKeyValueWrite(file, NULL, |
753 |
|
3 |
NULL, nrrd->kvp[0 + 2*jj], nrrd->kvp[1 + 2*jj]); |
754 |
✗✗✗✗
|
3 |
} else if (nio->headerStringWrite) { |
755 |
|
|
_nrrdKeyValueWrite(NULL, &strptr, |
756 |
|
|
NULL, nrrd->kvp[0 + 2*jj], nrrd->kvp[1 + 2*jj]); |
757 |
|
|
if (strptr) { |
758 |
|
|
strcat(nio->headerStringWrite, strptr); |
759 |
|
|
free(strptr); |
760 |
|
|
strptr = NULL; |
761 |
|
|
} |
762 |
|
|
} else { |
763 |
|
|
_nrrdKeyValueWrite(NULL, &strptr, |
764 |
|
|
NULL, nrrd->kvp[0 + 2*jj], nrrd->kvp[1 + 2*jj]); |
765 |
|
|
if (strptr) { |
766 |
|
|
nio->headerStrlen += AIR_CAST(unsigned int, strlen(strptr)); |
767 |
|
|
free(strptr); |
768 |
|
|
strptr = NULL; |
769 |
|
|
} |
770 |
|
|
} |
771 |
|
|
} |
772 |
|
|
|
773 |
✓✗ |
11 |
if (file) { |
774 |
✓✗✓✗
|
22 |
if (!( nio->detachedHeader || _nrrdDataFNNumber(nio) > 1 )) { |
775 |
|
11 |
fprintf(file, "\n"); |
776 |
|
11 |
} |
777 |
|
|
} |
778 |
|
|
|
779 |
✓✗✓✗
|
22 |
if (file && !nio->skipData) { |
780 |
|
11 |
nrrdIoStateDataFileIterBegin(nio); |
781 |
✗✓ |
11 |
if (nrrdIoStateDataFileIterNext(&dataFile, nio, AIR_FALSE)) { |
782 |
|
|
biffAddf(NRRD, "%s: couldn't write the first datafile", me); |
783 |
|
|
airMopError(mop); return 1; |
784 |
|
|
} |
785 |
|
|
|
786 |
|
11 |
valsPerPiece = nrrdElementNumber(nrrd)/_nrrdDataFNNumber(nio); |
787 |
|
11 |
data = (char*)nrrd->data; |
788 |
|
11 |
do { |
789 |
|
|
/* ---------------- write data */ |
790 |
✗✓ |
11 |
if (2 <= nrrdStateVerboseIO) { |
791 |
|
|
fprintf(stderr, "(%s: writing %s data ", me, nio->encoding->name); |
792 |
|
|
fflush(stderr); |
793 |
|
|
} |
794 |
✗✓✗✓
|
22 |
if (nio->encoding->write(dataFile, data, valsPerPiece, nrrd, nio)) { |
795 |
✗✗ |
11 |
if (2 <= nrrdStateVerboseIO) { |
796 |
|
|
fprintf(stderr, "error!\n"); |
797 |
|
|
} |
798 |
|
|
biffAddf(NRRD, "%s: couldn't write %s data", me, nio->encoding->name); |
799 |
|
|
airMopError(mop); return 1; |
800 |
|
|
} |
801 |
✗✓ |
11 |
if (2 <= nrrdStateVerboseIO) { |
802 |
|
|
fprintf(stderr, "done)\n"); |
803 |
|
|
} |
804 |
|
|
/* ---------------- go to next data file */ |
805 |
✗✓ |
11 |
if (dataFile != nio->headerFile) { |
806 |
|
|
dataFile = airFclose(dataFile); |
807 |
|
|
} |
808 |
|
11 |
data += valsPerPiece*nrrdElementSize(nrrd); |
809 |
✗✓ |
11 |
if (nrrdIoStateDataFileIterNext(&dataFile, nio, AIR_TRUE)) { |
810 |
|
|
biffAddf(NRRD, "%s: couldn't get the next datafile", me); |
811 |
|
|
airMopError(mop); return 1; |
812 |
|
|
} |
813 |
✗✓ |
11 |
} while (dataFile); |
814 |
|
|
} |
815 |
|
|
|
816 |
|
11 |
airMopOkay(mop); |
817 |
|
11 |
return 0; |
818 |
|
11 |
} |
819 |
|
|
|
820 |
|
|
const NrrdFormat |
821 |
|
|
_nrrdFormatNRRD = { |
822 |
|
|
"NRRD", |
823 |
|
|
AIR_FALSE, /* isImage */ |
824 |
|
|
AIR_TRUE, /* readable */ |
825 |
|
|
AIR_TRUE, /* usesDIO */ |
826 |
|
|
_nrrdFormatNRRD_available, |
827 |
|
|
_nrrdFormatNRRD_nameLooksLike, |
828 |
|
|
_nrrdFormatNRRD_fitsInto, |
829 |
|
|
_nrrdFormatNRRD_contentStartsLike, |
830 |
|
|
_nrrdFormatNRRD_read, |
831 |
|
|
_nrrdFormatNRRD_write |
832 |
|
|
}; |
833 |
|
|
|
834 |
|
|
const NrrdFormat *const |
835 |
|
|
nrrdFormatNRRD = &_nrrdFormatNRRD; |