From 93881b8decb07a28f3a8661e3ac9b6d3ca65f52d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 29 Jun 2018 16:32:28 +0100 Subject: [PATCH 01/17] add FIND_NIFTI looks for libniftiio --- configure.ac | 14 ++++++ m4/nifti.m4 | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 m4/nifti.m4 diff --git a/configure.ac b/configure.ac index c09495e8..bb9b02a6 100644 --- a/configure.ac +++ b/configure.ac @@ -842,6 +842,19 @@ if test x"$with_OpenEXR" != x"no"; then ) fi +# nifti +AC_ARG_WITH([nifti], + AS_HELP_STRING([--without-nifti], [build without nifti (default: test)])) + +if test x"$with_nifti" != x"no"; then + FIND_NIFTI([ + with_nifti=yes + ],[ + with_nifti=no + ] + ) +fi + # pdfium AC_ARG_WITH([pdfium], AS_HELP_STRING([--without-pdfium], [build without pdfium (default: test)])) @@ -1375,6 +1388,7 @@ save with libMagick: $enable_magicksave accelerate loops with orc: $with_orc (requires orc-0.4.11 or later) ICC profile support with lcms: $with_lcms +file import with niftiio: $with_nifti file import with OpenEXR: $with_OpenEXR file import with OpenSlide: $with_openslide (requires openslide-3.3.0 or later) diff --git a/m4/nifti.m4 b/m4/nifti.m4 new file mode 100644 index 00000000..0abf9008 --- /dev/null +++ b/m4/nifti.m4 @@ -0,0 +1,119 @@ +dnl From FIND_MOTIF and ACX_PTHREAD, without much understanding +dnl +dnl FIND_NIFTI[ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]] +dnl ------------------------------------------------ +dnl +dnl Find NIFTI libraries and headers +dnl +dnl Put compile stuff in NIFTI_INCLUDES +dnl Put link stuff in NIFTI_LIBS +dnl Define HAVE_NIFTI if found +dnl +AC_DEFUN([FIND_NIFTI], [ +AC_REQUIRE([AC_PATH_XTRA]) + +NIFTI_INCLUDES="" +NIFTI_LIBS="" + +AC_ARG_WITH(nifti, + AS_HELP_STRING([--without-nifti], [build without nifti (default: test)])) +# Treat --without-nifti like --without-nifti-includes --without-nifti-libraries. +if test "$with_nifti" = "no"; then + NIFTI_INCLUDES=no + NIFTI_LIBS=no +fi + +AC_ARG_WITH(nifti-includes, + AS_HELP_STRING([--with-nifti-includes=DIR], [libnifti includes are in DIR]), + NIFTI_INCLUDES="-I$withval") +AC_ARG_WITH(nifti-libraries, + AS_HELP_STRING([--with-nifti-libraries=DIR], [libnifti libraries are in DIR]), + NIFTI_LIBS="-L$withval -lnifti") + +AC_MSG_CHECKING(for NIFTI) + +# Look for nifti1_io.h ... usually in /usr/include/nifti +if test "$NIFTI_INCLUDES" = ""; then + nifti_save_CFLAGS="$CFLAGS" + + # annoyingly, the header must be unqualified, so we have to add to the + # search path + CFLAGS="-I/usr/include/nifti $nifti_save_CFLAGS" + + AC_TRY_COMPILE([#include ],[int a;],[ + NIFTI_INCLUDES="-I/usr/include/nifti" + ], [ + # not in the standard search path, try $prefix + CFLAGS="-I${prefix}/include/nifti $nifti_save_CFLAGS" + + AC_TRY_COMPILE([#include ],[int a;],[ + NIFTI_INCLUDES="-I${prefix}/include/nifti" + ], [ + NIFTI_INCLUDES="no" + ]) + ]) + + CFLAGS="$nifti_save_CFLAGS" +fi + +# Now for the libraries +if test "$NIFTI_LIBS" = ""; then + nifti_save_LIBS="$LIBS" + nifti_save_CFLAGS="$CFLAGS" + + LIBS="-lniftiio -lm $nifti_save_LIBS" + CFLAGS="$NIFTI_INCLUDES $CFLAGS" + + # Try the standard search path first + AC_TRY_LINK([#include ],[is_nifti_file("")], [ + NIFTI_LIBS="-lniftiio" + ], [ + # libniftiio is not in the standard search path, try $prefix + + LIBS="-L${prefix}/lib $LIBS" + + AC_TRY_LINK([#include ],[is_nifti_file("")], [ + NIFTI_LIBS="-L${prefix}/lib -lniftiio" + ], [ + NIFTI_LIBS=no + ]) + ]) + + LIBS="$nifti_save_LIBS" + CFLAGS="$nifti_save_CFLAGS" +fi + +AC_SUBST(NIFTI_LIBS) +AC_SUBST(NIFTI_INCLUDES) + +# Print a helpful message +nifti_libraries_result="$NIFTI_LIBS" +nifti_includes_result="$NIFTI_INCLUDES" + +if test x"$nifti_libraries_result" = x""; then + nifti_libraries_result="in default path" +fi +if test x"$nifti_includes_result" = x""; then + nifti_includes_result="in default path" +fi + +if test "$nifti_libraries_result" = "no"; then + nifti_libraries_result="(none)" +fi +if test "$nifti_includes_result" = "no"; then + nifti_includes_result="(none)" +fi + +AC_MSG_RESULT([libraries $nifti_libraries_result, headers $nifti_includes_result]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "$NIFTI_INCLUDES" != "no" && test "$NIFTI_LIBS" != "no"; then + AC_DEFINE(HAVE_NIFTI,1,[Define if you have nifti libraries and header files.]) + $1 +else + NIFTI_INCLUDES="" + NIFTI_LIBS="" + $2 +fi + +])dnl From 6dc6b4f7224c8c3f7adfdcabbd805edaa99d13fc Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 29 Jun 2018 17:31:17 +0100 Subject: [PATCH 02/17] add niftiload skeleton --- configure.ac | 4 +- libvips/foreign/Makefile.am | 1 + libvips/foreign/foreign.c | 5 + libvips/foreign/niftiload.c | 181 +++++++++++++++++++++++++++++++++ libvips/include/vips/foreign.h | 3 + 5 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 libvips/foreign/niftiload.c diff --git a/configure.ac b/configure.ac index bb9b02a6..4fc5c6d9 100644 --- a/configure.ac +++ b/configure.ac @@ -1297,8 +1297,8 @@ do done | sort -ru` VIPS_CFLAGS=`echo $VIPS_CFLAGS` VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS" -VIPS_INCLUDES="$ZLIB_INCLUDES $PNG_INCLUDES $TIFF_INCLUDES $JPEG_INCLUDES" -VIPS_LIBS="$ZLIB_LIBS $MAGICK_LIBS $PNG_LIBS $IMAGEQUANT_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" +VIPS_INCLUDES="$ZLIB_INCLUDES $PNG_INCLUDES $TIFF_INCLUDES $JPEG_INCLUDES $NIFTI_INCLUDES" +VIPS_LIBS="$ZLIB_LIBS $MAGICK_LIBS $PNG_LIBS $IMAGEQUANT_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $NIFTI_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" AC_SUBST(VIPS_LIBDIR) diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index 58a33053..c74d94b3 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -2,6 +2,7 @@ noinst_LTLIBRARIES = libforeign.la libforeign_la_SOURCES = \ pforeign.h \ + niftiload.c \ quantise.c \ exif.c \ gifload.c \ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 276edb12..74c935f2 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1836,6 +1836,7 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_load_svg_get_type( void ); extern GType vips_foreign_load_svg_file_get_type( void ); extern GType vips_foreign_load_svg_buffer_get_type( void ); + extern GType vips_foreign_load_nifti_get_type( void ); extern GType vips_foreign_load_gif_get_type( void ); extern GType vips_foreign_load_gif_file_get_type( void ); extern GType vips_foreign_load_gif_buffer_get_type( void ); @@ -1958,6 +1959,10 @@ vips_foreign_operation_init( void ) vips_foreign_load_openexr_get_type(); #endif /*HAVE_OPENEXR*/ +#ifdef HAVE_NIFTI + vips_foreign_load_nifti_get_type(); +#endif /*HAVE_NIFTI*/ + vips__foreign_load_operation = g_quark_from_static_string( "vips-foreign-load-operation" ); } diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c new file mode 100644 index 00000000..5ff2f253 --- /dev/null +++ b/libvips/foreign/niftiload.c @@ -0,0 +1,181 @@ +/* load nifti from a file + * + * 29/6/18 + * - from niftiload.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_NIFTI + +#include + +#include "pforeign.h" + +typedef struct _VipsForeignLoadNifti { + VipsForeignLoad parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadNifti; + +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 ) +{ + nifti_image *nim; + + gboolean result; + + VIPS_DEBUG_MSG( "nifti_is_a: testing \"%s\"\n", filename ); + + result = FALSE; + if( (nim = nifti_image_read( filename, FALSE )) ) { + nifti_image_free( nim ); + result = TRUE; + } + + return( result ); +} + +static int +vips_foreign_load_nifti_header( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) load; + + nifti_image *nim; + + /* FALSE means don't read data, just the header. Use + * nifti_image_load() later to pull the data in. + */ + if( !(nim = nifti_image_read( nifti->filename, FALSE )) ) { + vips_error( class->nickname, + "%s", _( "unable to read NIFTI file" ) ); + return( 0 ); + } + + /* Set load->out. + */ + + nifti_image_free( nim ); + + VIPS_SETSTR( load->out->filename, nifti->filename ); + + return( 0 ); +} + +const char *vips__nifti_suffs[] = { ".nii", ".nii.gz", NULL }; + +static void +vips_foreign_load_nifti_class_init( VipsForeignLoadNiftiClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "niftiload"; + object_class->description = _( "load a FITS image" ); + + /* is_a() is not that quick ... lower the priority. + */ + foreign_class->priority = -50; + + foreign_class->suffs = vips__nifti_suffs; + + load_class->is_a = vips_foreign_load_nifti_is_a; + load_class->header = vips_foreign_load_nifti_header; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadNifti, filename ), + NULL ); +} + +static void +vips_foreign_load_nifti_init( VipsForeignLoadNifti *nifti ) +{ +} + +#endif /*HAVE_CFITSIO*/ + +/** + * vips_niftiload: + * @filename: file to load + * @out: (out): decompressed image + * @...: %NULL-terminated list of optional named arguments + * + * Read a NIFTI image file into a VIPS image. + * + * NIFTI metadata is attached with the "nifti-" prefix. + * + * See also: vips_image_new_from_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_niftiload( const char *filename, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "niftiload", ap, filename, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 3e7ed922..57d0b321 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -560,6 +560,9 @@ int vips_gifload( const char *filename, VipsImage **out, ... ) int vips_gifload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_niftiload( const char *filename, VipsImage **out, ... ) + __attribute__((sentinel)); + /** * VipsForeignDzLayout: * @VIPS_FOREIGN_DZ_LAYOUT_DZ: use DeepZoom directory layout From 7716fa957b24923269a2db6678655408e64cfcfd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 30 Jun 2018 21:57:00 +0100 Subject: [PATCH 03/17] 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, From 06e8d1d5235004d974f15e48d63820c03f76463e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 2 Jul 2018 14:57:24 +0100 Subject: [PATCH 04/17] load seems to work --- libvips/foreign/niftiload.c | 151 ++++++++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 17 deletions(-) diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index 000c7f38..ad6429b4 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -1,7 +1,7 @@ /* load nifti from a file * * 29/6/18 - * - from niftiload.c + * - from fitsload.c */ /* @@ -33,8 +33,13 @@ /* #define DEBUG - */ #define VIPS_DEBUG + */ + +/* TODO + * - for uncompressed images, we could offer direct mapping of the input + * - could stream the image perhaps? + */ #ifdef HAVE_CONFIG_H #include @@ -62,6 +67,8 @@ typedef struct _VipsForeignLoadNifti { */ char *filename; + nifti_image *nim; + } VipsForeignLoadNifti; typedef VipsForeignLoadClass VipsForeignLoadNiftiClass; @@ -69,6 +76,17 @@ typedef VipsForeignLoadClass VipsForeignLoadNiftiClass; G_DEFINE_TYPE( VipsForeignLoadNifti, vips_foreign_load_nifti, VIPS_TYPE_FOREIGN_LOAD ); +static void +vips_foreign_load_nifti_dispose( GObject *gobject ) +{ + VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) gobject; + + VIPS_FREEF( nifti_image_free, nifti->nim ); + + G_OBJECT_CLASS( vips_foreign_load_nifti_parent_class )-> + dispose( gobject ); +} + /* Map DT_* datatype values to VipsBandFormat. */ typedef struct _DT2Vips { @@ -246,7 +264,7 @@ vips_gvalue_read( GValue *value, void *p ) } static int -vips_foreign_load_nifti_get_header( VipsForeignLoadNifti *nifti, +vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, nifti_image *nim, VipsImage *out ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( nifti ); @@ -255,6 +273,8 @@ vips_foreign_load_nifti_get_header( VipsForeignLoadNifti *nifti, guint height; guint bands; VipsBandFormat fmt; + double xres; + double yres; int i; char txt[256]; @@ -265,11 +285,23 @@ vips_foreign_load_nifti_get_header( VipsForeignLoadNifti *nifti, nim->ndim ); return( 0 ); } - for( i = 1; i < 8; i++ ) + for( i = 1; i < 8; i++ ) { if( nim->dim[i] <= 0 ) { vips_error( class->nickname, "%s", _( "invalid dimension" ) ); return( 0 ); + } + + /* If we have several images in a dimension, the spacing must + * be non-zero, or we'll get a /0 error in resolution + * calculation. + */ + if( nim->dim[i] > 1 && + nim->pixdim[i] == 0 ) { + vips_error( class->nickname, + "%s", _( "invalid resolution" ) ); + return( 0 ); + } } /* Unfold higher dimensions vertically. bands is updated below for @@ -308,6 +340,31 @@ vips_foreign_load_nifti_get_header( VipsForeignLoadNifti *nifti, if( nim->datatype == DT_RGBA32 ) bands = 4; + /* We fold y and z together, so they must have the same resolution.. + */ + xres = 1.0; + yres = 1.0; + if( nim->nz == 1 || + nim->dz == nim->dy ) + switch( nim->xyz_units ) { + case NIFTI_UNITS_METER: + xres = 1000.0 / nim->dx; + yres = 1000.0 / nim->dy; + break; + case NIFTI_UNITS_MM: + xres = 1.0 / nim->dx; + yres = 1.0 / nim->dy; + break; + + case NIFTI_UNITS_MICRON: + xres = 1.0 / (1000.0 * nim->dx); + yres = 1.0 / (1000.0 * nim->dy); + break; + + default: + break; + } + #ifdef DEBUG printf( "get_vips_properties: width = %d\n", width ); printf( "get_vips_properties: height = %d\n", height ); @@ -315,12 +372,13 @@ vips_foreign_load_nifti_get_header( VipsForeignLoadNifti *nifti, printf( "get_vips_properties: fmt = %d\n", fmt ); #endif /*DEBUG*/ + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL ); 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 ); + xres, yres ); for( i = 0; i < VIPS_NUMBER( other_fields ); i++ ) { GValue value = { 0 }; @@ -333,12 +391,12 @@ vips_foreign_load_nifti_get_header( VipsForeignLoadNifti *nifti, g_value_unset( &value ); } - vips_strncpy( txt, nim->intent_name, 16 ); - txt[16] = '\0'; + /* One byte longer than the spec to leave space for any extra + * '\0' termination. + */ + vips_strncpy( txt, nim->intent_name, 17 ); vips_image_set_string( out, "nifti-intent_name", txt ); - - vips_strncpy( txt, nim->descrip, 80 ); - txt[80] = '\0'; + vips_strncpy( txt, nim->descrip, 81 ); vips_image_set_string( out, "nifti-descrip", txt ); for( i = 0; i < nim->num_ext; i++ ) { @@ -362,29 +420,86 @@ vips_foreign_load_nifti_header( VipsForeignLoad *load ) VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) load; - nifti_image *nim; + + /* We can't use the (much faster) nifti_read_header() since it just + * reads the 348 bytes iof the analyze struct and does not read any of + * the extension fields. + */ /* FALSE means don't read data, just the header. Use * nifti_image_load() later to pull the data in. */ - if( !(nim = nifti_image_read( nifti->filename, FALSE )) ) { + if( !(nifti->nim = nifti_image_read( nifti->filename, FALSE )) ) { vips_error( class->nickname, - "%s", _( "unable to read NIFTI file" ) ); + "%s", _( "unable to read NIFTI header" ) ); return( 0 ); } - if( vips_foreign_load_nifti_get_header( nifti, nim, load->out ) ) { - nifti_image_free( nim ); + if( vips_foreign_load_nifti_set_header( nifti, + nifti->nim, load->out ) ) { return( -1 ); } - nifti_image_free( nim ); - VIPS_SETSTR( load->out->filename, nifti->filename ); return( 0 ); } +static int +vips_foreign_load_nifti_generate( VipsRegion *out, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) a; + VipsRect *r = &out->valid; + size_t len = VIPS_REGION_SIZEOF_LINE( out ); + size_t line_stride = VIPS_IMAGE_SIZEOF_LINE( out->im ); + size_t pixel_stride = VIPS_IMAGE_SIZEOF_PEL( out->im ); + + int y; + + VIPS_DEBUG_MSG( "vips_foreign_load_nifti_generate: " + "generating left = %d, top = %d, width = %d, height = %d\n", + r->left, r->top, r->width, r->height ); + + for( y = r->top; y < VIPS_RECT_BOTTOM( r ); y++ ) { + VipsPel *q = VIPS_REGION_ADDR( out, r->left, y ); + VipsPel *p = (VipsPel *) nifti->nim->data + + y * line_stride + + r->left * pixel_stride; + + memcpy( q, p, len ); + } + + return( 0 ); +} + +static int +vips_foreign_load_nifti_load( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) load; + +#ifdef DEBUG + printf( "vips_foreign_load_nifti_load: loading image\n" ); +#endif /*DEBUG*/ + + if( nifti_image_load( nifti->nim ) ) { + vips_error( class->nickname, + "%s", _( "unable to load NIFTI file" ) ); + return( -1 ); + } + + if( vips_foreign_load_nifti_set_header( nifti, + nifti->nim, load->real ) ) + return( -1 ); + + if( vips_image_generate( load->real, + NULL, vips_foreign_load_nifti_generate, NULL, nifti, NULL ) ) + return( -1 ); + + return( 0 ); +} + const char *vips__nifti_suffs[] = { ".nii", ".nii.gz", ".hdr", ".hdr.gz", @@ -400,6 +515,7 @@ vips_foreign_load_nifti_class_init( VipsForeignLoadNiftiClass *class ) VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + gobject_class->dispose = vips_foreign_load_nifti_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; @@ -414,6 +530,7 @@ vips_foreign_load_nifti_class_init( VipsForeignLoadNiftiClass *class ) load_class->is_a = is_nifti_file; load_class->header = vips_foreign_load_nifti_header; + load_class->load = vips_foreign_load_nifti_load; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), From 294104da32e7223d5e01fd51ed63bbe0a62e501c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 2 Jul 2018 15:15:37 +0100 Subject: [PATCH 05/17] more TODO notes on niftiload --- libvips/foreign/niftiload.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index ad6429b4..9fa228bc 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -37,8 +37,14 @@ */ /* TODO - * - for uncompressed images, we could offer direct mapping of the input - * - could stream the image perhaps? + * + * - for uncompressed images, we could do direct mapping of the input + * - perhaps we could stream compressed images? but only if ext is defined at + * the start of the file + * - we could use the much faster byteswap in glib? + * - I have not been able to test the ext stuff :( + * + * There should be at least a x2 speedup possible. */ #ifdef HAVE_CONFIG_H From b0168a3cf6b4d3e6f7a677f51eb538f6be97bb3f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 5 Jul 2018 15:22:11 +0100 Subject: [PATCH 06/17] remove a copy on load --- libvips/foreign/niftiload.c | 51 +++++++++++++------------------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index 9fa228bc..7372fcab 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -73,8 +73,15 @@ typedef struct _VipsForeignLoadNifti { */ char *filename; + /* The NIFTI image loaded to memory. + */ nifti_image *nim; + /* Wrap this VipsImage around the NIFTI pointer, then redirect read + * requests to that. Saves a copy. + */ + VipsImage *memory; + } VipsForeignLoadNifti; typedef VipsForeignLoadClass VipsForeignLoadNiftiClass; @@ -87,6 +94,7 @@ vips_foreign_load_nifti_dispose( GObject *gobject ) { VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) gobject; + VIPS_UNREF( nifti->memory ); VIPS_FREEF( nifti_image_free, nifti->nim ); G_OBJECT_CLASS( vips_foreign_load_nifti_parent_class )-> @@ -417,6 +425,9 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, (VipsCallbackFn) vips_free, data_copy, ext->esize ); } + if( nim->ny > 1 ) + vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, nim->ny ); + return( 0 ); } @@ -426,7 +437,6 @@ vips_foreign_load_nifti_header( VipsForeignLoad *load ) VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) load; - /* We can't use the (much faster) nifti_read_header() since it just * reads the 348 bytes iof the analyze struct and does not read any of * the extension fields. @@ -451,34 +461,6 @@ vips_foreign_load_nifti_header( VipsForeignLoad *load ) return( 0 ); } -static int -vips_foreign_load_nifti_generate( VipsRegion *out, - void *seq, void *a, void *b, gboolean *stop ) -{ - VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) a; - VipsRect *r = &out->valid; - size_t len = VIPS_REGION_SIZEOF_LINE( out ); - size_t line_stride = VIPS_IMAGE_SIZEOF_LINE( out->im ); - size_t pixel_stride = VIPS_IMAGE_SIZEOF_PEL( out->im ); - - int y; - - VIPS_DEBUG_MSG( "vips_foreign_load_nifti_generate: " - "generating left = %d, top = %d, width = %d, height = %d\n", - r->left, r->top, r->width, r->height ); - - for( y = r->top; y < VIPS_RECT_BOTTOM( r ); y++ ) { - VipsPel *q = VIPS_REGION_ADDR( out, r->left, y ); - VipsPel *p = (VipsPel *) nifti->nim->data + - y * line_stride + - r->left * pixel_stride; - - memcpy( q, p, len ); - } - - return( 0 ); -} - static int vips_foreign_load_nifti_load( VipsForeignLoad *load ) { @@ -489,18 +471,21 @@ vips_foreign_load_nifti_load( VipsForeignLoad *load ) printf( "vips_foreign_load_nifti_load: loading image\n" ); #endif /*DEBUG*/ + /* We just read the entire image to memory. + */ if( nifti_image_load( nifti->nim ) ) { vips_error( class->nickname, "%s", _( "unable to load NIFTI file" ) ); return( -1 ); } - if( vips_foreign_load_nifti_set_header( nifti, - nifti->nim, load->real ) ) + if( !(nifti->memory = vips_image_new_from_memory( + nifti->nim->data, VIPS_IMAGE_SIZEOF_IMAGE( load->out ), + load->out->Xsize, load->out->Ysize, + load->out->Bands, load->out->BandFmt )) ) return( -1 ); - if( vips_image_generate( load->real, - NULL, vips_foreign_load_nifti_generate, NULL, nifti, NULL ) ) + if( vips_copy( nifti->memory, &load->real, NULL ) ) return( -1 ); return( 0 ); From 76eef8912e20fb0c5591eca220a4eba988d62bf1 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 5 Jul 2018 17:38:37 +0100 Subject: [PATCH 07/17] start niftisave --- libvips/foreign/niftiload.c | 19 +++-- libvips/foreign/niftisave.c | 159 ++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 libvips/foreign/niftisave.c diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index 7372fcab..a649c868 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -109,13 +109,18 @@ typedef struct _DT2Vips { } 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 } + { DT_UINT8, VIPS_FORMAT_UCHAR }, + { DT_INT8, VIPS_FORMAT_CHAR }, + { DT_UINT16, VIPS_FORMAT_USHORT }, + { DT_INT16, VIPS_FORMAT_SHORT }, + { DT_UINT32, VIPS_FORMAT_UINT }, + { DT_INT32, VIPS_FORMAT_INT }, + { DT_FLOAT32, VIPS_FORMAT_FLOAT }, + { DT_FLOAT64, VIPS_FORMAT_DOUBLE }, + { DT_COMPLEX64, VIPS_FORMAT_COMPLEX }, + { DT_COMPLEX128, VIPS_FORMAT_DPCOMPLEX }, + { DT_RGB, VIPS_FORMAT_UCHAR }, + { DT_RGBA32, VIPS_FORMAT_UCHAR } }; /* Slow and horrid version if there's no recent glib. diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c new file mode 100644 index 00000000..e5e2cd9a --- /dev/null +++ b/libvips/foreign/niftisave.c @@ -0,0 +1,159 @@ +/* save to nifti + * + * 5/7/18 + * - from fitssave.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG_VERBOSE +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include + +#ifdef HAVE_CFITSIO + +#include "pforeign.h" + +typedef struct _VipsForeignSaveNifti { + VipsForeignSave parent_object; + + /* Filename for save. + */ + char *filename; + +} VipsForeignSaveNifti; + +typedef VipsForeignSaveClass VipsForeignSaveNiftiClass; + +G_DEFINE_TYPE( VipsForeignSaveNifti, vips_foreign_save_nifti, + VIPS_TYPE_FOREIGN_SAVE ); + +static int +vips_foreign_save_nifti_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveNifti *nifti = (VipsForeignSaveNifti *) object; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( nifti ), 2 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_save_nifti_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +/* Save a bit of typing. + */ +#define UC VIPS_FORMAT_UCHAR +#define C VIPS_FORMAT_CHAR +#define US VIPS_FORMAT_USHORT +#define S VIPS_FORMAT_SHORT +#define UI VIPS_FORMAT_UINT +#define I VIPS_FORMAT_INT +#define F VIPS_FORMAT_FLOAT +#define X VIPS_FORMAT_COMPLEX +#define D VIPS_FORMAT_DOUBLE +#define DX VIPS_FORMAT_DPCOMPLEX + +static int vips_nifti_bandfmt[10] = { +/* UC C US S UI I F X D DX */ + UC, C, US, S, UI, I, F, X, D, DX +}; + +static void +vips_foreign_save_nifti_class_init( VipsForeignSaveNiftiClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "niftisave"; + object_class->description = _( "save image to nifti file" ); + object_class->build = vips_foreign_save_nifti_build; + + foreign_class->suffs = vips__nifti_suffs; + + save_class->saveable = VIPS_SAVEABLE_ANY; + save_class->format_table = vips_nifti_bandfmt; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveNifti, filename ), + NULL ); +} + +static void +vips_foreign_save_nifti_init( VipsForeignSaveNifti *nifti ) +{ +} + +#endif /*HAVE_CFITSIO*/ + +/** + * vips_niftisave: (method) + * @in: image to save + * @filename: file to write to + * @...: %NULL-terminated list of optional named arguments + * + * Write a VIPS image to a file in NIFTI format. + * + * See also: vips_image_write_to_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_niftisave( VipsImage *in, const char *filename, ... ) +{ + va_list ap; + int result; + + va_start( ap, filename ); + result = vips_call_split( "niftisave", ap, in, filename ); + va_end( ap ); + + return( result ); +} From 34bb1fc83c2bd8669fd7c661ca2d65a286cbeada Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 6 Jul 2018 11:53:35 +0100 Subject: [PATCH 08/17] save framework done --- libvips/foreign/niftiload.c | 2 ++ libvips/foreign/niftisave.c | 46 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index a649c868..6523f7d1 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -41,6 +41,8 @@ * - for uncompressed images, we could do direct mapping of the input * - perhaps we could stream compressed images? but only if ext is defined at * the start of the file + * (yes, file format is magic number, 348-byte header, extension data, + * pixel data, then all gz'd) * - we could use the much faster byteswap in glib? * - I have not been able to test the ext stuff :( * diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c index e5e2cd9a..f40f5b85 100644 --- a/libvips/foreign/niftisave.c +++ b/libvips/foreign/niftisave.c @@ -58,6 +58,8 @@ typedef struct _VipsForeignSaveNifti { */ char *filename; + nifti_image *nim; + } VipsForeignSaveNifti; typedef VipsForeignSaveClass VipsForeignSaveNiftiClass; @@ -65,6 +67,26 @@ typedef VipsForeignSaveClass VipsForeignSaveNiftiClass; G_DEFINE_TYPE( VipsForeignSaveNifti, vips_foreign_save_nifti, VIPS_TYPE_FOREIGN_SAVE ); +static void +vips_foreign_save_nifti_dispose( GObject *gobject ) +{ + VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) gobject; + + VIPS_FREEF( nifti_image_free, nifti->nim ); + + G_OBJECT_CLASS( vips_foreign_load_nifti_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_save_nifti_make_header( VipsForeignSaveNifti *nifti, + struct nifti_1_header *nhdr ) +{ + + + return( 0 ); +} + static int vips_foreign_save_nifti_build( VipsObject *object ) { @@ -73,10 +95,33 @@ vips_foreign_save_nifti_build( VipsObject *object ) VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( nifti ), 2 ); + struct nifti_1_header nhdr; + if( VIPS_OBJECT_CLASS( vips_foreign_save_nifti_parent_class )-> build( object ) ) return( -1 ); + if( vips_foreign_save_nifti_make_header( nifti, &nhdr ) ) + return( -1 ); + + if( !(nifti->nim = nifti_convert_nhdr2nim( nhdr, nifti->filename )) ) + return( -1 ); + + /* set ext, plus other stuff + */ + + if( !(nim->data = vips_image_write_memory( save->ready, NULL )) ) + return( -1 ); + + /* No return code!??!?!! + */ + nifti_image_write( nifti->nim ); + + /* We must free and NULL the pointer or nifti will try to free it for + * us. + */ + VIPS_FREE( nim->data ); + return( 0 ); } @@ -106,6 +151,7 @@ vips_foreign_save_nifti_class_init( VipsForeignSaveNiftiClass *class ) VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + gobject_class->dispose = vips_foreign_save_nifti_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; From d186ac69d6ed4b85555f52745c568be5f1dec7bd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 10 Jul 2018 17:16:49 +0100 Subject: [PATCH 09/17] a more more work on niftisave --- .gitignore | 1 + libvips/foreign/niftiload.c | 31 ++++++++----------------- libvips/foreign/niftisave.c | 46 ++++++++++++++++++++++++++++++++++--- libvips/foreign/pforeign.h | 19 +++++++++++++++ 4 files changed, 73 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index c91c0ee7..d7989938 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.pytest_cache compile a.out *.log diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index 6523f7d1..e622fab3 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -103,14 +103,7 @@ vips_foreign_load_nifti_dispose( GObject *gobject ) dispose( gobject ); } -/* Map DT_* datatype values to VipsBandFormat. - */ -typedef struct _DT2Vips { - int datatype; - VipsBandFormat fmt; -} DT2Vips; - -static DT2Vips vips_DT2Vips[] = { +VipsForeignDT2Vips vips_foreign_DT2Vips[] = { { DT_UINT8, VIPS_FORMAT_UCHAR }, { DT_INT8, VIPS_FORMAT_CHAR }, { DT_UINT16, VIPS_FORMAT_USHORT }, @@ -135,15 +128,10 @@ static DT2Vips vips_DT2Vips[] = { ) #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[] = { +VipsForeignNiftiFields vips_foreign_nifti_fields[] = { + /* The first 8 must be the dims[] fields, see + * vips_foreign_save_nifti_make_nim(). + */ { "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 ) }, @@ -401,13 +389,14 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB, xres, yres ); - for( i = 0; i < VIPS_NUMBER( other_fields ); i++ ) { + for( i = 0; i < VIPS_NUMBER( vips_foreign_nifti_fields ); i++ ) { GValue value = { 0 }; - g_value_init( &value, other_fields[i].type ); + g_value_init( &value, vips_foreign_nifti_fields[i].type ); vips_gvalue_read( &value, - (gpointer) nim + other_fields[i].offset ); - vips_snprintf( txt, 256, "nifti-%s", other_fields[i].name ); + (gpointer) nim + vips_foreign_nifti_fields[i].offset ); + vips_snprintf( txt, 256, "nifti-%s", + vips_foreign_nifti_fields[i].name ); vips_image_set( out, txt, &value ); g_value_unset( &value ); } diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c index f40f5b85..80c51fa2 100644 --- a/libvips/foreign/niftisave.c +++ b/libvips/foreign/niftisave.c @@ -79,10 +79,50 @@ vips_foreign_save_nifti_dispose( GObject *gobject ) } static int -vips_foreign_save_nifti_make_header( VipsForeignSaveNifti *nifti, - struct nifti_1_header *nhdr ) +vips_foreign_save_nifti_make_nim( VipsForeignSaveNifti *nifti, + VipsImage *image ) { + int dims[8]; + int datatype; + int height; + int i; + /* The first 8 members of vips_foreign_nifti_fields[] are the dims + * fields. + */ + for( i = 0; i < VIPS_NUMBER( dims ); i++ ) { + static char name[256]; + + vips_snprintf( name, 256, "nifti-%s", + vips_foreign_nifti_fields[i].name ); + if( vips_image_get_int( image, name, &dims[i] ) ) + return( -1 ); + } + + height = 1; + for( i = 2; i < VIPS_NUMBER( dims ) && i < dims[0]; i++ ) + height *= dims[i]; + if( images->Xsize != dims[1] || + images->Ysize != height ) { + vips_error( class->nickname, + "%s", _( "bad image dimensions" ) ); + return( -1 ); + } + + datatype = -1; + for( i = 0; i < vips_foreign_DT2Vips; i++ ) + if( vips_foreign_DT2Vips[i].format == image.format ) { + datatype = vips_foreign_DT2Vips[i].datatype; + break; + } + if( datatype == -1 ) { + vips_error( class->nickname, + "%s", _( "unsupported libvips image type" ) ); + return( -1 ); + } + + if( !(nnifti->nim = nifti_make_new_nim( dims, datatype, FALSE )) ) + return( -1 ); return( 0 ); } @@ -101,7 +141,7 @@ vips_foreign_save_nifti_build( VipsObject *object ) build( object ) ) return( -1 ); - if( vips_foreign_save_nifti_make_header( nifti, &nhdr ) ) + if( vips_foreign_save_nifti_make_header( nifti, save->ready, &nhdr ) ) return( -1 ); if( !(nifti->nim = nifti_convert_nhdr2nim( nhdr, nifti->filename )) ) diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index c18c634e..7179c501 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -256,6 +256,25 @@ int vips__quantise_image( VipsImage *in, VipsImage **index_out, VipsImage **palette_out, int colours, int Q, double dither ); +/* Map DT_* datatype values to VipsBandFormat. + */ +typedef struct _VipsForeignDT2Vips { + int datatype; + VipsBandFormat fmt; +} VipsForeignDT2Vips + +extern VipsForeignDT2Vips vips_foreign_DT2Vips[]; + +/* All the header fields we attach as metadata. + */ +typedef struct _VipsForeignNiftiFields { + char *name; + GType type; + glong offset; +} VipsForeignNiftiFields; + +VipsForeignNiftiFields vips_foreign_nifti_fields[]; + #ifdef __cplusplus } #endif /*__cplusplus*/ From 7496381fc9c69ecae07d9036bbd890c16df46663 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 13 Jul 2018 13:08:23 +0100 Subject: [PATCH 10/17] more getting ready for niftisave --- libvips/foreign/niftiload.c | 124 ++++++++++++++++++++++++++---------- libvips/foreign/niftisave.c | 34 +++++++--- libvips/foreign/pforeign.h | 22 ++----- 3 files changed, 123 insertions(+), 57 deletions(-) diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index e622fab3..4d9e544e 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -103,7 +103,14 @@ vips_foreign_load_nifti_dispose( GObject *gobject ) dispose( gobject ); } -VipsForeignDT2Vips vips_foreign_DT2Vips[] = { +/* Map DT_* datatype values to VipsBandFormat. + */ +typedef struct _VipsForeignDT2Vips { + int datatype; + VipsBandFormat fmt; +} VipsForeignDT2Vips ; + +static VipsForeignDT2Vips vips_foreign_nifti_DT2Vips[] = { { DT_UINT8, VIPS_FORMAT_UCHAR }, { DT_INT8, VIPS_FORMAT_CHAR }, { DT_UINT16, VIPS_FORMAT_USHORT }, @@ -118,17 +125,39 @@ VipsForeignDT2Vips vips_foreign_DT2Vips[] = { { DT_RGBA32, 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*/ +VipsBandFormat +vips__foreign_nifti_datatype2BandFmt( int datatype ) +{ + int i; -VipsForeignNiftiFields vips_foreign_nifti_fields[] = { + for( i = 0; i < VIPS_NUMBER( vips_foreign_nifti_DT2Vips ); i++ ) + if( vips_foreign_nifti_DT2Vips[i].datatype == datatype ) + return( vips_foreign_nifti_DT2Vips[i].fmt ); + + return( VIPS_FORMAT_NOTSET ); +} + +int +vips__foreign_nifti_BandFmt2datatype( VipsBandFormat fmt ) +{ + int i; + + for( i = 0; i < VIPS_NUMBER( vips_foreign_nifti_DT2Vips ); i++ ) + if( vips_foreign_nifti_DT2Vips[i].fmt == fmt ) + return( vips_foreign_nifti_DT2Vips[i].datatype ); + + return( -1 ); +} + +/* All the header fields we attach as metadata. + */ +typedef struct _VipsForeignNiftiFields { + char *name; + GType type; + glong offset; +} VipsForeignNiftiFields; + +static VipsForeignNiftiFields vips_foreign_nifti_fields[] = { /* The first 8 must be the dims[] fields, see * vips_foreign_save_nifti_make_nim(). */ @@ -251,8 +280,29 @@ VipsForeignNiftiFields vips_foreign_nifti_fields[] = { G_STRUCT_OFFSET( nifti_image, intent_p3 ) }, }; +void * +vips__foreign_nifti_map( VipsNiftiMapFn fn, void *a, void *b ) +{ + int i; + void *result; + + for( i = 0; i < VIPS_NUMBER( vips_foreign_nifti_fields ); i++ ) { + GValue value = { 0 }; + + g_value_init( &value, vips_foreign_nifti_fields[i].type ); + result = fn( vips_foreign_nifti_fields[i].name, &value, + vips_foreign_nifti_fields[i].offset, a, b ); + g_value_unset( &value ); + + if( result ) + return( result ); + } + + return( NULL ); +} + /* How I wish glib had something like this :( Just implement the ones we need - * for Field above. + * for vips_foreign_nifti_fields above. */ static void vips_gvalue_read( GValue *value, void *p ) @@ -272,6 +322,32 @@ vips_gvalue_read( GValue *value, void *p ) } } +static void * +vips_foreign_load_nifti_set( const char *name, GValue *value, glong offset, + void *a, void *b ) +{ + nifti_image *nim = (nifti_image *) a; + VipsImage *out = VIPS_IMAGE( b ); + + char vips_name[256]; + + vips_gvalue_read( value, (gpointer) nim + offset ); + vips_snprintf( vips_name, 256, "nifti-%s", name ); + vips_image_set( out, vips_name, value ); + + return( NULL ); +} + +/* 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*/ + static int vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, nifti_image *nim, VipsImage *out ) @@ -330,15 +406,7 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, 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 ) ) { + if( !(fmt = vips__foreign_nifti_BandFmt2datatype( nim->datatype )) ) { vips_error( class->nickname, _( "datatype %d not supported" ), nim->datatype ); return( -1 ); @@ -389,17 +457,9 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB, xres, yres ); - for( i = 0; i < VIPS_NUMBER( vips_foreign_nifti_fields ); i++ ) { - GValue value = { 0 }; - - g_value_init( &value, vips_foreign_nifti_fields[i].type ); - vips_gvalue_read( &value, - (gpointer) nim + vips_foreign_nifti_fields[i].offset ); - vips_snprintf( txt, 256, "nifti-%s", - vips_foreign_nifti_fields[i].name ); - vips_image_set( out, txt, &value ); - g_value_unset( &value ); - } + /* Set some vips metadata for every nifti header field. + */ + vips__foreign_nifti_map( vips_foreign_load_nifti_set, nim, out ); /* One byte longer than the spec to leave space for any extra * '\0' termination. diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c index 80c51fa2..8d98a664 100644 --- a/libvips/foreign/niftisave.c +++ b/libvips/foreign/niftisave.c @@ -78,8 +78,19 @@ vips_foreign_save_nifti_dispose( GObject *gobject ) dispose( gobject ); } +/* Make ->nim from the vips header fields. + */ static int -vips_foreign_save_nifti_make_nim( VipsForeignSaveNifti *nifti, +vips_foreign_save_nifti_header_vips( VipsForeignSaveNifti *nifti, + VipsImage *image ) +{ + return( 0 ); +} + +/* Make ->nim from the nifti- fields. + */ +static int +vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, VipsImage *image ) { int dims[8]; @@ -135,17 +146,24 @@ vips_foreign_save_nifti_build( VipsObject *object ) VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( nifti ), 2 ); - struct nifti_1_header nhdr; - if( VIPS_OBJECT_CLASS( vips_foreign_save_nifti_parent_class )-> build( object ) ) return( -1 ); - if( vips_foreign_save_nifti_make_header( nifti, save->ready, &nhdr ) ) - return( -1 ); - - if( !(nifti->nim = nifti_convert_nhdr2nim( nhdr, nifti->filename )) ) - return( -1 ); + /* This could be an image (indirectly) from niftiload, or something + * like OME_TIFF, which does not have all the "nifti-ndim" fields. + * + * If it doesn't look like a nifti, try to make a nifti header from + * what we have. + */ + if( vips_image_get_typeof( save->ready, "nifti-ndim" ) ) { + if( vips_foreign_save_nifti_header_nifti( nifti, save->ready ) ) + return( -1 ); + } + else { + if( vips_foreign_save_nifti_header_vips( nifti, save->ready ) ) + return( -1 ); + } /* set ext, plus other stuff */ diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 7179c501..a4864c19 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -256,24 +256,12 @@ int vips__quantise_image( VipsImage *in, VipsImage **index_out, VipsImage **palette_out, int colours, int Q, double dither ); -/* Map DT_* datatype values to VipsBandFormat. - */ -typedef struct _VipsForeignDT2Vips { - int datatype; - VipsBandFormat fmt; -} VipsForeignDT2Vips +VipsBandFormat vips__foreign_nifti_datatype2BandFmt( int datatype ); +int vips__foreign_nifti_BandFmt2datatype( VipsBandFormat fmt ); -extern VipsForeignDT2Vips vips_foreign_DT2Vips[]; - -/* All the header fields we attach as metadata. - */ -typedef struct _VipsForeignNiftiFields { - char *name; - GType type; - glong offset; -} VipsForeignNiftiFields; - -VipsForeignNiftiFields vips_foreign_nifti_fields[]; +typedef void *(*VipsNiftiMapFn)( const char *name, GValue *value, glong offset, + void *a, void *b ); +void *vips__foreign_nifti_map( VipsNiftiMapFn fn, void *a, void *b ); #ifdef __cplusplus } From 1028907e581cee017bc1ccec87cb806e2337ab71 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 16 Jul 2018 12:49:12 +0100 Subject: [PATCH 11/17] more save work --- libvips/foreign/niftisave.c | 56 ++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c index 8d98a664..0a2055f4 100644 --- a/libvips/foreign/niftisave.c +++ b/libvips/foreign/niftisave.c @@ -84,31 +84,60 @@ static int vips_foreign_save_nifti_header_vips( VipsForeignSaveNifti *nifti, VipsImage *image ) { + g_assert( FALSE ); + return( 0 ); } +typedef struct _VipsNdimInfo { + VipsImage *image; + int *dims; + int n; +} VipsNdimInfo; + +static void * +vips_foreign_save_nifti_set_dims( const char *name, GValue *value, glong offset, + void *a, void *b ) +{ + VipsNdimInfo *info = (VipsNdimInfo *) a; + + /* The first 8 members are the dims fields. + */ + if( info->n < 7 ) { + char txt[256]; + + vips_snprintf( txt, 256, "nifti-%s", name ); + if( vips_image_get_int( image, name, &info->dims[i] ) ) + return( info ); + } + + info->n += 1; + + return( NULL ); +} + /* Make ->nim from the nifti- fields. */ static int vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, VipsImage *image ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( nifti ); + + VipsNdimInfo info; int dims[8]; int datatype; int height; int i; - /* The first 8 members of vips_foreign_nifti_fields[] are the dims - * fields. - */ - for( i = 0; i < VIPS_NUMBER( dims ); i++ ) { - static char name[256]; + info.image = image; + info.dims = dims; + info.n = 0; + if( vips__foreign_nifti_map( + vips_foreign_save_nifti_set_dims, &info, NULL ) ) + return( -1 ); + - vips_snprintf( name, 256, "nifti-%s", - vips_foreign_nifti_fields[i].name ); - if( vips_image_get_int( image, name, &dims[i] ) ) - return( -1 ); - } height = 1; for( i = 2; i < VIPS_NUMBER( dims ) && i < dims[0]; i++ ) @@ -120,12 +149,7 @@ vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, return( -1 ); } - datatype = -1; - for( i = 0; i < vips_foreign_DT2Vips; i++ ) - if( vips_foreign_DT2Vips[i].format == image.format ) { - datatype = vips_foreign_DT2Vips[i].datatype; - break; - } + datatype = vips__foreign_nifti_BandFmt2datatype( image->BandFmt ); if( datatype == -1 ) { vips_error( class->nickname, "%s", _( "unsupported libvips image type" ) ); From 06a40cc7ee9f1af4e4b1c1bc364ec6c15da06c01 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 18 Jul 2018 17:09:53 +0100 Subject: [PATCH 12/17] sort-of works but can't set meta correctly, try: $ vipsheader -a sub-CC00050XX01_ses-7201_T2w.nii.gz see all fields, but $ vips copy sub-CC00050XX01_ses-7201_T2w.nii.gz x.v $ vipsheader -a x.v only see a few, odd --- libvips/foreign/Makefile.am | 1 + libvips/foreign/foreign.c | 2 + libvips/foreign/niftiload.c | 21 +++---- libvips/foreign/niftisave.c | 106 +++++++++++++++++++++++++++++------- libvips/foreign/pforeign.h | 12 ++++ 5 files changed, 107 insertions(+), 35 deletions(-) diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index c74d94b3..cbd2f8f4 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -3,6 +3,7 @@ noinst_LTLIBRARIES = libforeign.la libforeign_la_SOURCES = \ pforeign.h \ niftiload.c \ + niftisave.c \ quantise.c \ exif.c \ gifload.c \ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 74c935f2..20d6dc74 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1837,6 +1837,7 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_load_svg_file_get_type( void ); extern GType vips_foreign_load_svg_buffer_get_type( void ); extern GType vips_foreign_load_nifti_get_type( void ); + extern GType vips_foreign_save_nifti_get_type( void ); extern GType vips_foreign_load_gif_get_type( void ); extern GType vips_foreign_load_gif_file_get_type( void ); extern GType vips_foreign_load_gif_buffer_get_type( void ); @@ -1961,6 +1962,7 @@ vips_foreign_operation_init( void ) #ifdef HAVE_NIFTI vips_foreign_load_nifti_get_type(); + vips_foreign_save_nifti_get_type(); #endif /*HAVE_NIFTI*/ vips__foreign_load_operation = diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index 4d9e544e..1f0692be 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -338,16 +338,6 @@ vips_foreign_load_nifti_set( const char *name, GValue *value, glong offset, return( NULL ); } -/* 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*/ - static int vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, nifti_image *nim, VipsImage *out ) @@ -406,7 +396,8 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, return( 0 ); } - if( !(fmt = vips__foreign_nifti_BandFmt2datatype( nim->datatype )) ) { + fmt = vips__foreign_nifti_datatype2BandFmt( nim->datatype ); + if( fmt == VIPS_FORMAT_NOTSET ) { vips_error( class->nickname, _( "datatype %d not supported" ), nim->datatype ); return( -1 ); @@ -459,7 +450,8 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, /* Set some vips metadata for every nifti header field. */ - vips__foreign_nifti_map( vips_foreign_load_nifti_set, nim, out ); + if( vips__foreign_nifti_map( vips_foreign_load_nifti_set, nim, out ) ) + return( -1 ); /* One byte longer than the spec to leave space for any extra * '\0' termination. @@ -494,7 +486,7 @@ vips_foreign_load_nifti_header( VipsForeignLoad *load ) VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) load; /* We can't use the (much faster) nifti_read_header() since it just - * reads the 348 bytes iof the analyze struct and does not read any of + * reads the 348 bytes of the analyze struct and does not read any of * the extension fields. */ @@ -552,7 +544,8 @@ const char *vips__nifti_suffs[] = { ".hdr", ".hdr.gz", ".img", ".img.gz", ".nia", ".nia.gz", - NULL }; + NULL +}; static void vips_foreign_load_nifti_class_init( VipsForeignLoadNiftiClass *class ) diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c index 0a2055f4..9b0531f2 100644 --- a/libvips/foreign/niftisave.c +++ b/libvips/foreign/niftisave.c @@ -47,7 +47,9 @@ #include -#ifdef HAVE_CFITSIO +#ifdef HAVE_NIFTI + +#include #include "pforeign.h" @@ -70,11 +72,11 @@ G_DEFINE_TYPE( VipsForeignSaveNifti, vips_foreign_save_nifti, static void vips_foreign_save_nifti_dispose( GObject *gobject ) { - VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) gobject; + VipsForeignSaveNifti *nifti = (VipsForeignSaveNifti *) gobject; VIPS_FREEF( nifti_image_free, nifti->nim ); - G_OBJECT_CLASS( vips_foreign_load_nifti_parent_class )-> + G_OBJECT_CLASS( vips_foreign_save_nifti_parent_class )-> dispose( gobject ); } @@ -91,24 +93,75 @@ vips_foreign_save_nifti_header_vips( VipsForeignSaveNifti *nifti, typedef struct _VipsNdimInfo { VipsImage *image; + nifti_image *nim; int *dims; int n; } VipsNdimInfo; static void * -vips_foreign_save_nifti_set_dims( const char *name, GValue *value, glong offset, - void *a, void *b ) +vips_foreign_save_nifti_set_dims( const char *name, + GValue *value, glong offset, void *a, void *b ) { VipsNdimInfo *info = (VipsNdimInfo *) a; /* The first 8 members are the dims fields. */ - if( info->n < 7 ) { - char txt[256]; + if( info->n < 8 ) { + char vips_name[256]; + int i; - vips_snprintf( txt, 256, "nifti-%s", name ); - if( vips_image_get_int( image, name, &info->dims[i] ) ) + vips_snprintf( vips_name, 256, "nifti-%s", name ); + if( vips_image_get_int( info->image, vips_name, &i ) || + i <= 0 || + i > VIPS_MAX_COORD ) return( info ); + info->dims[info->n] = i; + } + + info->n += 1; + + return( NULL ); +} + +/* How I wish glib had something like this :( Just implement the ones we need + * for vips_foreign_nifti_fields above. + */ +static void +vips_gvalue_write( GValue *value, void *p ) +{ + switch( G_VALUE_TYPE( value ) ) { + case G_TYPE_INT: + *((int *) p) = g_value_get_int( value ); + break; + + case G_TYPE_FLOAT: + *((float *) p) = g_value_get_float( value ); + break; + + default: + g_warning( "vips_gvalue_write: unsupported GType %s", + g_type_name( G_VALUE_TYPE( value ) ) ); + } +} + +static void * +vips_foreign_save_nifti_set_fields( const char *name, + GValue *value, glong offset, void *a, void *b ) +{ + VipsNdimInfo *info = (VipsNdimInfo *) a; + + /* The first 8 members are the dims fields. We set them above ^^^ -- + * do the others in this pass. + */ + if( info->n >= 8 ) { + char vips_name[256]; + GValue value_copy = { 0 }; + + vips_snprintf( vips_name, 256, "nifti-%s", name ); + if( vips_image_get( info->image, vips_name, &value_copy ) ) + return( info ); + vips_gvalue_write( &value_copy, (gpointer) info->nim + offset ); + g_value_unset( &value_copy ); } info->n += 1; @@ -127,7 +180,7 @@ vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, VipsNdimInfo info; int dims[8]; int datatype; - int height; + guint height; int i; info.image = image; @@ -137,13 +190,18 @@ vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, vips_foreign_save_nifti_set_dims, &info, NULL ) ) return( -1 ); - + /* FIXME what about page-height? should check that too. + */ height = 1; - for( i = 2; i < VIPS_NUMBER( dims ) && i < dims[0]; i++ ) - height *= dims[i]; - if( images->Xsize != dims[1] || - images->Ysize != height ) { + for( i = 2; i < VIPS_NUMBER( dims ) && i < dims[0] + 1; i++ ) + if( !g_uint_checked_mul( &height, height, dims[i] ) ) { + vips_error( class->nickname, + "%s", _( "dimension overflow" ) ); + return( 0 ); + } + if( image->Xsize != dims[1] || + image->Ysize != height ) { vips_error( class->nickname, "%s", _( "bad image dimensions" ) ); return( -1 ); @@ -156,9 +214,16 @@ vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, return( -1 ); } - if( !(nnifti->nim = nifti_make_new_nim( dims, datatype, FALSE )) ) + if( !(nifti->nim = nifti_make_new_nim( dims, datatype, FALSE )) ) return( -1 ); + info.image = image; + info.nim = nifti->nim; + info.n = 0; + if( vips__foreign_nifti_map( + vips_foreign_save_nifti_set_fields, &info, NULL ) ) + return( -1 ); + return( 0 ); } @@ -167,8 +232,6 @@ vips_foreign_save_nifti_build( VipsObject *object ) { VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveNifti *nifti = (VipsForeignSaveNifti *) object; - VipsImage **t = (VipsImage **) - vips_object_local_array( VIPS_OBJECT( nifti ), 2 ); if( VIPS_OBJECT_CLASS( vips_foreign_save_nifti_parent_class )-> build( object ) ) @@ -192,7 +255,8 @@ vips_foreign_save_nifti_build( VipsObject *object ) /* set ext, plus other stuff */ - if( !(nim->data = vips_image_write_memory( save->ready, NULL )) ) + if( !(nifti->nim->data = + vips_image_write_to_memory( save->ready, NULL )) ) return( -1 ); /* No return code!??!?!! @@ -202,7 +266,7 @@ vips_foreign_save_nifti_build( VipsObject *object ) /* We must free and NULL the pointer or nifti will try to free it for * us. */ - VIPS_FREE( nim->data ); + VIPS_FREE( nifti->nim->data ); return( 0 ); } @@ -259,7 +323,7 @@ vips_foreign_save_nifti_init( VipsForeignSaveNifti *nifti ) { } -#endif /*HAVE_CFITSIO*/ +#endif /*HAVE_NIFTI*/ /** * vips_niftisave: (method) diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index a4864c19..bdffc683 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -35,6 +35,16 @@ extern "C" { #endif /*__cplusplus*/ +/* 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*/ + void vips__tiff_init( void ); int vips__tiff_write( VipsImage *in, const char *filename, @@ -256,6 +266,8 @@ int vips__quantise_image( VipsImage *in, VipsImage **index_out, VipsImage **palette_out, int colours, int Q, double dither ); +extern const char *vips__nifti_suffs[]; + VipsBandFormat vips__foreign_nifti_datatype2BandFmt( int datatype ); int vips__foreign_nifti_BandFmt2datatype( VipsBandFormat fmt ); From 91791561321bb51161d370f5efd205d4e7ddf878 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 19 Jul 2018 16:06:21 +0100 Subject: [PATCH 13/17] almost working get a 1x1 pixel output now --- libvips/foreign/niftiload.c | 2 +- libvips/foreign/niftisave.c | 7 +++++++ libvips/iofuncs/type.c | 24 ++++++++++++++++++++++++ libvips/iofuncs/vips.c | 4 ++++ m4/nifti.m4 | 11 +++++++---- 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index 1f0692be..abfff1ac 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -290,7 +290,7 @@ vips__foreign_nifti_map( VipsNiftiMapFn fn, void *a, void *b ) GValue value = { 0 }; g_value_init( &value, vips_foreign_nifti_fields[i].type ); - result = fn( vips_foreign_nifti_fields[i].name, &value, + result = fn( vips_foreign_nifti_fields[i].name, &value, vips_foreign_nifti_fields[i].offset, a, b ); g_value_unset( &value ); diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c index 9b0531f2..0da42ab1 100644 --- a/libvips/foreign/niftisave.c +++ b/libvips/foreign/niftisave.c @@ -230,6 +230,7 @@ vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, static int vips_foreign_save_nifti_build( VipsObject *object ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveNifti *nifti = (VipsForeignSaveNifti *) object; @@ -255,6 +256,12 @@ vips_foreign_save_nifti_build( VipsObject *object ) /* set ext, plus other stuff */ + if( nifti_set_filenames( nifti->nim, nifti->filename, FALSE, TRUE ) ) { + vips_error( class->nickname, + "%s", _( "unable to set nifti filename" ) ); + return( -1 ); + } + if( !(nifti->nim->data = vips_image_write_to_memory( save->ready, NULL )) ) return( -1 ); diff --git a/libvips/iofuncs/type.c b/libvips/iofuncs/type.c index b17f3cd3..6d106a99 100644 --- a/libvips/iofuncs/type.c +++ b/libvips/iofuncs/type.c @@ -454,6 +454,26 @@ transform_save_string_double( const GValue *src_value, GValue *dest_value ) NULL ) ); } +static void +transform_float_save_string( const GValue *src_value, GValue *dest_value ) +{ + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + /* Need to be locale independent. + */ + g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, + g_value_get_float( src_value ) ); + vips_value_set_save_string( dest_value, buf ); +} + +static void +transform_save_string_float( const GValue *src_value, GValue *dest_value ) +{ + g_value_set_float( dest_value, + g_ascii_strtod( vips_value_get_save_string( src_value ), + NULL ) ); +} + /* Save meta fields to the header. We have a new string type for header fields * to save to XML and define transform functions to go from our meta types to * this string type. @@ -1847,4 +1867,8 @@ vips__meta_init_types( void ) transform_double_save_string ); g_value_register_transform_func( VIPS_TYPE_SAVE_STRING, G_TYPE_DOUBLE, transform_save_string_double ); + g_value_register_transform_func( G_TYPE_FLOAT, VIPS_TYPE_SAVE_STRING, + transform_float_save_string ); + g_value_register_transform_func( VIPS_TYPE_SAVE_STRING, G_TYPE_FLOAT, + transform_save_string_float ); } diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index 81992337..665c38ff 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -840,6 +840,10 @@ build_xml_meta( VipsMeta *meta, VipsDbuf *dbuf ) g_value_unset( &save_value ); } + else { + printf( "unable to convert %s for save\n", + g_type_name( type ) ); + } return( NULL ); } diff --git a/m4/nifti.m4 b/m4/nifti.m4 index 0abf9008..17e2a3f9 100644 --- a/m4/nifti.m4 +++ b/m4/nifti.m4 @@ -24,11 +24,14 @@ if test "$with_nifti" = "no"; then fi AC_ARG_WITH(nifti-includes, - AS_HELP_STRING([--with-nifti-includes=DIR], [libnifti includes are in DIR]), - NIFTI_INCLUDES="-I$withval") + AS_HELP_STRING([--with-nifti-includes=DIR], [libniftiio includes are in DIR]), + NIFTI_INCLUDES="-I$withval" +) AC_ARG_WITH(nifti-libraries, - AS_HELP_STRING([--with-nifti-libraries=DIR], [libnifti libraries are in DIR]), - NIFTI_LIBS="-L$withval -lnifti") + AS_HELP_STRING([--with-nifti-libraries=DIR], + [libniftiio libraries are in DIR]), + NIFTI_LIBS="-L$withval -lniftiio" +) AC_MSG_CHECKING(for NIFTI) From d03416d3860ddf2c25e21f112eb771db5bba55e2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 19 Jul 2018 17:45:54 +0100 Subject: [PATCH 14/17] works! just needs some tests could be faster too --- ChangeLog | 1 + libvips/foreign/niftiload.c | 45 +++++++++++++++++-- libvips/foreign/niftisave.c | 89 ++++++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2eca4791..2558f3ea 100644 --- a/ChangeLog +++ b/ChangeLog @@ -31,6 +31,7 @@ - add Mitchell kernel - pyramid builders have a choice of 2x2 shrinkers [harukizaemon] - add `palette` option to pngsave [felixbuenemann] +- add basic nifti load/save support 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [AdriĆ ] diff --git a/libvips/foreign/niftiload.c b/libvips/foreign/niftiload.c index abfff1ac..eedc0f0c 100644 --- a/libvips/foreign/niftiload.c +++ b/libvips/foreign/niftiload.c @@ -103,6 +103,43 @@ vips_foreign_load_nifti_dispose( GObject *gobject ) dispose( gobject ); } +static int +vips_foreign_load_nifti_is_a( const char *filename ) +{ + char *hfile; + znzFile fp; + nifti_1_header nhdr; + + /* Unfortunately is_nifti_file() is very slow and produces lots of + * output. We have to make our own. + */ + + if( !(hfile = nifti_findhdrname( filename )) ) + return( 0 ); + + fp = znzopen( hfile, "rb", nifti_is_gzfile( hfile )); + if( znz_isnull( fp ) ) { + free( hfile ); + return( 0 ); + } + free( hfile ); + + (void) znzread( &nhdr, 1, sizeof( nhdr ), fp ); + + znzclose( fp ); + + /* Test for sanity both ways around. There's a thing to test for byte + * order in niftilib, but it's static :( + */ + if( nifti_hdr_looks_good( &nhdr ) ) + return( 1 ); + swap_nifti_header( &nhdr, FALSE ); + if( nifti_hdr_looks_good( &nhdr ) ) + return( 1 ); + + return( 0 ); +} + /* Map DT_* datatype values to VipsBandFormat. */ typedef struct _VipsForeignDT2Vips { @@ -360,7 +397,7 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, nim->ndim ); return( 0 ); } - for( i = 1; i < 8; i++ ) { + for( i = 1; i < 8 && i < nim->ndim + 1; i++ ) { if( nim->dim[i] <= 0 ) { vips_error( class->nickname, "%s", _( "invalid dimension" ) ); @@ -368,7 +405,7 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, } /* If we have several images in a dimension, the spacing must - * be non-zero, or we'll get a /0 error in resolution + * be non-zero or we'll get a /0 error in resolution * calculation. */ if( nim->dim[i] > 1 && @@ -385,7 +422,7 @@ vips_foreign_load_nifti_set_header( VipsForeignLoadNifti *nifti, bands = 1; width = (guint) nim->nx; height = (guint) nim->ny; - for( i = 3; i < 8; i++ ) + for( i = 3; i < 8 && i < nim->ndim + 1; i++ ) if( !g_uint_checked_mul( &height, height, nim->dim[i] ) ) { vips_error( class->nickname, "%s", _( "dimension overflow" ) ); @@ -568,7 +605,7 @@ vips_foreign_load_nifti_class_init( VipsForeignLoadNiftiClass *class ) foreign_class->suffs = vips__nifti_suffs; - load_class->is_a = is_nifti_file; + load_class->is_a = vips_foreign_load_nifti_is_a; load_class->header = vips_foreign_load_nifti_header; load_class->load = vips_foreign_load_nifti_load; diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c index 0da42ab1..9427630d 100644 --- a/libvips/foreign/niftisave.c +++ b/libvips/foreign/niftisave.c @@ -86,7 +86,73 @@ static int vips_foreign_save_nifti_header_vips( VipsForeignSaveNifti *nifti, VipsImage *image ) { - g_assert( FALSE ); + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( nifti ); + + int dims[8]; + int datatype; + int i; + + /* Most nifti images have this defaulted as 1. + */ + for( i = 0; i < VIPS_NUMBER( dims ); i++ ) + dims[i] = 1; + + dims[0] = 2; + dims[1] = image->Xsize; + dims[2] = image->Ysize; + + if( vips_image_get_typeof( image, VIPS_META_PAGE_HEIGHT ) ) { + int page_height; + + if( vips_image_get_int( image, + VIPS_META_PAGE_HEIGHT, &page_height ) ) + return( -1 ); + + if( image->Ysize % page_height == 0 ) { + dims[0] = 3; + dims[2] = page_height; + dims[3] = image->Ysize / page_height; + } + } + + datatype = vips__foreign_nifti_BandFmt2datatype( image->BandFmt ); + if( datatype == -1 ) { + vips_error( class->nickname, + "%s", _( "unsupported libvips image type" ) ); + return( -1 ); + } + + if( image->Bands > 1 ) { + if( image->BandFmt != VIPS_FORMAT_UCHAR ) { + vips_error( class->nickname, + "%s", _( "8-bit colour images only" ) ); + return( -1 ); + } + + if( image->Bands == 3 ) + datatype = DT_RGB; + else if( image->Bands == 4 ) + datatype = DT_RGBA32; + else { + vips_error( class->nickname, + "%s", _( "3 or 4 band colour images only" ) ); + return( -1 ); + } + } + + if( !(nifti->nim = nifti_make_new_nim( dims, datatype, FALSE )) ) + return( -1 ); + + nifti->nim->dx = 1.0 / image->Xres; + nifti->nim->dy = 1.0 / image->Yres; + nifti->nim->dz = 1.0 / image->Yres; + nifti->nim->xyz_units = NIFTI_UNITS_MM; + + vips_snprintf( nifti->nim->descrip, sizeof( nifti->nim->descrip ), + "libvips-%s", VIPS_VERSION ); + + /* All other fields can stay at their default value. + */ return( 0 ); } @@ -183,6 +249,11 @@ vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, guint height; int i; + /* Most nifti images have this defaulted as 1. + */ + for( i = 0; i < VIPS_NUMBER( dims ); i++ ) + dims[i] = 1; + info.image = image; info.dims = dims; info.n = 0; @@ -190,8 +261,22 @@ vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, vips_foreign_save_nifti_set_dims, &info, NULL ) ) return( -1 ); - /* FIXME what about page-height? should check that too. + /* page-height overrides ny if it makes sense. This might not be + * correct :( */ + if( vips_image_get_typeof( image, VIPS_META_PAGE_HEIGHT ) ) { + int page_height; + + if( vips_image_get_int( image, + VIPS_META_PAGE_HEIGHT, &page_height ) ) + return( -1 ); + + if( image->Ysize % page_height == 0 ) { + dims[0] = 3; + dims[2] = page_height; + dims[3] = image->Ysize / page_height; + } + } height = 1; for( i = 2; i < VIPS_NUMBER( dims ) && i < dims[0] + 1; i++ ) From 3d5b0b814f3daa45f0b34a5088666a95652edeb2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 20 Jul 2018 13:58:27 +0100 Subject: [PATCH 15/17] merge in test suite --- ChangeLog | 3 --- 1 file changed, 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8f9f66a9..5b1db846 100644 --- a/ChangeLog +++ b/ChangeLog @@ -31,13 +31,10 @@ - add Mitchell kernel - pyramid builders have a choice of 2x2 shrinkers [harukizaemon] - add `palette` option to pngsave [felixbuenemann] -<<<<<<< HEAD - add basic nifti load/save support -======= - support writing string-valued fields via libexif - paste in the test suite from pyvips - get EXIF tag names from tag plus ifd [@Nan619] ->>>>>>> master 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [AdriĆ ] From e72ad7045ddd81092705a263f6cb38e3a2a02ed8 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 20 Jul 2018 17:23:06 +0100 Subject: [PATCH 16/17] add a nifti test --- test/test-suite/helpers/helpers.py | 1 + .../images/avg152T1_LR_nifti.nii.gz | Bin test/test-suite/test_foreign.py | 31 ++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) rename test/{ => test-suite}/images/avg152T1_LR_nifti.nii.gz (100%) diff --git a/test/test-suite/helpers/helpers.py b/test/test-suite/helpers/helpers.py index 41a02e5b..39e5efc2 100644 --- a/test/test-suite/helpers/helpers.py +++ b/test/test-suite/helpers/helpers.py @@ -27,6 +27,7 @@ SVG_GZ_FILE = os.path.join(IMAGES, "vips-profile.svg.gz") GIF_ANIM_FILE = os.path.join(IMAGES, "cogs.gif") DICOM_FILE = os.path.join(IMAGES, "dicom_test_image.dcm") BMP_FILE = os.path.join(IMAGES, "MARBLES.BMP") +NIFTI_FILE = os.path.join(IMAGES, "avg152T1_LR_nifti.nii.gz") unsigned_formats = [pyvips.BandFormat.UCHAR, pyvips.BandFormat.USHORT, diff --git a/test/images/avg152T1_LR_nifti.nii.gz b/test/test-suite/images/avg152T1_LR_nifti.nii.gz similarity index 100% rename from test/images/avg152T1_LR_nifti.nii.gz rename to test/test-suite/images/avg152T1_LR_nifti.nii.gz diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 2aa55635..f6db157a 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -6,12 +6,12 @@ import tempfile import pytest import pyvips -from helpers import JPEG_FILE, SRGB_FILE, \ - MATLAB_FILE, PNG_FILE, TIF_FILE, OME_FILE, ANALYZE_FILE, \ - GIF_FILE, WEBP_FILE, EXR_FILE, FITS_FILE, OPENSLIDE_FILE, \ - PDF_FILE, SVG_FILE, SVGZ_FILE, SVG_GZ_FILE, GIF_ANIM_FILE, \ - DICOM_FILE, BMP_FILE, temp_filename, assert_almost_equal_objects, have, \ - skip_if_no +from helpers import \ + JPEG_FILE, SRGB_FILE, MATLAB_FILE, PNG_FILE, TIF_FILE, OME_FILE, \ + ANALYZE_FILE, GIF_FILE, WEBP_FILE, EXR_FILE, FITS_FILE, OPENSLIDE_FILE, \ + PDF_FILE, SVG_FILE, SVGZ_FILE, SVG_GZ_FILE, GIF_ANIM_FILE, DICOM_FILE, \ + BMP_FILE, NIFTI_FILE, \ + temp_filename, assert_almost_equal_objects, have, skip_if_no class TestForeign: @@ -532,6 +532,25 @@ class TestForeign: self.file_loader("fitsload", FITS_FILE, fits_valid) self.save_load("%s.fits", self.mono) + @skip_if_no("niftiload") + def test_niftiload(self): + def nifti_valid(im): + a = im(30, 26) + assert_almost_equal_objects(a, [131]) + assert im.width == 91 + assert im.height == 9919 + assert im.bands == 1 + + print("NIFTI_FILE =", NIFTI_FILE) + im = pyvips.Operation.call("niftiload", NIFTI_FILE) + print("width = ", im.width) + + im = pyvips.Operation.call("niftiload", "test/images/avg152T1_LR_nifti.nii.gz") + print("width = ", im.width) + + self.file_loader("niftiload", NIFTI_FILE, nifti_valid) + self.save_load("%s.nii.gz", self.mono) + @skip_if_no("openslideload") def test_openslideload(self): def openslide_valid(im): From 8730d8ddf1f33d3ea95d3acb051d7335960bd77d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 20 Jul 2018 18:20:26 +0100 Subject: [PATCH 17/17] add nifti ext save --- libvips/foreign/niftisave.c | 47 ++++++++++++++++++++++++++++++++-- libvips/include/vips/foreign.h | 2 ++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/libvips/foreign/niftisave.c b/libvips/foreign/niftisave.c index 9427630d..a0c618ad 100644 --- a/libvips/foreign/niftisave.c +++ b/libvips/foreign/niftisave.c @@ -235,6 +235,41 @@ vips_foreign_save_nifti_set_fields( const char *name, return( NULL ); } +static void * +vips_foreign_save_nifti_ext( VipsImage *image, + const char *field, GValue *value, void *a ) +{ + nifti_image *nim = (nifti_image *) a; + + int i; + int ecode; + char *data; + size_t length; + + if( !vips_isprefix( "nifti-ext-", field ) ) + return( NULL ); + + /* The name is "nifti-ext-N-XX" where N is the index (discard this) + * and XX is the nifti ext ecode. + */ + if( sscanf( field, "nifti-ext-%d-%d", &i, &ecode ) != 2 ) { + vips_error( "niftisave", + "%s", _( "bad nifti-ext- field name" ) ); + return( image ); + } + + if( vips_image_get_blob( image, field, (void *) &data, &length ) ) + return( image ); + + if( nifti_add_extension( nim, data, length, ecode ) ) { + vips_error( "niftisave", + "%s", _( "unable to attach nifti ext" ) ); + return( image ); + } + + return( NULL ); +} + /* Make ->nim from the nifti- fields. */ static int @@ -309,6 +344,12 @@ vips_foreign_save_nifti_header_nifti( VipsForeignSaveNifti *nifti, vips_foreign_save_nifti_set_fields, &info, NULL ) ) return( -1 ); + /* Attach any ext blocks. + */ + if( vips_image_map( image, + (VipsImageMapFn) vips_foreign_save_nifti_ext, nifti->nim ) ) + return( -1 ); + return( 0 ); } @@ -423,9 +464,11 @@ vips_foreign_save_nifti_init( VipsForeignSaveNifti *nifti ) * @filename: file to write to * @...: %NULL-terminated list of optional named arguments * - * Write a VIPS image to a file in NIFTI format. + * Write a VIPS image to a file in NIFTI format. * - * See also: vips_image_write_to_file(). + * Use the various NIFTI suffixes to pick the nifti save format. + * + * See also: vips_image_write_to_file(), vips_niftiload(). * * Returns: 0 on success, -1 on error. */ diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 57d0b321..4711d448 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -562,6 +562,8 @@ int vips_gifload_buffer( void *buf, size_t len, VipsImage **out, ... ) int vips_niftiload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_niftisave( VipsImage *in, const char *filename, ... ) + __attribute__((sentinel)); /** * VipsForeignDzLayout: