changeset 1892:b33e4574ed49

Initial checkin, simplified minc interface
author bert <bert>
date Mon, 01 Nov 2004 22:06:48 +0000
parents 4cfab8eca7e4
children cfb149932725
files libsrc/minc_simple.c
diffstat 1 files changed, 1406 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/libsrc/minc_simple.c
@@ -0,0 +1,1406 @@
+/* ----------------------------- MNI Header -----------------------------------
+@NAME       : minc_simple.c
+@DESCRIPTION: Simplified interface to 3D and 4D minc files
+@METHOD     : Routines included in this file :
+@CREATED    : August 20, 2004. (Bert Vincent, Montreal Neurological Institute)
+@MODIFIED   : 
+ * $Log: minc_simple.c,v $
+ * Revision 6.1  2004-11-01 22:06:48  bert
+ * Initial checkin, simplified minc interface
+ *
+@COPYRIGHT  :
+              Copyright 2004 Robert Vincent, McConnell Brain Imaging Centre, 
+              Montreal Neurological Institute, McGill University.
+              Permission to use, copy, modify, and distribute this
+              software and its documentation for any purpose and without
+              fee is hereby granted, provided that the above copyright
+              notice appear in all copies.  The author and McGill University
+              make no representations about the suitability of this
+              software for any purpose.  It is provided "as is" without
+              express or implied warranty.
+---------------------------------------------------------------------------- */
+
+#include "minc_private.h"
+#include <float.h>              /* for DBL_MAX */
+#include "minc_simple.h"
+
+/* Trivial MINC interface */
+
+#define MI_S_T 0
+#define MI_S_Z 1
+#define MI_S_Y 2
+#define MI_S_X 3
+#define MI_S_NDIMS 4
+
+static char *minc_dimnames[] = {
+    MItime, 
+    MIxspace,
+    MIyspace,
+    MIzspace
+};
+
+/* private function from from libminc2.  This function is private partially
+ because it's parameters are somewhat bizarre.  It would be a good idea to
+ rework them into a more rational and easily described form. 
+*/
+
+extern void restructure_array(int ndims,
+                              unsigned char *array,
+                              const unsigned long *lengths_perm,
+                              int el_size,
+                              const int *map,
+                              const int *dir);
+
+/* Structures used to represent a file in memory.
+ */
+struct att_info {
+    char att_name[128];
+    nc_type att_type;
+    int att_len;
+    void *att_val;
+};
+
+struct var_info {
+    char var_name[128];
+    nc_type var_type;
+    int var_natts;
+    int var_ndims;
+    int var_dims[MAX_NC_DIMS];
+    struct att_info *var_atts;
+};
+
+struct file_info {
+    int file_ndims;
+    int file_nvars;
+    int file_natts;
+    struct att_info *file_atts;
+    struct var_info *file_vars;
+};
+
+static int
+minc_simple_to_nc_type(int minctype, nc_type *nctype, char **signstr)
+{
+    switch (minctype) {
+    case MINC_TYPE_CHAR:
+        *nctype = NC_BYTE;
+        *signstr = MI_SIGNED;
+        break;
+        
+    case MINC_TYPE_UCHAR:
+        *nctype = NC_BYTE;
+        *signstr = MI_UNSIGNED;
+        break;
+        
+    case MINC_TYPE_SHORT:
+        *nctype = NC_SHORT;
+        *signstr = MI_SIGNED;
+        break;
+        
+    case MINC_TYPE_USHORT:
+        *nctype = NC_SHORT;
+        *signstr = MI_UNSIGNED;
+        break;
+        
+    case MINC_TYPE_INT:
+        *nctype = NC_INT;
+        *signstr = MI_SIGNED;
+        break;
+        
+    case MINC_TYPE_UINT:
+        *nctype = NC_INT;
+        *signstr = MI_UNSIGNED;
+        break;
+        
+    case MINC_TYPE_FLOAT:
+        *nctype = NC_FLOAT;
+        *signstr = MI_SIGNED;
+        break;
+        
+    case MINC_TYPE_DOUBLE:
+        *nctype = NC_DOUBLE;
+        *signstr = MI_SIGNED;
+        break;
+        
+    default:
+        return (MINC_STATUS_ERROR);
+    }
+    return (MINC_STATUS_OK);
+}
+
+MNCAPI int
+minc_file_size(char *path,
+               long *ct, long *cz, long *cy, long *cx,
+               long *cvoxels, long *cbytes)
+{
+    int fd;
+    nc_type nctype;
+    int dim_id[MI_S_NDIMS];
+    long dim_len[MI_S_NDIMS];
+    int i;
+    int var_id;
+    int var_ndims;
+    int var_dims[MAX_NC_DIMS];
+    long voxel_count;
+    long byte_count;
+    int old_ncopts;
+
+    fd = miopen(path, NC_NOWRITE);
+    if (fd < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+    
+    old_ncopts = ncopts;
+    ncopts = 0;
+
+    for (i = 0; i < MI_S_NDIMS; i++) {
+        dim_id[i] = ncdimid(fd, minc_dimnames[i]);
+        if (dim_id[i] >= 0) {
+            ncdiminq(fd, dim_id[i], NULL, &dim_len[i]);
+        }
+        else {
+            dim_len[i] = 0;
+        }
+    }
+
+    ncopts = old_ncopts;
+
+    if (ct != NULL) {
+        *ct = dim_len[MI_S_T];
+    }
+    if (cz != NULL) {
+        *cz = dim_len[MI_S_Z];
+    }
+    if (cy != NULL) {
+        *cy = dim_len[MI_S_Y];
+    }
+    if (cx != NULL) {
+        *cx = dim_len[MI_S_X];
+    }
+
+    var_id = ncvarid(fd, MIimage);
+
+    if (cvoxels != NULL || cbytes != NULL) {
+        ncvarinq(fd, var_id, NULL, &nctype, &var_ndims, var_dims, NULL);
+
+        voxel_count = 1;
+
+        for (i = 0; i < var_ndims; i++) {
+            long length;
+            ncdiminq(fd, var_dims[i], NULL, &length);
+            voxel_count *= length;
+        }
+
+        byte_count = voxel_count * nctypelen(nctype);
+
+        if (cvoxels != NULL) {
+            *cvoxels = voxel_count;
+        }
+        if (cbytes != NULL) {
+            *cbytes = byte_count;
+        }
+    }
+    return (MINC_STATUS_OK);
+}
+
+MNCAPI int
+minc_load_data(char *path, void *dataptr, int datatype,
+               long *ct, long *cz, long *cy, long *cx,
+               double *dt, double *dz, double *dy, double *dx,
+               void **infoptr)
+{
+    int fd;                     /* MINC file descriptor */
+    nc_type nctype;             /* netCDF type */
+    char *signstr;              /* MI_SIGNED or MI_UNSIGNED */
+    int length;
+    int dim_id[MI_S_NDIMS];
+    long dim_len[MI_S_NDIMS];
+    int i, j;                   /* Generic loop counters */
+    int var_id;
+    int var_ndims;
+    int var_natts;
+    int var_dims[MAX_NC_DIMS];
+    char var_name[128];
+    int icv;                    /* MINC image conversion variable */
+    long start[MI_S_NDIMS];
+    long count[MI_S_NDIMS];
+    int dir[MI_S_NDIMS];        /* Dimension "directions" */
+    int map[MI_S_NDIMS];        /* Dimension mapping */
+    int old_ncopts;             /* For storing the old state of ncopts */
+    double *p_dtmp;
+    long *p_ltmp;
+    struct file_info *p_file;
+    struct att_info *p_att;
+    int r;                      /* Generic return code */
+    
+    *infoptr = NULL;
+
+    fd = miopen(path, NC_NOWRITE);
+    if (fd < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    old_ncopts = ncopts;
+    ncopts = 0;
+
+    for (i = 0; i < MI_S_NDIMS; i++) {
+        dim_id[i] = ncdimid(fd, minc_dimnames[i]);
+        if (dim_id[i] >= 0) {
+            ncdiminq(fd, dim_id[i], NULL, &dim_len[i]);
+            var_id = ncvarid(fd, minc_dimnames[i]);
+            ncattinq(fd, var_id, MIstep, &nctype, &length);
+
+            switch (i) {
+            case MI_S_T:
+                p_ltmp = ct;
+                p_dtmp = dt;
+                break;
+            case MI_S_X:
+                p_ltmp = cx;
+                p_dtmp = dx;
+                break;
+            case MI_S_Y:
+                p_ltmp = cy;
+                p_dtmp = dy;
+                break;
+            case MI_S_Z:
+                p_ltmp = cz;
+                p_dtmp = dz;
+                break;
+            default:
+                return (MINC_STATUS_ERROR);
+            }
+                
+            if (nctype == NC_DOUBLE && length == 1) {
+                ncattget(fd, var_id, MIstep, p_dtmp);
+            }
+            else {
+                *p_dtmp = 0;    /* Unknown/not set */
+            }
+            *p_ltmp = dim_len[i];
+        }
+        else {
+            dim_len[i] = 0;
+        }
+    }
+
+    ncopts = old_ncopts;
+
+    var_id = ncvarid(fd, MIimage);
+
+    ncvarinq(fd, var_id, NULL, &nctype, &var_ndims, var_dims, NULL);
+
+    if (var_ndims != 3 && var_ndims != 4) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    /* We want the data to wind up in t, x, y, z order. */
+
+    for (i = 0; i < var_ndims; i++) {
+        if (var_dims[i] == dim_id[MI_S_T]) {
+            map[i] = MI_S_T;
+        }
+        else if (var_dims[i] == dim_id[MI_S_X]) {
+            map[i] = MI_S_X;
+        }
+        else if (var_dims[i] == dim_id[MI_S_Y]) {
+            map[i] = MI_S_Y;
+        }
+        else if (var_dims[i] == dim_id[MI_S_Z]) {
+            map[i] = MI_S_Z;
+        }
+        else {
+            map[i] = -1;
+        }
+    }
+
+    icv = miicv_create();
+
+    minc_simple_to_nc_type(datatype, &nctype, &signstr);
+    miicv_setint(icv, MI_ICV_TYPE, nctype);
+    miicv_setstr(icv, MI_ICV_SIGN, signstr);
+    miicv_attach(icv, fd, var_id);
+
+    for (i = 0; i < var_ndims; i++) {
+        start[i] = 0;
+    }
+
+    for (i = 0; i < MI_S_NDIMS; i++) {
+        if (map[i] >= 0) {
+            count[i] = dim_len[map[i]];
+        }
+    }
+
+    r = miicv_get(icv, start, count, dataptr);
+    if (r < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    if (map[MI_S_T] >= 0) {
+        if (*dt < 0) {
+            dir[MI_S_T] = -1;
+            *dt = -*dt;
+        }
+        else {
+            dir[MI_S_T] = 1;
+        }
+    }
+
+    if (map[MI_S_X] >= 0) {
+        if (*dx < 0) {
+            dir[MI_S_X] = -1;
+            *dx = -*dx;
+        }
+        else {
+            dir[MI_S_X] = 1;
+        }
+    }
+
+    if (map[MI_S_Y] >= 0) {
+        if (*dy < 0) {
+            dir[MI_S_Y] = -1;
+            *dy = -*dy;
+        }
+        else {
+            dir[MI_S_Y] = 1;
+        }
+    }
+
+    if (map[MI_S_Z] >= 0) {
+        if (*dz < 0) {
+            dir[MI_S_Z] = -1;
+            *dz = -*dz;
+        }
+        else {
+            dir[MI_S_Z] = 1;
+        }
+    }
+
+    if (var_ndims == 3) {
+        for (i = 0; i < var_ndims; i++) {
+            map[i] = map[i] - 1;
+        }
+        for (i = 1; i < MI_S_NDIMS; i++) {
+            dir[i-1] = dir[i];
+        }
+    }
+
+    j = 0;
+    for (i = 0; i < MI_S_NDIMS; i++) {
+        if (dim_len[i] > 0) {
+            count[j++] = dim_len[i];
+        }
+    }
+
+    restructure_array(var_ndims, dataptr, count, nctypelen(nctype),
+                      map, dir);
+
+    miicv_detach(icv);
+    miicv_free(icv);
+
+    old_ncopts = ncopts;
+    ncopts = 0;
+
+    /* Generate the complete infoptr array.
+     * This is essentially an in-memory copy of the variables and attributes
+     * in the file.
+     */
+
+    p_file = (struct file_info *) malloc(sizeof (struct file_info));
+
+    ncinquire(fd, &p_file->file_ndims, &p_file->file_nvars,
+              &p_file->file_natts, NULL);
+
+    p_file->file_atts = (struct att_info *) malloc(sizeof (struct att_info) * 
+                                                   p_file->file_natts);
+
+    p_file->file_vars = (struct var_info *) malloc(sizeof (struct var_info) *
+                                                   p_file->file_nvars);
+
+    for (i = 0; i < p_file->file_natts; i++) {
+        p_att = &p_file->file_atts[i];
+
+        ncattname(fd, NC_GLOBAL, i, p_att->att_name);
+        ncattinq(fd, NC_GLOBAL, 
+                 p_att->att_name, 
+                 &p_att->att_type, 
+                 &p_att->att_len);
+
+        p_att->att_val = malloc(p_att->att_len * nctypelen(p_att->att_type));
+
+        ncattget(fd, NC_GLOBAL, p_att->att_name, p_att->att_val);
+    }
+
+    for (i = 0; i < p_file->file_nvars; i++) {
+        struct var_info *p_var = &p_file->file_vars[i];
+
+        ncvarinq(fd, i, 
+                 p_var->var_name, 
+                 &p_var->var_type, 
+                 &p_var->var_ndims, 
+                 p_var->var_dims,
+                 &p_var->var_natts);
+
+        p_var->var_atts = malloc(p_var->var_natts *
+                                 sizeof (struct att_info));
+
+        if (ncdimid(fd, p_var->var_name) >= 0) {
+            /* It's a dimension variable, have to treat it specially... */
+        }
+
+        for (j = 0; j < p_var->var_natts; j++) {
+            p_att = &p_var->var_atts[j];
+
+            ncattname(fd, i, j, p_att->att_name);
+            ncattinq(fd, i, 
+                     p_att->att_name, 
+                     &p_att->att_type, 
+                     &p_att->att_len);
+
+            p_att->att_val = malloc(p_att->att_len * nctypelen(p_att->att_type));
+            ncattget(fd, i, p_att->att_name, p_att->att_val);
+        }
+    }
+
+    *infoptr = p_file;
+
+    ncopts = old_ncopts;
+
+    miclose(fd);
+
+    return (MINC_STATUS_OK);
+}
+
+MNCAPI void
+minc_free_info(void *infoptr)
+{
+    struct file_info *p_file;
+    int i, j;
+
+    if ((p_file = infoptr) != NULL) {
+        if (p_file->file_natts != 0 && p_file->file_atts != NULL) {
+            for (i = 0; i < p_file->file_natts; i++) {
+                free(p_file->file_atts[i].att_val);
+            }
+            free(p_file->file_atts);
+        }
+
+        if (p_file->file_nvars != 0 && p_file->file_vars != NULL) {
+            for (i = 0; i < p_file->file_nvars; i++) {
+                if (p_file->file_vars[i].var_natts != 0 &&
+                    p_file->file_vars[i].var_atts != NULL) {
+                    for (j = 0; j < p_file->file_vars[i].var_natts; j++) {
+                        if (p_file->file_vars[i].var_atts[j].att_val != NULL) {
+                            free(p_file->file_vars[i].var_atts[j].att_val);
+                        }
+                    }
+                    free(p_file->file_vars[i].var_atts);
+                }
+            }
+            free(p_file->file_vars);
+        }
+    }
+}
+
+/* int minc_save_start()
+ *
+ * Returns an opaque handle which must be passed to successive calls to
+ * minc_save_data() and ultimately to minc_save_done().
+ */
+
+MNCAPI int
+minc_save_start(char *path,     /* Path to the file */
+                int filetype,   /* Date type as stored in the file */
+                long ct,        /* Total length of time axis, in voxels */
+                long cz,        /* Total length of Z axis, in voxels */
+                long cy,        /* Total length of Y axis, in voxels */
+                long cx,        /* Total length of X axis, in voxels */
+                double dt,      /* Sample width along time axis, in seconds */
+                double dz,      /* Sample width along Z axis, in mm */
+                double dy,      /* Sample width along Y axis, in mm */
+                double dx,      /* Sample width along X axis, in mm */
+                void *infoptr,  /* Opaque file structure information */
+                const char *history) /* New history information */
+{
+    int fd;                     /* MINC file descriptor */
+    int dim_id[MI_S_NDIMS];     /* netCDF dimension ID array */
+    int var_ndims;              /* Number of dimensions per variable */
+    int var_dims[MI_S_NDIMS];   /* Dimension ID's per variable */
+    long dim_len[MI_S_NDIMS];   /* Lengths of dimensions */
+    int i, j;                   /* Generic loop counters */
+    int old_ncopts;             /* For supressing fatal error messages */
+    struct file_info *p_file;   /* For accessing the file structure */
+    struct var_info *p_var;
+    struct att_info *p_att;
+    int var_id;                 /* netCDF ID for variable */
+    char *signstr;
+    nc_type nctype;
+    int r;
+
+    old_ncopts = ncopts;
+    ncopts = 0;
+
+    fd = micreate(path, NC_CLOBBER);
+    if (fd < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    if (ct > 0) {
+        dim_id[MI_S_T] = ncdimdef(fd, MItime, ct);
+        micreate_std_variable(fd, MItime, NC_INT, 0, NULL);
+        if (dt > 0.0) {
+            miattputdbl(fd, ncvarid(fd, MItime), MIstep, dt);
+        }
+    }
+    else {
+        dim_id[MI_S_T] = -1;
+    }
+
+    if (cz > 0) {
+        dim_id[MI_S_Z] = ncdimdef(fd, MIzspace, cz);
+        micreate_std_variable(fd, MIzspace, NC_INT, 0, NULL);
+        if (dz > 0.0) {
+            miattputdbl(fd, ncvarid(fd, MIzspace), MIstep, dz);
+        }
+    }
+    else {
+        dim_id[MI_S_Z] = -1;
+    }
+
+    if (cy > 0) {
+        dim_id[MI_S_Y] = ncdimdef(fd, MIyspace, cy);
+        micreate_std_variable(fd, MIyspace, NC_INT, 0, NULL);
+        if (dy > 0.0) {
+            miattputdbl(fd, ncvarid(fd, MIyspace), MIstep, dy);
+        }
+    }
+    else {
+        return (MINC_STATUS_ERROR); /* Must define Y */
+    }
+
+    if (cx > 0) {
+        dim_id[MI_S_X] = ncdimdef(fd, MIxspace, cx);
+        micreate_std_variable(fd, MIxspace, NC_INT, 0, NULL);
+        if (dx > 0.0) {
+            miattputdbl(fd, ncvarid(fd, MIxspace), MIstep, dx);
+        }
+    }
+    else {
+        return (MINC_STATUS_ERROR); /* Must define X */
+    }
+    
+
+    /* The var_dims[] array is the array of actual dimension ID's to
+     * be used in defining the image variables.  Here I set it up by
+     * copying all valid dimension ID's from the dim_id[] array.
+     */
+    var_ndims = 0;
+    for (i = 0; i < MI_S_NDIMS; i++) {
+        if (dim_id[i] >= 0) {
+            var_dims[var_ndims] = dim_id[i];
+            var_ndims++;
+        }
+    }
+
+    minc_simple_to_nc_type(filetype, &nctype, &signstr);
+
+    /* Create the image variable with the standard
+     * dimension order, and the same type as the template
+     * file.
+     */
+    micreate_std_variable(fd, MIimage, nctype, var_ndims, var_dims);
+    micreate_std_variable(fd, MIimagemin, NC_DOUBLE, 1, var_dims);
+    micreate_std_variable(fd, MIimagemax, NC_DOUBLE, 1, var_dims);
+
+    /* Copy information from the infoptr to the output. 
+     */
+    if ((p_file = infoptr) != NULL) {
+        old_ncopts = ncopts;
+        ncopts = 0;
+
+        for (i = 0; i < p_file->file_natts; i++) {
+            p_att = &p_file->file_atts[i];
+            if (strcmp(p_att->att_name, "ident") != 0) {
+                ncattput(fd, NC_GLOBAL, p_att->att_name, p_att->att_type, 
+                         p_att->att_len, p_att->att_val);
+            }
+        }
+
+        for (i = 0; i < p_file->file_nvars; i++) {
+            p_var = &p_file->file_vars[i];
+
+            if ((var_id = ncvarid(fd, p_var->var_name)) < 0) {
+                var_id = ncvardef(fd, p_var->var_name, p_var->var_type, 
+                                  p_var->var_ndims, p_var->var_dims);
+            }
+            
+            for (j = 0; j < p_var->var_natts; j++) {
+                p_att = &p_var->var_atts[j];
+                ncattput(fd, var_id, p_att->att_name, p_att->att_type,
+                         p_att->att_len, p_att->att_val);
+            }
+        }
+    }
+
+    miattputstr(fd, ncvarid(fd, MIimage), MIcomplete, MI_FALSE);
+    miattputstr(fd, ncvarid(fd, MIimage), MIsigntype, signstr);
+
+    miappend_history(fd, history);
+    ncendef(fd);
+    return fd;
+}
+
+/* Internal function */
+static void
+find_minmax(void *dataptr, long datacount, int datatype, double *min, 
+            double *max)
+{
+    *min = DBL_MAX;
+    *max = -DBL_MAX;
+
+    switch (datatype) {
+    case MINC_TYPE_CHAR:
+        {
+            char *c_ptr = dataptr;
+            while (datacount--) {
+                if (*c_ptr > *max) {
+                    *max = *c_ptr;
+                }
+                if (*c_ptr < *min) {
+                    *min = *c_ptr;
+                }
+                c_ptr++;
+            }
+        }
+        break;
+    case MINC_TYPE_UCHAR:
+        {
+            unsigned char *c_ptr = dataptr;
+            while (datacount--) {
+                if (*c_ptr > *max) {
+                    *max = *c_ptr;
+                }
+                if (*c_ptr < *min) {
+                    *min = *c_ptr;
+                }
+                c_ptr++;
+            }
+        }
+        break;
+    case MINC_TYPE_SHORT:
+        {
+            short *s_ptr = dataptr;
+            while (datacount--) {
+                if (*s_ptr > *max) {
+                    *max = *s_ptr;
+                }
+                if (*s_ptr < *min) {
+                    *min = *s_ptr;
+                }
+                s_ptr++;
+            }
+        }
+        break;
+    case MINC_TYPE_USHORT:
+        {
+            unsigned short *s_ptr = dataptr;
+            while (datacount--) {
+                if (*s_ptr > *max) {
+                    *max = *s_ptr;
+                }
+                if (*s_ptr < *min) {
+                    *min = *s_ptr;
+                }
+                s_ptr++;
+            }
+        }
+        break;
+    case MINC_TYPE_INT:
+        {
+            int *i_ptr = dataptr;
+            while (datacount--) {
+                if (*i_ptr > *max) {
+                    *max = *i_ptr;
+                }
+                if (*i_ptr < *min) {
+                    *min = *i_ptr;
+                }
+                i_ptr++;
+            }
+        }
+        break;
+    case MINC_TYPE_UINT:
+        {
+            unsigned int *i_ptr = dataptr;
+            while (datacount--) {
+                if (*i_ptr > *max) {
+                    *max = *i_ptr;
+                }
+                if (*i_ptr < *min) {
+                    *min = *i_ptr;
+                }
+                i_ptr++;
+            }
+        }
+        break;
+    case MINC_TYPE_FLOAT:
+        {
+            float *f_ptr = dataptr;
+            while (datacount--) {
+                if (*f_ptr > *max) {
+                    *max = *f_ptr;
+                }
+                if (*f_ptr < *min) {
+                    *min = *f_ptr;
+                }
+                f_ptr++;
+            }
+        }
+        break;
+    case MINC_TYPE_DOUBLE:
+        {
+            double *d_ptr = dataptr;
+            while (datacount--) {
+                if (*d_ptr > *max) {
+                    *max = *d_ptr;
+                }
+                if (*d_ptr < *min) {
+                    *min = *d_ptr;
+                }
+                d_ptr++;
+            }
+        }
+        break;
+    default:
+        return;
+    }
+}
+
+
+MNCAPI int
+minc_save_data(int fd, void *dataptr, int datatype,
+               long st, long sz, long sy, long sx,
+               long ct, long cz, long cy, long cx)
+{
+    nc_type nctype;
+    char *signstr;
+    int i, j;
+    int var_id;
+    int var_ndims;
+    int var_dims[MAX_NC_DIMS];
+    int icv;
+    long start[MI_S_NDIMS];
+    long count[MI_S_NDIMS];
+    int old_ncopts;
+    int r;
+    double min, max;
+    long slice_size;
+    long index;
+    int dtbytes;                /* Length of datatype in bytes */
+
+    old_ncopts = ncopts;
+    ncopts = 0;
+
+    var_id = ncvarid(fd, MIimage);
+
+    ncvarinq(fd, var_id, NULL, NULL, &var_ndims, var_dims, NULL);
+
+    if (var_ndims < 2 || var_ndims > 4) {
+        return (MINC_STATUS_ERROR);
+    }
+    
+    r = minc_simple_to_nc_type(datatype, &nctype, &signstr);
+    if (r == MINC_STATUS_ERROR) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    dtbytes = nctypelen(nctype);
+
+    /* Update the image-min and image-max values */
+    if (ct > 0) {
+        slice_size = cz * cy * cx;
+        index = st;
+
+        for (i = 0; i < ct; i++) {
+            find_minmax((char *) dataptr + (dtbytes * slice_size * i), 
+                        slice_size, datatype, &min, &max);
+        
+            mivarput1(fd, ncvarid(fd, MIimagemin), &index, 
+                      NC_DOUBLE, MI_SIGNED, &min);
+            mivarput1(fd, ncvarid(fd, MIimagemax), &index, 
+                      NC_DOUBLE, MI_SIGNED, &max);
+            index++;
+        }
+    }
+    else {
+        slice_size = cy * cx;
+        index = sz;
+
+        for (i = 0; i < cz; i++) {
+            find_minmax((char *) dataptr + (dtbytes * slice_size * i), 
+                        slice_size, datatype, &min, &max);
+            mivarput1(fd, ncvarid(fd, MIimagemin), &index, 
+                      NC_DOUBLE, MI_SIGNED, &min);
+            mivarput1(fd, ncvarid(fd, MIimagemax), &index, 
+                      NC_DOUBLE, MI_SIGNED, &max);
+            index++;
+        }
+    }
+
+    /* We want the data to wind up in t, x, y, z order. */
+
+    icv = miicv_create();
+    if (icv < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    r = miicv_setint(icv, MI_ICV_TYPE, nctype);
+    if (r < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    r = miicv_setstr(icv, MI_ICV_SIGN, signstr);
+    if (r < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    r = miicv_setint(icv, MI_ICV_DO_NORM, 1);
+    if (r < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    r = miicv_setint(icv, MI_ICV_DO_FILLVALUE, 1);
+    if (r < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    r = miicv_attach(icv, fd, var_id);
+    if (r < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    i = 0;
+    switch (var_ndims) {
+    case 4:
+        count[i] = ct;
+        start[i] = st;
+        i++;
+        /* fall through */
+    case 3:
+        count[i] = cz;
+        start[i] = sz;
+        i++;
+        /* fall through */
+    case 2:
+        count[i] = cy;
+        start[i] = sy;
+        i++;
+        
+        count[i] = cx;
+        start[i] = sx;
+        i++;
+        break;
+    }
+
+    r = miicv_put(icv, start, count, dataptr);
+    if (r < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    miicv_detach(icv);
+    miicv_free(icv);
+    return (MINC_STATUS_OK);
+}
+
+MNCAPI int 
+minc_save_done(int fd)
+{
+    miattputstr(fd, ncvarid(fd, MIimage), MIcomplete, MI_TRUE);
+    miclose(fd);
+    return (MINC_STATUS_OK);
+}
+
+
+static void 
+normalize_vector(double vector[MINC_3D])
+{
+    int i;
+    double magnitude;
+
+    magnitude = 0.0;
+    for (i = 0; i < MINC_3D; i++) {
+        magnitude += (vector[i] * vector[i]);
+    }
+    magnitude = sqrt(magnitude);
+    if (magnitude > 0.0) {
+        for (i = 0; i < MINC_3D; i++) {
+            vector[i] /= magnitude;
+        }
+    }
+}
+
+MNCAPI int
+minc_get_world_transform(char *path, double transform[MINC_3D][MINC_3D+1])
+{
+    int i, j;
+    double dircos[MINC_3D];
+    double step, start;
+    char *dimensions[] = { MIxspace, MIyspace, MIzspace };
+    int length;
+    int fd;
+    int varid;
+    int old_ncopts;
+
+    old_ncopts = ncopts;
+    ncopts = 0;
+
+    fd = miopen(path, NC_NOWRITE);
+    if (fd < 0) {
+        return (MINC_STATUS_ERROR);
+    }
+
+    /* Zero the matrix */
+    for (i = 0; i < MINC_3D; i++) {
+        for (j = 0; j < MINC_3D + 1; j++) {
+          transform[i][j] = 0.0;
+        }
+        transform[i][i] = 1.0;
+    }
+
+   for (j = 0; j < MINC_3D; j++) {
+
+      /* Set default values */
+      step = 1.0;
+      start = 0.0;
+      for (i = 0; i < MINC_3D; i++) {
+         dircos[i] = 0.0;
+      }
+      dircos[j] = 1.0;
+
+      varid = ncvarid(fd, dimensions[j]);
+      miattget(fd, varid, MIstart, NC_DOUBLE, 1, &start, &length);
+      miattget(fd, varid, MIstep, NC_DOUBLE, 1, &step, &length);
+      miattget(fd, varid, MIdirection_cosines, NC_DOUBLE, 3, dircos, &length);
+
+      normalize_vector(dircos);
+
+      /* Put them in the matrix.
+       */
+      for (i = 0; i < MINC_3D; i++) {
+         transform[i][j] = step * dircos[i];
+         transform[i][MINC_3D] += start * dircos[i];
+      }
+
+   }
+
+   ncopts = old_ncopts;
+   return (MINC_STATUS_OK);
+}
+
+#ifndef MINC2
+
+typedef unsigned long mioffset_t;
+
+/** In-place array dimension restructuring.
+ *
+ * Based on Chris H.Q. Ding, "An Optimal Index Reshuffle Algorithm for
+ * Multidimensional Arrays and its Applications for Parallel Architectures"
+ * IEEE Transactions on Parallel and Distributed Systems, Vol.12, No.3,
+ * March 2001, pp.306-315.
+ * 
+ * I rewrote the algorithm in "C" an generalized it to N dimensions.
+ *
+ * Guaranteed to do the minimum number of memory moves, but requires
+ * that we allocate a bitmap of nelem/8 bytes.  The paper suggests
+ * ways to eliminate the bitmap - I'll work on it.
+ */
+
+/**
+ * Map a set of array coordinates to a linear offset in the array memory.
+ */
+static mioffset_t
+index_to_offset(int ndims, 
+                const unsigned long sizes[], 
+                const unsigned long index[])
+{
+    mioffset_t offset = index[0];
+    int i;
+
+    for (i = 1; i < ndims; i++) {
+        offset *= sizes[i];
+        offset += index[i];
+    }
+    return (offset);
+}
+
+/**
+ * Map a linear offset to a set of coordinates in a multidimensional array.
+ */
+static void
+offset_to_index(int ndims, 
+                const unsigned long sizes[], 
+                mioffset_t offset, 
+                unsigned long index[])
+{
+    int i;
+
+    for (i = ndims - 1; i > 0; i--) {
+        index[i] = offset % sizes[i];
+        offset /= sizes[i];
+    }
+    index[0] = offset;
+}
+
+/* Trivial bitmap test & set.
+ */
+#define BIT_TST(bm, i) (bm[(i) / 8] & (1 << ((i) % 8)))
+#define BIT_SET(bm, i) (bm[(i) / 8] |= (1 << ((i) % 8)))
+
+/** The main restructuring code.
+ */
+void
+restructure_array(int ndims,    /* Dimension count */
+                  unsigned char *array, /* Raw data */
+                  const unsigned long *lengths_perm, /* Permuted lengths */
+                  int el_size,  /* Element size, in bytes */
+                  const int *map, /* Mapping array */
+                  const int *dir) /* Direction array, in permuted order */
+{
+    unsigned long index[MAX_VAR_DIMS]; /* Raw indices */
+    unsigned long index_perm[MAX_VAR_DIMS]; /* Permuted indices */
+    unsigned long lengths[MAX_VAR_DIMS]; /* Raw (unpermuted) lengths */
+    unsigned char *temp;
+    mioffset_t offset_start;
+    mioffset_t offset_next;
+    mioffset_t offset;
+    unsigned char *bitmap;
+    size_t total;
+    int i;
+
+    if ((temp = malloc(el_size)) == NULL) {
+        return;
+    }
+
+    /**
+     * Permute the lengths from their "output" configuration back into
+     * their "raw" or native order:
+     **/
+    for (i = 0; i < ndims; i++) {
+        lengths[i] = lengths_perm[map[i]];
+    }
+
+    /**
+     * Calculate the total size of the array, in elements.
+     **/
+    total = 1;
+    for (i = 0; i < ndims; i++) {
+        total *= lengths[i];
+    }
+
+    /**
+     * Allocate a bitmap with enough space to hold one bit for each
+     * element in the array.
+     **/
+    bitmap = calloc((total + 8 - 1) / 8, 1); /* bit array */
+    if (bitmap == NULL) {
+        return;
+    }
+
+    for (offset_start = 0; offset_start < total; offset_start++) {
+
+        /**
+         * Look for an unset bit - that's where we start the next
+         * cycle.
+         **/
+
+        if (!BIT_TST(bitmap, offset_start)) {
+
+            /**
+             * Found a cycle we have not yet performed.
+             **/
+
+            offset_next = -1;   /* Initialize. */
+
+#ifdef DEBUG
+            printf("%ld", offset_start);
+#endif /* DEBUG */
+
+            /** 
+             * Save the first element in this cycle.
+             **/
+
+            memcpy(temp, array + (offset_start * el_size), el_size);
+
+            /**
+             * We've touched this location.
+             **/
+
+            BIT_SET(bitmap, offset_start);
+
+            offset = offset_start;
+
+            /**
+             * Do until the cycle repeats.
+             **/
+
+            while (offset_next != offset_start) {
+
+                /**
+                 * Compute the index from the offset and permuted length.
+                 **/
+
+                offset_to_index(ndims, lengths_perm, offset, index_perm);
+        
+                /**
+                 * Permute the index into the alternate arrangement.
+                 **/
+
+                for (i = 0; i < ndims; i++) {
+                    if (dir[i] < 0) {
+                        index[i] = lengths[i] - index_perm[map[i]] - 1;
+                    }
+                    else {
+                        index[i] = index_perm[map[i]];
+                    }
+                }
+
+                /**
+                 * Calculate the next offset from the permuted index.
+                 **/
+
+                offset_next = index_to_offset(ndims, lengths, index);
+#ifdef DEBUG
+                if (offset_next >= total) {
+                    printf("Fatal - offset %ld out of bounds!\n", offset_next);
+                    printf("lengths %ld,%ld,%ld\n",
+                           lengths[0],lengths[1],lengths[2]);
+                    printf("index %ld,%ld,%ld\n",
+                           index_perm[0], index_perm[0], index_perm[2]);
+                    exit(-1);
+                }
+#endif
+                /**
+                 * If we are not at the end of the cycle...
+                 **/
+
+                if (offset_next != offset_start) {
+
+                    /**
+                     * Note that we've touched a new location.
+                     **/
+
+                    BIT_SET(bitmap, offset_next);
+
+#ifdef DEBUG
+                    printf(" - %ld", offset_next);
+#endif /* DEBUG */
+                    
+                    /**
+                     * Move from old to new location.
+                     **/
+
+                    memcpy(array + (offset * el_size), 
+                           array + (offset_next * el_size), 
+                           el_size);
+
+                    /** 
+                     * Advance offset to the next location in the cycle.
+                     **/
+
+                    offset = offset_next;
+                }
+            }
+
+            /**
+             * Store the first value in the cycle, which we saved in
+             * 'tmp', into the last offset in the cycle.
+             **/
+
+            memcpy(array + (offset * el_size), temp, el_size);
+
+#ifdef DEBUG
+            printf("\n");
+#endif /* DEBUG */
+        }
+    }
+
+    free(bitmap);               /* Get rid of the bitmap. */
+    free(temp);
+}
+
+#endif /* MINC2 not defined */
+
+#ifdef MINC_SIMPLE_TEST
+/*
+#define NC_TYPE MINC_TYPE_FLOAT
+#define MM_TYPE float
+#define MM_FMT "%20.16g"
+#define EPSILON 1.0e-6
+*/
+
+#define NC_TYPE MINC_TYPE_DOUBLE
+#define MM_TYPE double
+#define MM_FMT "%20.16g"
+#define EPSILON 1.0e-9
+
+
+/*
+#define NC_TYPE MINC_TYPE_CHAR
+#define MM_TYPE unsigned char
+#define MM_FMT "%d"
+*/
+
+/*
+#define NC_TYPE MINC_TYPE_SHORT
+#define MM_TYPE short
+#define MM_FMT "%d"
+*/
+
+/*
+#define NC_TYPE MINC_TYPE_INT
+#define MM_TYPE int
+#define MM_FMT "%d"
+*/
+
+#define CX 11
+#define CY 12
+#define CZ 9
+
+main(int argc, char **argv)
+{
+    short buffer[1][256][256][256];
+    long ct, cx, cy, cz;
+    double dt, dx, dy, dz;
+    long cvoxels, cbytes;
+    int i, j, k;
+    int h;
+
+    MM_TYPE buf_xyz[CX][CY][CZ];
+    MM_TYPE buf_xYz[CX][CY][CZ]; /* Negative Y */
+    MM_TYPE buf_xyZ[CX][CY][CZ]; /* Negative Z */
+    MM_TYPE buf_xzy[CX][CY][CZ];
+    MM_TYPE buf_yxz[CX][CY][CZ];
+    MM_TYPE buf_yzx[CX][CY][CZ];
+    MM_TYPE buf_zyx[CX][CY][CZ];
+    MM_TYPE buf_zxy[CX][CY][CZ];
+    void *inf_xyz;
+    void *inf_xYz;
+    void *inf_xyZ;
+    void *inf_xzy;
+    void *inf_yxz;
+    void *inf_yzx;
+    void *inf_zxy;
+    void *inf_zyx;
+    int errors[6];
+
+    printf("junk-xyz.mnc\n");
+    minc_load_data("junk-xyz.mnc", buf_xyz, NC_TYPE, &ct, &cx, &cy, &cz,
+                   &dt, &dx, &dy, &dz, &inf_xyz);
+
+    printf("junk-xYz.mnc\n");
+    minc_load_data("junk-xYz.mnc", buf_xYz, NC_TYPE, &ct, &cx, &cy, &cz,
+                   &dt, &dx, &dy, &dz, &inf_xYz);
+
+    printf("junk-xyZ.mnc\n");
+    minc_load_data("junk-xyZ.mnc", buf_xyZ, NC_TYPE, &ct, &cx, &cy, &cz,
+                   &dt, &dx, &dy, &dz, &inf_xyZ);
+
+    printf("junk-xzy.mnc\n");
+    minc_load_data("junk-xzy.mnc", buf_xzy, NC_TYPE, &ct, &cx, &cy, &cz,
+                   &dt, &dx, &dy, &dz, &inf_xzy);
+
+    printf("junk-yxz.mnc\n");
+    minc_load_data("junk-yxz.mnc", buf_yxz, NC_TYPE, &ct, &cx, &cy, &cz,
+                   &dt, &dx, &dy, &dz, &inf_yxz);
+
+    printf("junk-yzx.mnc\n");
+    minc_load_data("junk-yzx.mnc", buf_yzx, NC_TYPE, &ct, &cx, &cy, &cz,
+                   &dt, &dx, &dy, &dz, &inf_yzx);
+
+    printf("junk-zxy.mnc\n");
+    minc_load_data("junk-zxy.mnc", buf_zxy, NC_TYPE, &ct, &cx, &cy, &cz,
+                   &dt, &dx, &dy, &dz, &inf_zxy);
+
+    printf("junk-zyx.mnc\n");
+    minc_load_data("junk-zyx.mnc", buf_zyx, NC_TYPE, &ct, &cx, &cy, &cz,
+                   &dt, &dx, &dy, &dz, &inf_zyx);
+
+    for (i = 0; i < 6; i++) {
+        errors[i] = 0;
+    }
+
+#ifdef EPSILON
+#define CLOSE(a, b) (fabs(a - b) < EPSILON)
+#else
+#define CLOSE(a, b) (a == b)
+#endif
+
+    for (i = 0; i < CX; i++) {
+        for (j = 0; j < CY; j++) {
+            for (k = 0; k < CZ; k++) {
+                double e;
+                if (!CLOSE(buf_xyz[i][j][k], buf_xzy[i][j][k])) {
+                    printf(MM_FMT, buf_xyz[i][j][k]);
+                    printf(" ");
+                    printf(MM_FMT, buf_xzy[i][j][k]);
+                    printf("\n");
+                    errors[1]++;
+                }
+                if (!CLOSE(buf_xyz[i][j][k], buf_xYz[i][j][k])) {
+                    errors[0]++;
+                }
+                if (!CLOSE(buf_xyz[i][j][k], buf_xyZ[i][j][k])) {
+                    errors[0]++;
+                }
+                if (!CLOSE(buf_xyz[i][j][k], buf_yxz[i][j][k])) {
+                    errors[2]++;
+                }
+                if (!CLOSE(buf_xyz[i][j][k], buf_yzx[i][j][k])) {
+                    errors[3]++;
+                }
+                if (!CLOSE(buf_xyz[i][j][k], buf_zxy[i][j][k])) {
+                    errors[4]++;
+                }
+                if (!CLOSE(buf_xyz[i][j][k], buf_zyx[i][j][k])) {
+                    errors[5]++;
+                }
+            }
+        }
+    }
+
+    for (i = 0; i < 6; i++) {
+        printf("%d - ", errors[i]);
+        printf("\n");
+    }
+
+    h = minc_save_start("temp-xyz.mnc", MINC_TYPE_SHORT, ct, cx, cy, cz, 
+                        dt, dx, dy, dz, inf_xyz, "testing");
+    minc_save_data(h, buf_xyz, NC_TYPE, 0, 0, 0, 0, ct, cx, cy, cz);
+    minc_save_done(h);
+
+    h = minc_save_start("temp-xzy.mnc", MINC_TYPE_SHORT, ct, cx, cy, cz, 
+                        dt, dx, dy, dz, inf_xzy, "testing");
+    minc_save_data(h, buf_xzy, NC_TYPE, 0, 0, 0, 0, ct, cx, cy, cz);
+    minc_save_done(h);
+
+    h = minc_save_start("temp-yxz.mnc", MINC_TYPE_SHORT, ct, cx, cy, cz, 
+                        dt, dx, dy, dz, inf_yxz, "testing");
+    minc_save_data(h, buf_yxz, NC_TYPE, 0, 0, 0, 0, ct, cx, cy, cz);
+    minc_save_done(h);
+
+    h = minc_save_start("temp-yzx.mnc", MINC_TYPE_SHORT, ct, cx, cy, cz, 
+                        dt, dx, dy, dz, inf_yzx, "testing");
+    minc_save_data(h, buf_yzx, NC_TYPE, 0, 0, 0, 0, ct, cx, cy, cz);
+    minc_save_done(h);
+
+    h = minc_save_start("temp-zxy.mnc", MINC_TYPE_SHORT, ct, cx, cy, cz, 
+                        dt, dx, dy, dz, inf_zxy, "testing");
+    minc_save_data(h, buf_zxy, NC_TYPE, 0, 0, 0, 0, ct, cx, cy, cz);
+    minc_save_done(h);
+
+    h = minc_save_start("temp-zyx.mnc", MINC_TYPE_SHORT, ct, cx, cy, cz, 
+                        dt, dx, dy, dz, inf_zyx, "testing");
+    minc_save_data(h, buf_zyx, NC_TYPE, 0, 0, 0, 0, ct, cx, cy, cz);
+    minc_save_done(h);
+}
+
+#endif /* MINC_SIMPLE_TEST */