1 /* save to nifti
2 *
3 * 5/7/18
4 * - from fitssave.c
5 * 9/9/19
6 * - use double for all floating point scalar metadata, like other loaders
7 */
8
9 /*
10
11 This file is part of VIPS.
12
13 VIPS is free software; you can redistribute it and/or modify
14 it under the terms of the GNU Lesser General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU Lesser General Public License for more details.
22
23 You should have received a copy of the GNU Lesser General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 02110-1301 USA
27
28 */
29
30 /*
31
32 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
33
34 */
35
36 /*
37 #define DEBUG_VERBOSE
38 #define DEBUG
39 */
40
41 #ifdef HAVE_CONFIG_H
42 #include <config.h>
43 #endif /*HAVE_CONFIG_H*/
44 #include <vips/intl.h>
45
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49
50 #include <vips/vips.h>
51
52 #ifdef HAVE_NIFTI
53
54 #include <nifti1_io.h>
55
56 #include "pforeign.h"
57
58 typedef struct _VipsForeignSaveNifti {
59 VipsForeignSave parent_object;
60
61 /* Filename for save.
62 */
63 char *filename;
64
65 nifti_image *nim;
66
67 } VipsForeignSaveNifti;
68
69 typedef VipsForeignSaveClass VipsForeignSaveNiftiClass;
70
71 G_DEFINE_TYPE( VipsForeignSaveNifti, vips_foreign_save_nifti,
72 VIPS_TYPE_FOREIGN_SAVE );
73
74 static void
vips_foreign_save_nifti_dispose(GObject * gobject)75 vips_foreign_save_nifti_dispose( GObject *gobject )
76 {
77 VipsForeignSaveNifti *nifti = (VipsForeignSaveNifti *) gobject;
78
79 VIPS_FREEF( nifti_image_free, nifti->nim );
80
81 G_OBJECT_CLASS( vips_foreign_save_nifti_parent_class )->
82 dispose( gobject );
83 }
84
85 /* Make ->nim from the vips header fields.
86 */
87 static int
vips_foreign_save_nifti_header_vips(VipsForeignSaveNifti * nifti,VipsImage * image)88 vips_foreign_save_nifti_header_vips( VipsForeignSaveNifti *nifti,
89 VipsImage *image )
90 {
91 VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( nifti );
92
93 int dims[8];
94 int datatype;
95 int i;
96
97 /* Most nifti images have this defaulted as 1.
98 */
99 for( i = 0; i < VIPS_NUMBER( dims ); i++ )
100 dims[i] = 1;
101
102 dims[0] = 2;
103 dims[1] = image->Xsize;
104 dims[2] = vips_image_get_page_height( image );
105
106 /* Multipage image?
107 */
108 if( dims[2] < image->Ysize ) {
109 dims[0] = 3;
110 dims[3] = image->Ysize / dims[2];
111 }
112
113 datatype = vips__foreign_nifti_BandFmt2datatype( image->BandFmt );
114 if( datatype == -1 ) {
115 vips_error( class->nickname,
116 "%s", _( "unsupported libvips image type" ) );
117 return( -1 );
118 }
119
120 if( image->Bands > 1 ) {
121 if( image->BandFmt != VIPS_FORMAT_UCHAR ) {
122 vips_error( class->nickname,
123 "%s", _( "8-bit colour images only" ) );
124 return( -1 );
125 }
126
127 if( image->Bands == 3 )
128 datatype = DT_RGB;
129 else if( image->Bands == 4 )
130 datatype = DT_RGBA32;
131 else {
132 vips_error( class->nickname,
133 "%s", _( "3 or 4 band colour images only" ) );
134 return( -1 );
135 }
136 }
137
138 if( !(nifti->nim = nifti_make_new_nim( dims, datatype, FALSE )) )
139 return( -1 );
140
141 nifti->nim->dx = 1.0 / image->Xres;
142 nifti->nim->dy = 1.0 / image->Yres;
143 nifti->nim->dz = 1.0 / image->Yres;
144 nifti->nim->xyz_units = NIFTI_UNITS_MM;
145
146 vips_snprintf( nifti->nim->descrip, sizeof( nifti->nim->descrip ),
147 "libvips-%s", VIPS_VERSION );
148
149 /* All other fields can stay at their default value.
150 */
151
152 return( 0 );
153 }
154
155 typedef struct _VipsNdimInfo {
156 VipsImage *image;
157 nifti_image *nim;
158 int *dims;
159 int n;
160 } VipsNdimInfo;
161
162 static void *
vips_foreign_save_nifti_set_dims(const char * name,GValue * value,glong offset,void * a,void * b)163 vips_foreign_save_nifti_set_dims( const char *name,
164 GValue *value, glong offset, void *a, void *b )
165 {
166 VipsNdimInfo *info = (VipsNdimInfo *) a;
167
168 /* The first 8 members are the dims fields.
169 */
170 if( info->n < 8 ) {
171 char vips_name[256];
172 int i;
173
174 vips_snprintf( vips_name, 256, "nifti-%s", name );
175 if( vips_image_get_int( info->image, vips_name, &i ) ||
176 i <= 0 ||
177 i >= VIPS_MAX_COORD )
178 return( info );
179 info->dims[info->n] = i;
180 }
181
182 info->n += 1;
183
184 return( NULL );
185 }
186
187 /* How I wish glib had something like this :( Just implement the ones we need
188 * for vips_foreign_nifti_fields above.
189 */
190 static void
vips_gvalue_write(GValue * value,void * p)191 vips_gvalue_write( GValue *value, void *p )
192 {
193 switch( G_VALUE_TYPE( value ) ) {
194 case G_TYPE_INT:
195 *((int *) p) = g_value_get_int( value );
196 break;
197
198 case G_TYPE_DOUBLE:
199 *((float *) p) = g_value_get_double( value );
200 break;
201
202 default:
203 g_warning( "vips_gvalue_write: unsupported GType %s",
204 g_type_name( G_VALUE_TYPE( value ) ) );
205 }
206 }
207
208 static void *
vips_foreign_save_nifti_set_fields(const char * name,GValue * value,glong offset,void * a,void * b)209 vips_foreign_save_nifti_set_fields( const char *name,
210 GValue *value, glong offset, void *a, void *b )
211 {
212 VipsNdimInfo *info = (VipsNdimInfo *) a;
213
214 /* The first 8 members are the dims fields. We set them above ^^^ --
215 * do the others in this pass.
216 */
217 if( info->n >= 8 ) {
218 char vips_name[256];
219 GValue value_copy = { 0 };
220
221 vips_snprintf( vips_name, 256, "nifti-%s", name );
222 if( vips_image_get( info->image, vips_name, &value_copy ) )
223 return( info );
224 vips_gvalue_write( &value_copy, (gpointer) info->nim + offset );
225 g_value_unset( &value_copy );
226 }
227
228 info->n += 1;
229
230 return( NULL );
231 }
232
233 static void *
vips_foreign_save_nifti_ext(VipsImage * image,const char * field,GValue * value,void * a)234 vips_foreign_save_nifti_ext( VipsImage *image,
235 const char *field, GValue *value, void *a )
236 {
237 nifti_image *nim = (nifti_image *) a;
238
239 int i;
240 int ecode;
241 char *data;
242 size_t length;
243
244 if( !vips_isprefix( "nifti-ext-", field ) )
245 return( NULL );
246
247 /* The name is "nifti-ext-N-XX" where N is the index (discard this)
248 * and XX is the nifti ext ecode.
249 */
250 if( sscanf( field, "nifti-ext-%d-%d", &i, &ecode ) != 2 ) {
251 vips_error( "niftisave",
252 "%s", _( "bad nifti-ext- field name" ) );
253 return( image );
254 }
255
256 if( vips_image_get_blob( image, field, (void *) &data, &length ) )
257 return( image );
258
259 if( nifti_add_extension( nim, data, length, ecode ) ) {
260 vips_error( "niftisave",
261 "%s", _( "unable to attach nifti ext" ) );
262 return( image );
263 }
264
265 return( NULL );
266 }
267
268 /* Make ->nim from the nifti- fields.
269 */
270 static int
vips_foreign_save_nifti_header_nifti(VipsForeignSaveNifti * nifti,VipsImage * image)271 vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti,
272 VipsImage *image )
273 {
274 VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( nifti );
275
276 VipsNdimInfo info;
277 int dims[8];
278 int datatype;
279 guint height;
280 int i;
281
282 /* Most nifti images have this defaulted as 1.
283 */
284 for( i = 0; i < VIPS_NUMBER( dims ); i++ )
285 dims[i] = 1;
286
287 info.image = image;
288 info.dims = dims;
289 info.n = 0;
290 if( vips__foreign_nifti_map(
291 vips_foreign_save_nifti_set_dims, &info, NULL ) )
292 return( -1 );
293
294 /* page-height overrides ny if it makes sense. This might not be
295 * correct :(
296 */
297 dims[2] = vips_image_get_page_height( image );
298
299 /* Multipage image?
300 */
301 if( dims[2] < image->Ysize ) {
302 dims[0] = 3;
303 dims[3] = image->Ysize / dims[2];
304 }
305
306 height = 1;
307 for( i = 2; i < VIPS_NUMBER( dims ) && i < dims[0] + 1; i++ )
308 if( !g_uint_checked_mul( &height, height, dims[i] ) ) {
309 vips_error( class->nickname,
310 "%s", _( "dimension overflow" ) );
311 return( 0 );
312 }
313 if( image->Xsize != dims[1] ||
314 image->Ysize != height ) {
315 vips_error( class->nickname,
316 "%s", _( "bad image dimensions" ) );
317 return( -1 );
318 }
319
320 datatype = vips__foreign_nifti_BandFmt2datatype( image->BandFmt );
321 if( datatype == -1 ) {
322 vips_error( class->nickname,
323 "%s", _( "unsupported libvips image type" ) );
324 return( -1 );
325 }
326
327 if( !(nifti->nim = nifti_make_new_nim( dims, datatype, FALSE )) )
328 return( -1 );
329
330 info.image = image;
331 info.nim = nifti->nim;
332 info.n = 0;
333 if( vips__foreign_nifti_map(
334 vips_foreign_save_nifti_set_fields, &info, NULL ) )
335 return( -1 );
336
337 /* Attach any ext blocks.
338 */
339 if( vips_image_map( image,
340 (VipsImageMapFn) vips_foreign_save_nifti_ext, nifti->nim ) )
341 return( -1 );
342
343 return( 0 );
344 }
345
346 static int
vips_foreign_save_nifti_build(VipsObject * object)347 vips_foreign_save_nifti_build( VipsObject *object )
348 {
349 VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
350 VipsForeignSave *save = (VipsForeignSave *) object;
351 VipsForeignSaveNifti *nifti = (VipsForeignSaveNifti *) object;
352
353 if( VIPS_OBJECT_CLASS( vips_foreign_save_nifti_parent_class )->
354 build( object ) )
355 return( -1 );
356
357 /* This could be an image (indirectly) from niftiload, or something
358 * like OME_TIFF, which does not have all the "nifti-ndim" fields.
359 *
360 * If it doesn't look like a nifti, try to make a nifti header from
361 * what we have.
362 */
363 if( vips_image_get_typeof( save->ready, "nifti-ndim" ) ) {
364 if( vips_foreign_save_nifti_header_nifti( nifti, save->ready ) )
365 return( -1 );
366 }
367 else {
368 if( vips_foreign_save_nifti_header_vips( nifti, save->ready ) )
369 return( -1 );
370 }
371
372 /* set ext, plus other stuff
373 */
374
375 if( nifti_set_filenames( nifti->nim, nifti->filename, FALSE, TRUE ) ) {
376 vips_error( class->nickname,
377 "%s", _( "unable to set nifti filename" ) );
378 return( -1 );
379 }
380
381 if( !(nifti->nim->data =
382 vips_image_write_to_memory( save->ready, NULL )) )
383 return( -1 );
384
385 /* No return code!??!?!!
386 */
387 nifti_image_write( nifti->nim );
388
389 /* We must free and NULL the pointer or nifti will try to free it for
390 * us.
391 */
392 VIPS_FREE( nifti->nim->data );
393
394 return( 0 );
395 }
396
397 /* Save a bit of typing.
398 */
399 #define UC VIPS_FORMAT_UCHAR
400 #define C VIPS_FORMAT_CHAR
401 #define US VIPS_FORMAT_USHORT
402 #define S VIPS_FORMAT_SHORT
403 #define UI VIPS_FORMAT_UINT
404 #define I VIPS_FORMAT_INT
405 #define F VIPS_FORMAT_FLOAT
406 #define X VIPS_FORMAT_COMPLEX
407 #define D VIPS_FORMAT_DOUBLE
408 #define DX VIPS_FORMAT_DPCOMPLEX
409
410 static int vips_nifti_bandfmt[10] = {
411 /* UC C US S UI I F X D DX */
412 UC, C, US, S, UI, I, F, X, D, DX
413 };
414
415 static void
vips_foreign_save_nifti_class_init(VipsForeignSaveNiftiClass * class)416 vips_foreign_save_nifti_class_init( VipsForeignSaveNiftiClass *class )
417 {
418 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
419 VipsObjectClass *object_class = (VipsObjectClass *) class;
420 VipsForeignClass *foreign_class = (VipsForeignClass *) class;
421 VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class;
422
423 gobject_class->dispose = vips_foreign_save_nifti_dispose;
424 gobject_class->set_property = vips_object_set_property;
425 gobject_class->get_property = vips_object_get_property;
426
427 object_class->nickname = "niftisave";
428 object_class->description = _( "save image to nifti file" );
429 object_class->build = vips_foreign_save_nifti_build;
430
431 foreign_class->suffs = vips_foreign_nifti_suffs;
432
433 save_class->saveable = VIPS_SAVEABLE_ANY;
434 save_class->format_table = vips_nifti_bandfmt;
435
436 VIPS_ARG_STRING( class, "filename", 1,
437 _( "Filename" ),
438 _( "Filename to save to" ),
439 VIPS_ARGUMENT_REQUIRED_INPUT,
440 G_STRUCT_OFFSET( VipsForeignSaveNifti, filename ),
441 NULL );
442 }
443
444 static void
vips_foreign_save_nifti_init(VipsForeignSaveNifti * nifti)445 vips_foreign_save_nifti_init( VipsForeignSaveNifti *nifti )
446 {
447 }
448
449 #endif /*HAVE_NIFTI*/
450
451 /**
452 * vips_niftisave: (method)
453 * @in: image to save
454 * @filename: file to write to
455 * @...: %NULL-terminated list of optional named arguments
456 *
457 * Write a VIPS image to a file in NIFTI format.
458 *
459 * Use the various NIFTI suffixes to pick the nifti save format.
460 *
461 * See also: vips_image_write_to_file(), vips_niftiload().
462 *
463 * Returns: 0 on success, -1 on error.
464 */
465 int
vips_niftisave(VipsImage * in,const char * filename,...)466 vips_niftisave( VipsImage *in, const char *filename, ... )
467 {
468 va_list ap;
469 int result;
470
471 va_start( ap, filename );
472 result = vips_call_split( "niftisave", ap, in, filename );
473 va_end( ap );
474
475 return( result );
476 }
477