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