From 7716fa957b24923269a2db6678655408e64cfcfd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 30 Jun 2018 21:57:00 +0100 Subject: [PATCH] nifi header read done --- configure.ac | 8 + libvips/foreign/niftiload.c | 307 ++++++++++++++++++++++++++++++++++-- 2 files changed, 300 insertions(+), 15 deletions(-) diff --git a/configure.ac b/configure.ac index 4fc5c6d9..affe6673 100644 --- a/configure.ac +++ b/configure.ac @@ -516,6 +516,14 @@ PKG_CHECK_MODULES(WIN32_GET_COMMAND_LINE, glib-2.0 >= 2.40, ] ) +# from 2.48 we have g_uint_checked_mul() etc. +PKG_CHECK_MODULES(HAVE_CHECKED_MUL, glib-2.0 >= 2.48, + [AC_DEFINE(HAVE_CHECKED_MUL,1,[define if your glib has checked multiply.]) + ], + [: + ] +) + # check for gtk-doc GTK_DOC_CHECK([1.14],[--flavour no-tmpl]) diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index 5ff2f253..000c7f38 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -34,6 +34,7 @@ /* #define DEBUG */ +#define VIPS_DEBUG #ifdef HAVE_CONFIG_H #include @@ -68,22 +69,291 @@ typedef VipsForeignLoadClass VipsForeignLoadNiftiClass; G_DEFINE_TYPE( VipsForeignLoadNifti, vips_foreign_load_nifti, VIPS_TYPE_FOREIGN_LOAD ); -static int -vips_foreign_load_nifti_is_a( const char *filename ) +/* Map DT_* datatype values to VipsBandFormat. + */ +typedef struct _DT2Vips { + int datatype; + VipsBandFormat fmt; +} DT2Vips; + +static DT2Vips vips_DT2Vips[] = { + { DT_UNSIGNED_CHAR, VIPS_FORMAT_UCHAR }, + { DT_SIGNED_SHORT, VIPS_FORMAT_SHORT }, + { DT_SIGNED_INT, VIPS_FORMAT_INT }, + { DT_FLOAT, VIPS_FORMAT_FLOAT }, + { DT_COMPLEX, VIPS_FORMAT_COMPLEX }, + { DT_DOUBLE, VIPS_FORMAT_DOUBLE }, + { DT_RGB, VIPS_FORMAT_UCHAR } +}; + +/* Slow and horrid version if there's no recent glib. + */ +#ifndef HAVE_CHECKED_MUL +#define g_uint_checked_mul( dest, a, b ) ( \ + ((guint64) a * b) > UINT_MAX ? \ + (*dest = UINT_MAX, FALSE) : \ + (*dest = a * b, TRUE) \ +) +#endif /*HAVE_CHECKED_MUL*/ + +/* All the header fields we attach as metadata. + */ +typedef struct _Field { + char *name; + GType type; + glong offset; +} Field; + +static Field other_fields[] = { + { "ndim", G_TYPE_INT, G_STRUCT_OFFSET( nifti_image, ndim ) }, + { "nx", G_TYPE_INT, G_STRUCT_OFFSET( nifti_image, nx ) }, + { "ny", G_TYPE_INT, G_STRUCT_OFFSET( nifti_image, ny ) }, + { "nz", G_TYPE_INT, G_STRUCT_OFFSET( nifti_image, nz ) }, + { "nt", G_TYPE_INT, G_STRUCT_OFFSET( nifti_image, nt ) }, + { "nu", G_TYPE_INT, G_STRUCT_OFFSET( nifti_image, nu ) }, + { "nv", G_TYPE_INT, G_STRUCT_OFFSET( nifti_image, nv ) }, + { "nw", G_TYPE_INT, G_STRUCT_OFFSET( nifti_image, nw ) }, + + { "dx", G_TYPE_FLOAT, G_STRUCT_OFFSET( nifti_image, dx ) }, + { "dy", G_TYPE_FLOAT, G_STRUCT_OFFSET( nifti_image, dy ) }, + { "dz", G_TYPE_FLOAT, G_STRUCT_OFFSET( nifti_image, dz ) }, + { "dt", G_TYPE_FLOAT, G_STRUCT_OFFSET( nifti_image, dt ) }, + { "du", G_TYPE_FLOAT, G_STRUCT_OFFSET( nifti_image, du ) }, + { "dv", G_TYPE_FLOAT, G_STRUCT_OFFSET( nifti_image, dv ) }, + { "dw", G_TYPE_FLOAT, G_STRUCT_OFFSET( nifti_image, dw ) }, + + { "scl_slope", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, scl_slope ) }, + { "scl_inter", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, scl_inter ) }, + + { "cal_min", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, cal_min ) }, + { "cal_max", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, cal_max ) }, + + { "qform_code", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, qform_code ) }, + { "sform_code", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, sform_code ) }, + + { "freq_dim", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, freq_dim ) }, + { "phase_dim", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, phase_dim ) }, + { "slice_dim", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, slice_dim ) }, + + { "slice_code", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, slice_code ) }, + { "slice_start", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, slice_start ) }, + { "slice_end", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, slice_end ) }, + { "slice_duration", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, slice_duration ) }, + + { "quatern_b", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, quatern_b ) }, + { "quatern_c", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, quatern_c ) }, + { "quatern_d", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, quatern_d ) }, + { "qoffset_x", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, qoffset_x ) }, + { "qoffset_y", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, qoffset_y ) }, + { "qoffset_z", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, qoffset_z ) }, + { "qfac", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, qfac ) }, + + { "sto_xyz00", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[0][0] ) }, + { "sto_xyz01", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[0][1] ) }, + { "sto_xyz02", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[0][2] ) }, + { "sto_xyz03", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[0][3] ) }, + + { "sto_xyz10", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[1][0] ) }, + { "sto_xyz11", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[1][1] ) }, + { "sto_xyz12", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[1][2] ) }, + { "sto_xyz13", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[1][3] ) }, + + { "sto_xyz20", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[2][0] ) }, + { "sto_xyz21", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[2][1] ) }, + { "sto_xyz22", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[2][2] ) }, + { "sto_xyz23", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[2][3] ) }, + + { "sto_xyz30", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[3][0] ) }, + { "sto_xyz31", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[3][1] ) }, + { "sto_xyz32", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[3][2] ) }, + { "sto_xyz33", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, sto_xyz.m[3][3] ) }, + + { "toffset", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, toffset ) }, + + { "xyz_units", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, xyz_units ) }, + { "time_units", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, time_units ) }, + + { "nifti_type", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, nifti_type ) }, + { "intent_code", G_TYPE_INT, + G_STRUCT_OFFSET( nifti_image, intent_code ) }, + { "intent_p1", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, intent_p1 ) }, + { "intent_p2", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, intent_p2 ) }, + { "intent_p3", G_TYPE_FLOAT, + G_STRUCT_OFFSET( nifti_image, intent_p3 ) }, +}; + +/* How I wish glib had something like this :( Just implement the ones we need + * for Field above. + */ +static void +vips_gvalue_read( GValue *value, void *p ) { - nifti_image *nim; + switch( G_VALUE_TYPE( value ) ) { + case G_TYPE_INT: + g_value_set_int( value, *((int *) p) ); + break; - gboolean result; + case G_TYPE_FLOAT: + g_value_set_float( value, *((float *) p) ); + break; - VIPS_DEBUG_MSG( "nifti_is_a: testing \"%s\"\n", filename ); + default: + g_warning( "vips_gvalue_read: unsupported GType %s", + g_type_name( G_VALUE_TYPE( value ) ) ); + } +} - result = FALSE; - if( (nim = nifti_image_read( filename, FALSE )) ) { - nifti_image_free( nim ); - result = TRUE; +static int +vips_foreign_load_nifti_get_header( VipsForeignLoadNifti *nifti, + nifti_image *nim, VipsImage *out ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( nifti ); + + guint width; + guint height; + guint bands; + VipsBandFormat fmt; + int i; + char txt[256]; + + if( nim->ndim < 1 || + nim->ndim > 7 ) { + vips_error( class->nickname, + _( "%d-dimensional images not supported" ), + nim->ndim ); + return( 0 ); + } + for( i = 1; i < 8; i++ ) + if( nim->dim[i] <= 0 ) { + vips_error( class->nickname, + "%s", _( "invalid dimension" ) ); + return( 0 ); } - return( result ); + /* Unfold higher dimensions vertically. bands is updated below for + * DT_RGB. Be careful to avoid height going over 2^31. + */ + bands = 1; + width = (guint) nim->nx; + height = (guint) nim->ny; + for( i = 3; i < 8; i++ ) + if( !g_uint_checked_mul( &height, height, nim->dim[i] ) ) { + vips_error( class->nickname, + "%s", _( "dimension overflow" ) ); + return( 0 ); + } + if( height > INT_MAX ) { + vips_error( class->nickname, "%s", _( "dimension overflow" ) ); + return( 0 ); + } + + /* Decode voxel format. + */ + fmt = VIPS_FORMAT_UCHAR; + for( i = 0; i < VIPS_NUMBER( vips_DT2Vips ); i++ ) + if( nim->datatype == vips_DT2Vips[i].datatype ) { + fmt = vips_DT2Vips[i].fmt; + break; + } + if( i == VIPS_NUMBER( vips_DT2Vips ) ) { + vips_error( class->nickname, + _( "datatype %d not supported" ), nim->datatype ); + return( -1 ); + } + + if( nim->datatype == DT_RGB ) + bands = 3; + if( nim->datatype == DT_RGBA32 ) + bands = 4; + +#ifdef DEBUG + printf( "get_vips_properties: width = %d\n", width ); + printf( "get_vips_properties: height = %d\n", height ); + printf( "get_vips_properties: bands = %d\n", bands ); + printf( "get_vips_properties: fmt = %d\n", fmt ); +#endif /*DEBUG*/ + + vips_image_init_fields( out, + width, height, bands, fmt, + VIPS_CODING_NONE, + bands == 1 ? + VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB, + 1.0, 1.0 ); + + for( i = 0; i < VIPS_NUMBER( other_fields ); i++ ) { + GValue value = { 0 }; + + g_value_init( &value, other_fields[i].type ); + vips_gvalue_read( &value, + (gpointer) nim + other_fields[i].offset ); + vips_snprintf( txt, 256, "nifti-%s", other_fields[i].name ); + vips_image_set( out, txt, &value ); + g_value_unset( &value ); + } + + vips_strncpy( txt, nim->intent_name, 16 ); + txt[16] = '\0'; + vips_image_set_string( out, "nifti-intent_name", txt ); + + vips_strncpy( txt, nim->descrip, 80 ); + txt[80] = '\0'; + vips_image_set_string( out, "nifti-descrip", txt ); + + for( i = 0; i < nim->num_ext; i++ ) { + nifti1_extension *ext = &nim->ext_list[i]; + char *data_copy; + + vips_snprintf( txt, 256, "nifti-ext-%d-%d", i, ext->ecode ); + if( !(data_copy = vips_malloc( NULL, ext->esize )) ) + return( -1 ); + memcpy( data_copy, ext->edata, ext->esize ); + vips_image_set_blob( out, txt, + (VipsCallbackFn) vips_free, data_copy, ext->esize ); + } + + return( 0 ); } static int @@ -103,8 +373,10 @@ vips_foreign_load_nifti_header( VipsForeignLoad *load ) return( 0 ); } - /* Set load->out. - */ + if( vips_foreign_load_nifti_get_header( nifti, nim, load->out ) ) { + nifti_image_free( nim ); + return( -1 ); + } nifti_image_free( nim ); @@ -113,7 +385,12 @@ vips_foreign_load_nifti_header( VipsForeignLoad *load ) return( 0 ); } -const char *vips__nifti_suffs[] = { ".nii", ".nii.gz", NULL }; +const char *vips__nifti_suffs[] = { + ".nii", ".nii.gz", + ".hdr", ".hdr.gz", + ".img", ".img.gz", + ".nia", ".nia.gz", + NULL }; static void vips_foreign_load_nifti_class_init( VipsForeignLoadNiftiClass *class ) @@ -127,7 +404,7 @@ vips_foreign_load_nifti_class_init( VipsForeignLoadNiftiClass *class ) gobject_class->get_property = vips_object_get_property; object_class->nickname = "niftiload"; - object_class->description = _( "load a FITS image" ); + object_class->description = _( "load a NIFTI image" ); /* is_a() is not that quick ... lower the priority. */ @@ -135,7 +412,7 @@ vips_foreign_load_nifti_class_init( VipsForeignLoadNiftiClass *class ) foreign_class->suffs = vips__nifti_suffs; - load_class->is_a = vips_foreign_load_nifti_is_a; + load_class->is_a = is_nifti_file; load_class->header = vips_foreign_load_nifti_header; VIPS_ARG_STRING( class, "filename", 1,