1 |
|
|
/* |
2 |
|
|
Teem: Tools to process and visualize scientific data and images . |
3 |
|
|
Copyright (C) 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 |
|
|
#define MAGIC_P6 "P6" |
28 |
|
|
#define MAGIC_P5 "P5" |
29 |
|
|
#define MAGIC_P3 "P3" |
30 |
|
|
#define MAGIC_P2 "P2" |
31 |
|
|
|
32 |
|
|
static int |
33 |
|
|
_nrrdFormatPNM_available(void) { |
34 |
|
|
|
35 |
|
8 |
return AIR_TRUE; |
36 |
|
|
} |
37 |
|
|
|
38 |
|
|
static int |
39 |
|
|
_nrrdFormatPNM_nameLooksLike(const char *filename) { |
40 |
|
|
|
41 |
|
3 |
return (airEndsWith(filename, NRRD_EXT_PGM) |
42 |
✗✓ |
2 |
|| airEndsWith(filename, NRRD_EXT_PPM)); |
43 |
|
|
} |
44 |
|
|
|
45 |
|
|
static int |
46 |
|
|
_nrrdFormatPNM_fitsInto(const Nrrd *nrrd, const NrrdEncoding *encoding, |
47 |
|
|
int useBiff) { |
48 |
|
|
static const char me[]="_nrrdFormatPNM_fitsInto"; |
49 |
|
|
int ret; |
50 |
|
|
|
51 |
✗✓ |
2 |
if (!( nrrd && encoding )) { |
52 |
|
|
biffMaybeAddf(useBiff, NRRD, "%s: got NULL nrrd (%p) or encoding (%p)", |
53 |
|
|
me, AIR_CVOIDP(nrrd), AIR_CVOIDP(encoding)); |
54 |
|
|
return AIR_FALSE; |
55 |
|
|
} |
56 |
✗✓ |
1 |
if (nrrdTypeUChar != nrrd->type) { |
57 |
|
|
biffMaybeAddf(useBiff, NRRD, "%s: type must be %s (not %s)", me, |
58 |
|
|
airEnumStr(nrrdType, nrrdTypeUChar), |
59 |
|
|
airEnumStr(nrrdType, nrrd->type)); |
60 |
|
|
return AIR_FALSE; |
61 |
|
|
} |
62 |
✗✓✗✗
|
1 |
if (!( nrrdEncodingRaw == encoding || nrrdEncodingAscii == encoding)) { |
63 |
|
|
biffMaybeAddf(useBiff, NRRD, "%s: encoding can only be %s or %s", me, |
64 |
|
|
nrrdEncodingRaw->name, nrrdEncodingAscii->name); |
65 |
|
|
return AIR_FALSE; |
66 |
|
|
} |
67 |
✓✗ |
1 |
if (2 == nrrd->dim) { |
68 |
|
|
/* its a gray-scale image */ |
69 |
|
|
ret = 2; |
70 |
✗✗ |
1 |
} else if (3 == nrrd->dim) { |
71 |
|
|
if (1 == nrrd->axis[0].size) { |
72 |
|
|
/* its a faux-3D image, really grayscale */ |
73 |
|
|
ret = 2; |
74 |
|
|
} else if (3 == nrrd->axis[0].size) { |
75 |
|
|
/* its a real color image */ |
76 |
|
|
ret = 3; |
77 |
|
|
} else { |
78 |
|
|
/* else its no good */ |
79 |
|
|
char stmp[AIR_STRLEN_SMALL]; |
80 |
|
|
biffMaybeAddf(useBiff, NRRD, |
81 |
|
|
"%s: dim is 3, but 1st axis size is %s, not 1 or 3", me, |
82 |
|
|
airSprintSize_t(stmp, nrrd->axis[0].size)); |
83 |
|
|
return AIR_FALSE; |
84 |
|
|
} |
85 |
|
|
} else { |
86 |
|
|
biffMaybeAddf(useBiff, NRRD, |
87 |
|
|
"%s: dimension is %d, not 2 or 3", me, nrrd->dim); |
88 |
|
|
return AIR_FALSE; |
89 |
|
|
} |
90 |
|
1 |
return ret; |
91 |
|
1 |
} |
92 |
|
|
|
93 |
|
|
int |
94 |
|
|
_nrrdFormatPNM_contentStartsLike(NrrdIoState *nio) { |
95 |
|
|
|
96 |
|
12 |
return (!strcmp(MAGIC_P6, nio->line) |
97 |
✓✗ |
8 |
|| !strcmp(MAGIC_P5, nio->line) |
98 |
✗✓ |
4 |
|| !strcmp(MAGIC_P3, nio->line) |
99 |
✗✗ |
4 |
|| !strcmp(MAGIC_P2, nio->line)); |
100 |
|
|
} |
101 |
|
|
|
102 |
|
|
static int |
103 |
|
|
_nrrdFormatPNM_read(FILE *file, Nrrd *nrrd, NrrdIoState *nio) { |
104 |
|
|
static const char me[]="_nrrdFormatPNM_read"; |
105 |
|
|
const char *fs; |
106 |
|
|
char *perr; |
107 |
|
4 |
int color, got, want, ret, val[5], sx, sy, max, magic; |
108 |
|
2 |
unsigned int i, llen; |
109 |
|
|
|
110 |
✗✓ |
2 |
if (!_nrrdFormatPNM_contentStartsLike(nio)) { |
111 |
|
|
biffAddf(NRRD, "%s: this doesn't look like a %s file", me, |
112 |
|
|
nrrdFormatPNM->name); |
113 |
|
|
return 1; |
114 |
|
|
} |
115 |
|
2 |
nrrd->type = nrrdTypeUChar; |
116 |
✗✓ |
2 |
if (!strcmp(MAGIC_P6, nio->line)) { |
117 |
|
|
magic = 6; |
118 |
✓✗ |
2 |
} else if (!strcmp(MAGIC_P5, nio->line)) { |
119 |
|
|
magic = 5; |
120 |
✗✗ |
2 |
} else if (!strcmp(MAGIC_P3, nio->line)) { |
121 |
|
|
magic = 3; |
122 |
|
|
} else if (!strcmp(MAGIC_P2, nio->line)) { |
123 |
|
|
magic = 2; |
124 |
|
|
} else { |
125 |
|
|
fprintf(stderr, "%s: PANIC: magic \"%s\" not handled\n", me, nio->line); |
126 |
|
|
biffAddf(NRRD, "%s: PANIC: magic \"%s\" not handled\n", me, nio->line); |
127 |
|
|
return 1; |
128 |
|
|
} |
129 |
|
|
|
130 |
✗✗✓✗ ✗ |
2 |
switch(magic) { |
131 |
|
|
case 2: |
132 |
|
|
color = AIR_FALSE; |
133 |
|
|
nio->encoding = nrrdEncodingAscii; |
134 |
|
|
nrrd->dim = 2; |
135 |
|
|
break; |
136 |
|
|
case 3: |
137 |
|
|
color = AIR_TRUE; |
138 |
|
|
nio->encoding = nrrdEncodingAscii; |
139 |
|
|
nrrd->dim = 3; |
140 |
|
|
break; |
141 |
|
|
case 5: |
142 |
|
|
color = AIR_FALSE; |
143 |
|
2 |
nio->encoding = nrrdEncodingRaw; |
144 |
|
2 |
nrrd->dim = 2; |
145 |
|
2 |
break; |
146 |
|
|
case 6: |
147 |
|
|
color = AIR_TRUE; |
148 |
|
|
nio->encoding = nrrdEncodingRaw; |
149 |
|
|
nrrd->dim = 3; |
150 |
|
|
break; |
151 |
|
|
default: |
152 |
|
|
biffAddf(NRRD, "%s: sorry, PNM magic %d not implemented", me, magic); |
153 |
|
|
return 1; |
154 |
|
|
break; |
155 |
|
|
} |
156 |
|
2 |
val[0] = val[1] = val[2] = 0; |
157 |
|
|
got = 0; |
158 |
|
|
want = 3; |
159 |
✓✓ |
24 |
while (got < want) { |
160 |
|
20 |
nio->pos = 0; |
161 |
✗✓ |
20 |
if (_nrrdOneLine(&llen, nio, file)) { |
162 |
|
|
biffAddf(NRRD, "%s: failed to get line from PNM header", me); |
163 |
|
|
return 1; |
164 |
|
|
} |
165 |
✗✓ |
20 |
if (!(0 < llen)) { |
166 |
|
|
biffAddf(NRRD, "%s: hit EOF in header with %d of %d ints parsed", |
167 |
|
|
me, got, want); |
168 |
|
|
return 1; |
169 |
|
|
} |
170 |
✓✓ |
20 |
if ('#' == nio->line[0]) { |
171 |
✓✓ |
16 |
if (strncmp(nio->line, NRRD_PNM_COMMENT, strlen(NRRD_PNM_COMMENT))) { |
172 |
|
|
/* this is a generic comment */ |
173 |
|
|
ret = 0; |
174 |
|
6 |
goto plain; |
175 |
|
|
} |
176 |
|
|
/* else this PNM comment is trying to tell us something */ |
177 |
|
10 |
nio->pos = AIR_CAST(int, strlen(NRRD_PNM_COMMENT)); |
178 |
|
10 |
nio->pos += AIR_CAST(int, strspn(nio->line + nio->pos, _nrrdFieldSep)); |
179 |
|
10 |
ret = _nrrdReadNrrdParseField(nio, AIR_FALSE); |
180 |
✗✓ |
10 |
if (!ret) { |
181 |
|
|
if (1 <= nrrdStateVerboseIO) { |
182 |
|
|
fprintf(stderr, "(%s: unparsable field \"%s\" --> plain comment)\n", |
183 |
|
|
me, nio->line); |
184 |
|
|
} |
185 |
|
|
goto plain; |
186 |
|
|
} |
187 |
✗✓ |
10 |
if (nrrdField_comment == ret) { |
188 |
|
|
ret = 0; |
189 |
|
|
goto plain; |
190 |
|
|
} |
191 |
|
10 |
fs = airEnumStr(nrrdField, ret); |
192 |
✗✓ |
10 |
if (!_nrrdFieldValidInImage[ret]) { |
193 |
|
|
if (1 <= nrrdStateVerboseIO) { |
194 |
|
|
fprintf(stderr, "(%s: field \"%s\" (not allowed in PNM) " |
195 |
|
|
"--> plain comment)\n", me, fs); |
196 |
|
|
} |
197 |
|
|
ret = 0; |
198 |
|
|
goto plain; |
199 |
|
|
} |
200 |
✗✓ |
20 |
if (!nio->seen[ret] |
201 |
✓✗ |
20 |
&& nrrdFieldInfoParse[ret](file, nrrd, nio, AIR_TRUE)) { |
202 |
|
|
perr = biffGetDone(NRRD); |
203 |
|
|
if (1 <= nrrdStateVerboseIO) { |
204 |
|
|
fprintf(stderr, "(%s: unparsable info for field \"%s\" " |
205 |
|
|
"--> plain comment:\n%s)\n", me, fs, perr); |
206 |
|
|
} |
207 |
|
|
free(perr); |
208 |
|
|
ret = 0; |
209 |
|
|
goto plain; |
210 |
|
|
} |
211 |
|
10 |
nio->seen[ret] = AIR_TRUE; |
212 |
|
|
plain: |
213 |
✓✓ |
16 |
if (!ret) { |
214 |
✓✗ |
6 |
if (nrrdCommentAdd(nrrd, nio->line+1)) { |
215 |
|
|
biffAddf(NRRD, "%s: couldn't add comment", me); |
216 |
|
|
return 1; |
217 |
|
|
} |
218 |
|
|
} |
219 |
|
|
continue; |
220 |
|
|
} |
221 |
|
|
|
222 |
✗✓ |
4 |
if (3 == sscanf(nio->line, "%d%d%d", val+got+0, val+got+1, val+got+2)) { |
223 |
|
|
got += 3; |
224 |
|
|
continue; |
225 |
|
|
} |
226 |
✓✓ |
4 |
if (2 == sscanf(nio->line, "%d%d", val+got+0, val+got+1)) { |
227 |
|
2 |
got += 2; |
228 |
|
2 |
continue; |
229 |
|
|
} |
230 |
✓✗ |
2 |
if (1 == sscanf(nio->line, "%d", val+got+0)) { |
231 |
|
2 |
got += 1; |
232 |
|
2 |
continue; |
233 |
|
|
} |
234 |
|
|
|
235 |
|
|
/* else, we couldn't parse ANY numbers on this line, which is okay |
236 |
|
|
as long as the line contains nothing but white space */ |
237 |
|
|
for (i=0; (i<=strlen(nio->line)-1 |
238 |
|
|
&& isspace(AIR_INT(nio->line[i]))); i++) |
239 |
|
|
; |
240 |
|
|
if (i != strlen(nio->line)) { |
241 |
|
|
biffAddf(NRRD, "%s: \"%s\" has no integers but isn't just whitespace", |
242 |
|
|
me, nio->line); |
243 |
|
|
return 1; |
244 |
|
|
} |
245 |
|
|
} |
246 |
|
|
/* this assumes 3 == want */ |
247 |
|
2 |
sx = val[0]; |
248 |
|
2 |
sy = val[1]; |
249 |
|
2 |
max = val[2]; |
250 |
✗✓ |
2 |
if (!(sx > 0 && sy > 0 && max > 0)) { |
251 |
|
|
biffAddf(NRRD, "%s: sx,sy,max of %d,%d,%d has problem", me, sx, sy, max); |
252 |
|
|
return 1; |
253 |
|
|
} |
254 |
✗✓ |
2 |
if (255 != max) { |
255 |
|
|
biffAddf(NRRD, "%s: sorry, can only deal with max value 255 (not %d)", |
256 |
|
|
me, max); |
257 |
|
|
return 1; |
258 |
|
|
} |
259 |
|
|
|
260 |
|
|
/* we know what we need in order to set nrrd fields and read data */ |
261 |
✗✓✗✓
|
4 |
if (color) { |
262 |
|
|
nrrdAxisInfoSet_va(nrrd, nrrdAxisInfoSize, |
263 |
|
|
AIR_CAST(size_t, 3), |
264 |
|
2 |
AIR_CAST(size_t, sx), |
265 |
|
2 |
AIR_CAST(size_t, sy)); |
266 |
|
|
} else { |
267 |
|
2 |
nrrdAxisInfoSet_va(nrrd, nrrdAxisInfoSize, |
268 |
|
|
AIR_CAST(size_t, sx), |
269 |
|
|
AIR_CAST(size_t, sy)); |
270 |
|
|
} |
271 |
✓✗ |
2 |
if (!nio->skipData) { |
272 |
✗✓ |
2 |
if (_nrrdCalloc(nrrd, nio, file)) { |
273 |
|
|
biffAddf(NRRD, "%s: couldn't allocate memory for data", me); |
274 |
|
|
return 1; |
275 |
|
|
} |
276 |
✗✓ |
2 |
if (nio->encoding->read(file, nrrd->data, nrrdElementNumber(nrrd), |
277 |
|
|
nrrd, nio)) { |
278 |
|
|
biffAddf(NRRD, "%s:", me); |
279 |
|
|
return 1; |
280 |
|
|
} |
281 |
|
|
} else { |
282 |
|
|
nrrd->data = NULL; |
283 |
|
|
} |
284 |
|
|
|
285 |
|
2 |
return 0; |
286 |
|
2 |
} |
287 |
|
|
|
288 |
|
|
static int |
289 |
|
|
_nrrdFormatPNM_write(FILE *file, const Nrrd *_nrrd, NrrdIoState *nio) { |
290 |
|
|
static const char me[]="_nrrdFormatPNM_write"; |
291 |
|
|
int color, sx, sy, magic, fi; |
292 |
|
|
unsigned int ci; |
293 |
|
|
Nrrd *nrrd; |
294 |
|
|
airArray *mop; |
295 |
|
|
|
296 |
|
2 |
mop = airMopNew(); |
297 |
|
1 |
airMopAdd(mop, nrrd = nrrdNew(), (airMopper)nrrdNuke, airMopAlways); |
298 |
✗✓ |
1 |
if (nrrdCopy(nrrd, _nrrd)) { |
299 |
|
|
biffAddf(NRRD, "%s: couldn't make private copy", me); |
300 |
|
|
airMopError(mop); return 1; |
301 |
|
|
} |
302 |
✗✓✗✗
|
1 |
if (3 == nrrd->dim && 1 == nrrd->axis[0].size) { |
303 |
|
|
if (nrrdAxesDelete(nrrd, nrrd, 0)) { |
304 |
|
|
biffAddf(NRRD, "%s:", me); |
305 |
|
|
airMopError(mop); return 1; |
306 |
|
|
} |
307 |
|
|
} |
308 |
|
1 |
color = (3 == nrrd->dim); |
309 |
✓✗ |
1 |
if (!color) { |
310 |
|
1 |
magic = (nrrdEncodingAscii == nio->encoding ? 2 : 5); |
311 |
|
1 |
sx = AIR_CAST(int, nrrd->axis[0].size); |
312 |
|
1 |
sy = AIR_CAST(int, nrrd->axis[1].size); |
313 |
|
1 |
} else { |
314 |
|
1 |
magic = (nrrdEncodingAscii == nio->encoding ? 3 : 6); |
315 |
|
|
sx = AIR_CAST(int, nrrd->axis[1].size); |
316 |
|
|
sy = AIR_CAST(int, nrrd->axis[2].size); |
317 |
|
|
} |
318 |
|
|
|
319 |
|
1 |
fprintf(file, "P%d\n", magic); |
320 |
|
1 |
fprintf(file, "%d %d\n", sx, sy); |
321 |
✓✓ |
66 |
for (fi=nrrdField_unknown+1; fi<nrrdField_last; fi++) { |
322 |
✓✓ |
52 |
if (_nrrdFieldValidInImage[fi] |
323 |
✓✓ |
52 |
&& _nrrdFieldInteresting(nrrd, nio, fi)) { |
324 |
|
|
/* dropAxis0 is always AIR_FALSE because of code |
325 |
|
|
above to delete a stub axis 0 */ |
326 |
|
5 |
_nrrdFprintFieldInfo(file, NRRD_PNM_COMMENT, nrrd, nio, fi, AIR_FALSE); |
327 |
|
5 |
} |
328 |
|
|
} |
329 |
✓✓ |
8 |
for (ci=0; ci<nrrd->cmtArr->len; ci++) { |
330 |
|
3 |
fprintf(file, "# %s\n", nrrd->cmt[ci]); |
331 |
|
|
} |
332 |
|
1 |
fprintf(file, "255\n"); |
333 |
|
|
|
334 |
✓✗ |
1 |
if (!nio->skipData) { |
335 |
✗✓ |
1 |
if (nio->encoding->write(file, nrrd->data, nrrdElementNumber(nrrd), |
336 |
|
|
nrrd, nio)) { |
337 |
|
|
biffAddf(NRRD, "%s:", me); |
338 |
|
|
airMopError(mop); return 1; |
339 |
|
|
} |
340 |
|
|
} |
341 |
|
|
|
342 |
|
1 |
airMopError(mop); |
343 |
|
1 |
return 0; |
344 |
|
1 |
} |
345 |
|
|
|
346 |
|
|
const NrrdFormat |
347 |
|
|
_nrrdFormatPNM = { |
348 |
|
|
"PNM", |
349 |
|
|
AIR_TRUE, /* isImage */ |
350 |
|
|
AIR_TRUE, /* readable */ |
351 |
|
|
AIR_FALSE, /* usesDIO */ |
352 |
|
|
_nrrdFormatPNM_available, |
353 |
|
|
_nrrdFormatPNM_nameLooksLike, |
354 |
|
|
_nrrdFormatPNM_fitsInto, |
355 |
|
|
_nrrdFormatPNM_contentStartsLike, |
356 |
|
|
_nrrdFormatPNM_read, |
357 |
|
|
_nrrdFormatPNM_write |
358 |
|
|
}; |
359 |
|
|
|
360 |
|
|
const NrrdFormat *const |
361 |
|
|
nrrdFormatPNM = &_nrrdFormatPNM; |