view volume_io/Volumes/output_mnc.c @ 2590:4089d520ca08

making a super build
author Vladimir Fonov <vladimir.fonov@gmail.com>
date Wed, 18 Jan 2012 02:58:47 -0500
parents aaf8cc96fda0
children
line wrap: on
line source

/* ----------------------------------------------------------------------------
@COPYRIGHT  :
              Copyright 1993,1994,1995 David MacDonald,
              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  <internal_volume_io.h>
#include  <minc_basic.h>

#define  INVALID_AXIS   -1

#define  UNITS           "mm"

static  Status  get_dimension_ordering(
    int          n_vol_dims,
    STRING       vol_dim_names[],
    int          n_file_dims,
    STRING       file_dim_names[],
    int          to_volume[],
    int          to_file[] );

/* ----------------------------- MNI Header -----------------------------------
@NAME       : is_default_direction_cosine
@INPUT      : axis
              dir_cosines
@OUTPUT     : 
@RETURNS    : TRUE if is default
@DESCRIPTION: Checks to see if the cosine is the default for the axis,
              i.e., for x axis, is it ( 1, 0, 0 ).
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

static  BOOLEAN  is_default_direction_cosine(
    int        axis,
    double     dir_cosines[] )
{
    BOOLEAN   is_default;
    int       i;

    is_default = TRUE;
    for_less( i, 0, N_DIMENSIONS )
    {
        if( i == axis && dir_cosines[i] != 1.0 ||
            i != axis && dir_cosines[i] != 0.0 )
        {
            is_default = FALSE;
            break;
        }
    }

    return( is_default );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : output_world_transform
@INPUT      : file
              space_type
              voxel_to_world_transform
@OUTPUT     : 
@RETURNS    : OK or ERROR
@DESCRIPTION: Outputs the voxel to world transformation, in terms of MINC
              starts, steps, and direction cosines.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : Oct. 24, 1995    David MacDonald
@MODIFIED   : Nov. 15, 1996    D. MacDonald  - added handling of space type
@MODIFIED   : May. 20, 1997    D. MacDonald  - removed float arithmetic
@MODIFIED   : May  22, 1997   D. MacDonald - added use_volume_starts_and_steps
---------------------------------------------------------------------------- */

static  Status  output_world_transform(
    Minc_file              file,
    STRING                 space_type,
    General_transform      *voxel_to_world_transform,
    BOOLEAN                use_volume_starts_and_steps_flag )
{
    double              step[MAX_VAR_DIMS];
    Real                start[MAX_VAR_DIMS];
    double              dir_cosines[MAX_DIMENSIONS][N_DIMENSIONS];
    int                 dim, axis, spatial_axes[N_DIMENSIONS];

    /*--- set all starts/steps/dir_cosines to default */

    for_less( dim, 0, file->n_file_dimensions )
    {
        start[dim] = 0.0;
        step[dim] = 1.0;
        dir_cosines[dim][X] = 0.0;
        dir_cosines[dim][Y] = 0.0;
        dir_cosines[dim][Z] = 0.0;
    }

    /*--- if must use the volume's starts and steps */

    if( use_volume_starts_and_steps_flag )
    {
        for_less( dim, 0, file->n_file_dimensions )
        {
            if( convert_dim_name_to_spatial_axis( file->dim_names[dim], &axis ))
            {
                if( file->to_volume_index[dim] == INVALID_AXIS )
                    dir_cosines[dim][axis] = 1.0;    /*--- default */
                else
                {
                    start[dim] =
                          file->volume->starts[file->to_volume_index[dim]];
                    step[dim] =
                          file->volume->separations[file->to_volume_index[dim]];
                    dir_cosines[dim][X] = file->volume->direction_cosines
                                       [file->to_volume_index[dim]][X];
                    dir_cosines[dim][Y] = file->volume->direction_cosines
                                       [file->to_volume_index[dim]][Y];
                    dir_cosines[dim][Z] = file->volume->direction_cosines
                                       [file->to_volume_index[dim]][Z];
                }
            }
        }
    }
    else  /*--- convert the linear transform to starts/steps/dir cosines */
    {
        if( voxel_to_world_transform == NULL ||
            get_transform_type( voxel_to_world_transform ) != LINEAR )
        {
            print_error(
             "Cannot output null or non-linear transforms.  Using identity.\n");

            for_less( dim, 0, file->n_file_dimensions )
            {
                if( convert_dim_name_to_spatial_axis( file->dim_names[dim],
                                                      &axis ))
                    dir_cosines[dim][axis] = 1.0;
            }
        }
        else
        {
            spatial_axes[0] = INVALID_AXIS;
            spatial_axes[1] = INVALID_AXIS;
            spatial_axes[2] = INVALID_AXIS;

            for_less( dim, 0, file->n_file_dimensions )
            {
                if( convert_dim_name_to_spatial_axis( file->dim_names[dim],
                                                      &axis ))
                    spatial_axes[axis] = dim;
            }

            convert_transform_to_starts_and_steps( voxel_to_world_transform,
                                                   file->n_file_dimensions,
                                                   NULL, spatial_axes,
                                                   start, step,
                                                   dir_cosines );
        }
    }

    for_less( dim, 0, file->n_file_dimensions )
    {
        if( convert_dim_name_to_spatial_axis( file->dim_names[dim], &axis ) )
        {
            file->dim_ids[dim] = micreate_std_variable( file->cdfid,
                                      file->dim_names[dim], NC_DOUBLE, 0, NULL);

            if( file->dim_ids[dim] < 0 )
                return( ERROR );

            (void) miattputdbl( file->cdfid, file->dim_ids[dim], MIstep,
                                step[dim]);
            (void) miattputdbl( file->cdfid, file->dim_ids[dim], MIstart,
                                start[dim]);
            if( !is_default_direction_cosine( axis, dir_cosines[dim] ) )
            {
                (void) ncattput( file->cdfid, file->dim_ids[dim],
                                 MIdirection_cosines,
                                 NC_DOUBLE, N_DIMENSIONS, dir_cosines[dim]);
            }
            (void) miattputstr( file->cdfid, file->dim_ids[dim], MIunits,
                                UNITS );
            if( !equal_strings( space_type, MI_UNKNOWN_SPACE ) )
            {
                (void) miattputstr( file->cdfid, file->dim_ids[dim],
                                    MIspacetype, space_type );
            }
        }
        else
            file->dim_ids[dim] = -1;
    }

    return( OK );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : create_image_variable
@INPUT      : file
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Defines the image variable in the output minc file. This 
              should be done as the last thing before ending the header 
              definition.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : September 13, 2001    Peter Neelin
@MODIFIED   : 
---------------------------------------------------------------------------- */

static void create_image_variable(Minc_file file)
{
    int old_ncopts;

    old_ncopts = ncopts;

    /* Create the variable */
    file->img_var_id = micreate_std_variable( file->cdfid, MIimage,
                                              file->nc_data_type,
                                              file->n_file_dimensions, 
                                              file->image_dims );

    /* Copy all attributes if required */
    if( file->src_img_var != MI_ERROR )
    {
        ncopts = 0;
        (void) micopy_all_atts( file->src_cdfid, file->src_img_var,
                                file->cdfid, file->img_var_id );
        (void) ncattdel( file->cdfid, file->img_var_id, MIvalid_max );
        (void) ncattdel( file->cdfid, file->img_var_id, MIvalid_min );
        (void) ncattdel( file->cdfid, file->img_var_id, MIvalid_range );

        ncopts = old_ncopts;
    }

    (void) miattputstr( file->cdfid, file->img_var_id, MIcomplete, MI_FALSE );

    if( file->signed_flag )
        (void) miattputstr( file->cdfid, file->img_var_id, MIsigntype,
                            MI_SIGNED );
    else
        (void) miattputstr( file->cdfid, file->img_var_id, MIsigntype,
                            MI_UNSIGNED );

    /* --- put the valid voxel range */

    if( file->valid_range[0] < file->valid_range[1] )
    {

        (void) miset_valid_range( file->cdfid, file->img_var_id, 
                                  file->valid_range);

    }

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : end_file_def
@INPUT      : file
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Ends the definition of the file, calling ncendef, but
              first creating the image variable as the last variable in 
              the file. This is done to allow images > 2 GB (on 64-bit 
              machines) and to ensure that data is written right to the 
              end of the file for backwards compatibility.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : September 13, 2001    Peter Neelin
@MODIFIED   : 
---------------------------------------------------------------------------- */

static Status end_file_def(Minc_file file)
{
   int ret;

   create_image_variable(file);

   ret = ncendef( file->cdfid );

   return ( ret == MI_ERROR ? ERROR : OK );

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : initialize_minc_output
@INPUT      : filename
              n_dimensions
              dim_names
              sizes
              file_nc_data_type
              file_signed_flag
              file_voxel_min
              file_voxel_max
              voxel_to_world_transform
              volume_to_attach
              options
@OUTPUT     : 
@RETURNS    : minc file
@DESCRIPTION: Creates a minc file for outputting volumes.  The n_dimensions,
              dim_names, sizes, file_nc_data_type, file_signed_flag,
              file_voxel_min, file_voxel_max, and voxel_to_world_transform
              define the type and shape of the file.  The volume_to_attach
              is the volume that will be output once or many times to 
              fill up the file.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : Nov. 15, 1996   D. MacDonald  - added handling of space type
@MODIFIED   : Nov.  2, 1998   D. MacDonald  - fixed the bug with non-global
                                              limits on multiple volumes,
                                              found by peter
---------------------------------------------------------------------------- */

VIOAPI  Minc_file  initialize_minc_output(
    STRING                 filename,
    int                    n_dimensions,
    STRING                 dim_names[],
    int                    sizes[],
    nc_type                file_nc_data_type,
    BOOLEAN                file_signed_flag,
    Real                   file_voxel_min,
    Real                   file_voxel_max,
    General_transform      *voxel_to_world_transform,
    Volume                 volume_to_attach,
    minc_output_options    *options )
{
    minc_file_struct    *file;
    int                 volume_sizes[MAX_DIMENSIONS];
    int                 n_volume_dims;
    int                 d, vol_index, n_range_dims;
    static  STRING      default_dim_names[] = { MIzspace, MIyspace, MIxspace };
    STRING              *vol_dimension_names;
    minc_output_options default_options;

    if( options == (minc_output_options *) NULL )
    {
        set_default_minc_output_options( &default_options );
        options = &default_options;
    }

    if( dim_names == NULL )
    {
        if( n_dimensions != 3 )
        {
            print_error( "initialize_minc_output: " );
            print_error(
                "can't use NULL dim_names except with 3 dimensions.\n" );
            return( (Minc_file) NULL );
        }

        dim_names = default_dim_names;
    }

    if( file_nc_data_type == MI_ORIGINAL_TYPE )
    {
        file_nc_data_type = get_volume_nc_data_type( volume_to_attach,
                                                     &file_signed_flag );
        get_volume_voxel_range( volume_to_attach,
                                &file_voxel_min, &file_voxel_max );
    }
    else if( (file_nc_data_type == NC_FLOAT ||
              file_nc_data_type == NC_DOUBLE) &&
              file_voxel_min >= file_voxel_max )
    {
        get_volume_real_range( volume_to_attach,
                               &file_voxel_min, &file_voxel_max );
    }

    /* --- check if dimension name correspondence between volume and file */

    n_volume_dims = get_volume_n_dimensions( volume_to_attach );

    if( n_volume_dims > n_dimensions )
    {
        print_error( "initialize_minc_output:" );
        print_error( " volume (%d) has more dimensions than file (%d).\n",
                     n_volume_dims, n_dimensions );
        return( (Minc_file) NULL );
    }

    ALLOC( file, 1 );

    file->file_is_being_read = FALSE;
    file->n_file_dimensions = n_dimensions;
    file->volume = volume_to_attach;
    file->outputting_in_order = TRUE;
    file->entire_file_written = FALSE;
    file->ignoring_because_cached = FALSE;
    file->src_img_var = MI_ERROR;

    file->filename = expand_filename( filename );

    if( volume_to_attach->is_cached_volume &&
        volume_to_attach->cache.output_file_is_open &&
        equal_strings( volume_to_attach->cache.output_filename, file->filename))
    {
        file->ignoring_because_cached = TRUE;
        flush_volume_cache( volume_to_attach );
        return( file );
    }

    /*--- find correspondence between volume dimensions and file dimensions */

    vol_dimension_names = get_volume_dimension_names( volume_to_attach );

    if( get_dimension_ordering( n_volume_dims, vol_dimension_names,
                                n_dimensions, dim_names,
                            file->to_volume_index, file->to_file_index ) != OK )
    {
        FREE( file );
        return( (Minc_file) NULL );
    }

    delete_dimension_names( volume_to_attach, vol_dimension_names );

    /*--- check if image range specified */

    if( options->global_image_range[0] >= options->global_image_range[1] )
    {
        n_range_dims = n_dimensions - 2;
        if( equal_strings( dim_names[n_dimensions-1], MIvector_dimension ) )
            --n_range_dims;

        for_less( d, n_range_dims, n_dimensions )
        {
            if( file->to_volume_index[d] == INVALID_AXIS )
            {
                print_error( "initialize_minc_output: " );
                print_error( "if outputting volumes which don't contain all image\n");
                print_error( "dimensions, then must specify global image range.\n" );
                FREE( file );
                return( (Minc_file) NULL );
            }
        }
    }

    /*--- check sizes match between volume and file */

    get_volume_sizes( volume_to_attach, volume_sizes );

    for_less( d, 0, n_dimensions )
    {
        vol_index = file->to_volume_index[d];

        if( vol_index >= 0 && volume_sizes[vol_index] != sizes[d] )
        {
            print_error( "initialize_minc_output: " );
            print_error( "volume size[%d]=%d does not match file[%d]=%d.\n",
                   vol_index, volume_sizes[vol_index], d, sizes[d] );
            return( NULL );
        }
    }

    /*--- create the file */

    ncopts = NC_VERBOSE;

    file->cdfid =  micreate( file->filename, NC_CLOBBER );

    if( file->cdfid == MI_ERROR )
    {
        print_error( "Error: opening MINC file \"%s\".\n", file->filename );
        return( NULL );
    }

    /* Create the root variable */
    (void) micreate_std_variable(file->cdfid, MIrootvariable, 
                                 NC_INT, 0, NULL);

    for_less( d, 0, n_dimensions )
    {
        file->sizes_in_file[d] = (long) sizes[d];
        file->indices[d] = 0;
        file->dim_names[d] = create_string( dim_names[d] );
        file->image_dims[d] = ncdimdef( file->cdfid, file->dim_names[d],
                                        (long) sizes[d] );
    }

    if( output_world_transform( file, volume_to_attach->coordinate_system_name,
                                voxel_to_world_transform,
                                options->use_volume_starts_and_steps ) != OK )
    {
        FREE( file );
        return( NULL );
    }

    /*
     * Save information for creating image variable later
     */

    file->nc_data_type = file_nc_data_type;
    file->signed_flag = file_signed_flag;
    file->valid_range[0] = file_voxel_min;
    file->valid_range[1] = file_voxel_max;
            
    file->image_range[0] = options->global_image_range[0];
    file->image_range[1] = options->global_image_range[1];

    if( file->image_range[0] < file->image_range[1] )
    {
        file->min_id = micreate_std_variable( file->cdfid, MIimagemin,
                                              NC_DOUBLE, 0, (int *) NULL );
        file->max_id = micreate_std_variable( file->cdfid, MIimagemax,
                                              NC_DOUBLE, 0, (int *) NULL );
    }
    else
    {
        n_range_dims = n_dimensions - 2;
        if( equal_strings( dim_names[n_dimensions-1], MIvector_dimension ) )
            --n_range_dims;

        file->min_id = micreate_std_variable( file->cdfid, MIimagemin,
                                              NC_DOUBLE, n_range_dims, 
                                              file->image_dims);
        file->max_id = micreate_std_variable( file->cdfid, MIimagemax,
                                              NC_DOUBLE, n_range_dims, 
                                              file->image_dims );
    }

    ncopts = NC_VERBOSE | NC_FATAL;

    file->end_def_done = FALSE;
    file->variables_written = FALSE;

    return( file );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : copy_auxiliary_data_from_minc_file
@INPUT      : file
              filename
              history_string
@OUTPUT     : 
@RETURNS    : OK or ERROR
@DESCRIPTION: Copies the auxiliary data from the filename to the opened
              Minc file, 'file'.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  Status  copy_auxiliary_data_from_minc_file(
    Minc_file   file,
    STRING      filename,
    STRING      history_string )
{
    Status  status;
    int     src_cdfid;
    STRING  expanded;

    if( file->ignoring_because_cached )
        return( OK );

    ncopts = NC_VERBOSE;

    expanded = expand_filename( filename );

    src_cdfid =  miopen( expanded, NC_NOWRITE );

    if( src_cdfid == MI_ERROR )
    {
        print_error( "Error opening %s\n", expanded );
        return( ERROR );
    }

    delete_string( expanded );

    status = copy_auxiliary_data_from_open_minc_file( file, src_cdfid,
                                                      history_string );

    (void) miclose( src_cdfid );

    ncopts = NC_VERBOSE | NC_FATAL;

    return( status );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : copy_auxiliary_data_from_open_minc_file
@INPUT      : file
              src_cdfid
              history_string
@OUTPUT     : 
@RETURNS    : OK or ERROR
@DESCRIPTION: Copies the auxiliary data from the opened minc file specified
              by src_cdfid to the opened minc file specified by 'file'.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  Status  copy_auxiliary_data_from_open_minc_file(
    Minc_file   file,
    int         src_cdfid,
    STRING      history_string )
{
    int     src_img_var, varid, n_excluded, excluded_vars[10];
    int     i, src_min_id, src_max_id, src_root_id;
    Status  status;
    STRING  excluded_list[] = {
                                  MIxspace,
                                  MIyspace,
                                  MIzspace,
                                  MItime,
                                  MItfrequency,
                                  MIxfrequency,
                                  MIyfrequency,
                                  MIzfrequency,
                                  MIvector_dimension
                               };

    if( file->ignoring_because_cached )
        return( OK );

    if( file->end_def_done )
    {
        print_error( "Cannot call copy_auxiliary_data_from_open_minc_file when not in define mode\n" );
        return( ERROR );
    }

    ncopts = 0;

    n_excluded = 0;

    for_less( i, 0, SIZEOF_STATIC_ARRAY( excluded_list ) )
    {
        if( (varid = ncvarid(src_cdfid, excluded_list[i] )) != MI_ERROR )
            excluded_vars[n_excluded++] = varid;
    }

    if( (src_img_var = ncvarid(src_cdfid, MIimage )) != MI_ERROR )
        excluded_vars[n_excluded++] = src_img_var;
    if( (src_max_id = ncvarid(src_cdfid, MIimagemax )) != MI_ERROR )
        excluded_vars[n_excluded++] = src_max_id;
    if( (src_min_id = ncvarid(src_cdfid, MIimagemin )) != MI_ERROR )
        excluded_vars[n_excluded++] = src_min_id;
    if( (src_root_id = ncvarid(src_cdfid, MIrootvariable )) != MI_ERROR )
        excluded_vars[n_excluded++] = src_root_id;

    ncopts = NC_VERBOSE;

    (void) micopy_all_var_defs( src_cdfid, file->cdfid, n_excluded,
                                excluded_vars );

    if( src_min_id != MI_ERROR )
    {
        (void) micopy_all_atts( src_cdfid, src_min_id,
                                file->cdfid, file->min_id );
    }

    if( src_max_id != MI_ERROR )
    {
        (void) micopy_all_atts( src_cdfid, src_max_id,
                                file->cdfid, file->max_id );
    }

    if( src_root_id != MI_ERROR )
    {
        (void) micopy_all_atts( src_cdfid, src_root_id,
                                file->cdfid,
                                ncvarid( file->cdfid, MIrootvariable) );
    }

    status = OK;

    if( history_string != NULL )
        status = add_minc_history( file, history_string );

    if( status == OK )
    {

        /* Set info for copying image attributes. Unset afterwards, just
           to be safe. */
        file->src_cdfid = src_cdfid;
        file->src_img_var = src_img_var;

        status = end_file_def( file );

        file->src_img_var = MI_ERROR;

        if( status != OK )
        {
            print_error( "Error outputting volume: possibly disk full?\n" );
        }
    }

    if( status == OK )
    {
        file->end_def_done = TRUE;

        (void) micopy_all_var_values( src_cdfid, file->cdfid,
                                      n_excluded, excluded_vars );
    }

    ncopts = NC_VERBOSE | NC_FATAL;

    return( status );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : add_minc_history
@INPUT      : file
              history_string
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Adds the history_string to the history in the file.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : Nov. 2, 1998  D. MacDonald - fixed error P. Neelin found with
                                           concating to non-existent history
---------------------------------------------------------------------------- */

VIOAPI  Status  add_minc_history(
    Minc_file   file,
    STRING      history_string )
{
    int      old_att_length;
    nc_type  datatype;
    STRING   new_history;

    if( file->ignoring_because_cached )
        return( OK );

    if( file->end_def_done )
    {
        print_error( "Cannot call add_minc_history when not in define mode\n" );
        return( ERROR );
    }

    ncopts = 0;

    if( ncattinq(file->cdfid, NC_GLOBAL, MIhistory, &datatype, &old_att_length)
                                                          == MI_ERROR ||
        datatype != NC_CHAR )
    {
        old_att_length = 0;
    }

    /* Allocate a string and get the old history */

    new_history = alloc_string( old_att_length );
    new_history[0] = (char) 0;

    (void) miattgetstr( file->cdfid, NC_GLOBAL, MIhistory, old_att_length+1,
                        new_history );

/*
    if( new_history[old_att_length] != (char) 0 )
    {
        print( "old_att_length %d  %d\n", old_att_length,
                new_history[old_att_length] );
        new_history[old_att_length] = (char) 0;
        print( "string: %d\n", new_history );
    }
*/

    /* Add the new command and put the new history. */

    concat_to_string( &new_history, history_string );

    ncopts = NC_VERBOSE | NC_FATAL;
    (void) miattputstr( file->cdfid, NC_GLOBAL, MIhistory, new_history );

    delete_string( new_history );

    return( OK );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : get_dimension_ordering
@INPUT      : n_vol_dims
              vol_dim_names
              n_file_dims
              file_dim_names
@OUTPUT     : to_volume
              to_file
@RETURNS    : OK or ERROR
@DESCRIPTION: Matches dimension names between the volume and file, setting
              the axis conversion from file to_volume and from volume to_file.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

static  Status  get_dimension_ordering(
    int          n_vol_dims,
    STRING       vol_dim_names[],
    int          n_file_dims,
    STRING       file_dim_names[],
    int          to_volume[],
    int          to_file[] )
{
    Status   status;
    int      v, f, n_found;

    for_less( f, 0, n_file_dims )
        to_volume[f] = -1;

    for_less( v, 0, n_vol_dims )
        to_file[v] = -1;

    n_found = 0;

    for_less( v, 0, n_vol_dims )
    {
        for_less( f, 0, n_file_dims )
        {
            if( to_volume[f] < 0 &&
                equal_strings( vol_dim_names[v], file_dim_names[f] ) )
            {
                to_volume[f] = v;
                to_file[v] = f;
                ++n_found;
            }
        }
    }

    if( n_found != n_vol_dims )
    {
        print_error( "Unsuccessful matching of volume and output dimension names.\n");
        status = ERROR;
    }
    else
        status = OK;

    return( status );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : check_minc_output_variables
@INPUT      : file
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Checks if the file variables has been put into data mode,
              and does so if necessary.  Then it checks if the variables have
              been written, and does so if necessary.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : Sep. 1, 1995    David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

static  Status  check_minc_output_variables(
    Minc_file   file )
{
    int               d, axis;
    long              start_index, mindex[MAX_VAR_DIMS];
    Real              voxel_min, voxel_max, real_min, real_max;
    double            dim_value;
    Volume            volume;
    Status            status;

    if( !file->end_def_done )
    {
        /* --- Get into data mode */

        ncopts = NC_VERBOSE;
        status = end_file_def( file );
        ncopts = NC_VERBOSE | NC_FATAL;
        file->end_def_done = TRUE;

        if( status != OK )
        {
            print_error( "Error outputting volume: possibly disk full?\n" );
            return( status );
        }
    }

    if( !file->variables_written )
    {
        volume = file->volume;

        file->variables_written = TRUE;

        ncopts = NC_VERBOSE;
        for_less( d, 0, file->n_file_dimensions )
            mindex[d] = 0;

        dim_value = 0.0;
        for_less( d, 0, file->n_file_dimensions )
        {
            if( convert_dim_name_to_spatial_axis( file->dim_names[d], &axis ) )
            {
                (void) mivarput1( file->cdfid, file->dim_ids[d], mindex,
                                  NC_DOUBLE, MI_SIGNED, &dim_value );
            }
        }

        file->minc_icv = miicv_create();

        (void) miicv_setint( file->minc_icv, MI_ICV_TYPE,
                             (int) volume->nc_data_type);
        (void) miicv_setstr( file->minc_icv, MI_ICV_SIGN,
                             volume->signed_flag ? MI_SIGNED : MI_UNSIGNED );
        (void) miicv_setint( file->minc_icv, MI_ICV_DO_NORM, TRUE );
        (void) miicv_setint( file->minc_icv, MI_ICV_USER_NORM, TRUE );

        if( file->image_range[0] < file->image_range[1] )
        {
            (void) miicv_setdbl( file->minc_icv, MI_ICV_IMAGE_MIN,
                                 file->image_range[0] );
            (void) miicv_setdbl( file->minc_icv, MI_ICV_IMAGE_MAX,
                                 file->image_range[1] );
        }
        else
        {
            get_volume_real_range( volume, &real_min, &real_max );
            (void) miicv_setdbl( file->minc_icv, MI_ICV_IMAGE_MIN, real_min );
            (void) miicv_setdbl( file->minc_icv, MI_ICV_IMAGE_MAX, real_max );
        }

        get_volume_voxel_range( volume, &voxel_min, &voxel_max );
        if( voxel_min < voxel_max )
        {
            (void) miicv_setdbl( file->minc_icv, MI_ICV_VALID_MIN, voxel_min );
            (void) miicv_setdbl( file->minc_icv, MI_ICV_VALID_MAX, voxel_max );
        }
        else
            print_error( "Volume has invalid min and max voxel value\n" );

        (void) miicv_attach( file->minc_icv, file->cdfid, file->img_var_id );

        start_index = 0;

        if( file->image_range[0] < file->image_range[1] )
        {
            (void) mivarput1( file->cdfid, file->min_id, &start_index,
                              NC_DOUBLE, MI_SIGNED, &file->image_range[0] );
            (void) mivarput1( file->cdfid, file->max_id, &start_index,
                              NC_DOUBLE, MI_SIGNED, &file->image_range[1] );
        }
        ncopts = NC_VERBOSE | NC_FATAL;
    }

    return( OK );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : set_minc_output_random_order
@INPUT      : file
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Sets the file into random order access, used by volume
              caching.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : Oct. 26, 1995    David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  Status  set_minc_output_random_order(
    Minc_file   file )
{
    Status  status;

    status = check_minc_output_variables( file );

    file->outputting_in_order = FALSE;

    return( status );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : output_minc_hyperslab
@INPUT      : file
              data_type
              n_array_dims
              array_sizes
              array_data_ptr
              to_array
              file_start
              file_count
@OUTPUT     : 
@RETURNS    : OK or ERROR
@DESCRIPTION: Outputs a hyperslab from an array to the file.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : Sep. 1, 1995    David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  Status  output_minc_hyperslab(
    Minc_file           file,
    Data_types          data_type,
    int                 n_array_dims,
    int                 array_sizes[],
    void                *array_data_ptr,
    int                 to_array[],
    int                 file_start[],
    int                 file_count[] )
{
    int              ind, expected_ind, file_ind, dim;
    int              n_file_dims, n_tmp_dims;
    long             long_file_start[MAX_DIMENSIONS];
    long             long_file_count[MAX_DIMENSIONS];
    void             *void_ptr;
    BOOLEAN          direct_from_array, non_full_size_found;
    int              tmp_ind, tmp_sizes[MAX_DIMENSIONS];
    int              array_indices[MAX_DIMENSIONS];
    int              array_counts[MAX_VAR_DIMS];
    Status           status;
    multidim_array   buffer_array;

    status = check_minc_output_variables( file );

    if( status != OK )
        return( status );

    n_file_dims = file->n_file_dimensions;
    expected_ind = n_array_dims-1;
    tmp_ind = n_file_dims-1;
    non_full_size_found = FALSE;

    for_less( ind, 0, n_array_dims )
    {
        array_indices[ind] = -1;
        array_counts[ind] = 1;
    }

    direct_from_array = TRUE;

    /*--- determine if the hyperslab represents a consecutive chunk of
          memory in the array */

    for( file_ind = n_file_dims-1;  file_ind >= 0;  --file_ind )
    {
        long_file_start[file_ind] = (long) file_start[file_ind];
        long_file_count[file_ind] = (long) file_count[file_ind];
        ind = to_array[file_ind];
        if( ind != INVALID_AXIS )
        {
            array_counts[ind] = file_count[file_ind];

            if( !non_full_size_found &&
                (long) file_count[file_ind] < file->sizes_in_file[file_ind] )
                non_full_size_found = TRUE;
            else if( non_full_size_found && file_count[file_ind] > 1 )
                direct_from_array = FALSE;

            if( file_count[file_ind] > 1 && ind != expected_ind )
                direct_from_array = FALSE;

            if( file_count[file_ind] != 1 ||
                file->sizes_in_file[file_ind] == 1 )
            {
                tmp_sizes[tmp_ind] = file_count[file_ind];
                array_indices[ind] = tmp_ind;
                --tmp_ind;
            }

            --expected_ind;
        }
    }

    if( direct_from_array )     /* hyperslab is consecutive chunk of memory */
    {
        void_ptr = array_data_ptr;
    }
    else
    {
        /*--- create a temporary array to copy hyperslab to, so that
              we have a consecutive chunk of memory to write from */

        n_tmp_dims = n_file_dims - tmp_ind - 1;

        for_less( dim, 0, n_tmp_dims )
            tmp_sizes[dim] = tmp_sizes[dim+tmp_ind+1];

        for_less( dim, 0, n_array_dims )
            array_indices[dim] -= tmp_ind + 1;

        create_multidim_array( &buffer_array, n_tmp_dims, tmp_sizes, data_type);

        GET_MULTIDIM_PTR( void_ptr, buffer_array, 0, 0, 0, 0, 0 );

        /*--- copy from the array argument to the temporary array */

        copy_multidim_data_reordered( get_type_size(data_type),
                                      void_ptr, n_tmp_dims, tmp_sizes,
                                      array_data_ptr, n_array_dims, array_sizes,
                                      array_counts, array_indices, TRUE );

        GET_MULTIDIM_PTR( void_ptr, buffer_array, 0, 0, 0, 0, 0 );
    }

    /*--- output the data to the file */

    if( miicv_put( file->minc_icv, long_file_start, long_file_count,
                   void_ptr ) == MI_ERROR )
        status = ERROR;
    else
        status = OK;

    if( !direct_from_array )
        delete_multidim_array( &buffer_array );

    return( status );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : output_slab
@INPUT      : file
              volume
              to_volume
              file_start
              file_count
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Outputs the slab specified by the file start and count arrays,
              from the volume.  The to_volume array translates axes in the file
              to axes in the volume.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : Sep  1, 1995    D. MacDonald - added cached volumes.
---------------------------------------------------------------------------- */

static  void  output_slab(
    Minc_file   file,
    Volume      volume,
    int         to_volume[],
    long        file_start[],
    long        file_count[] )
{
    int               dim, file_ind, ind, n_slab_dims;
    int               to_array[MAX_DIMENSIONS];
    int               volume_start[MAX_DIMENSIONS];
    int               volume_sizes[MAX_DIMENSIONS];
    int               array_to_volume[MAX_DIMENSIONS];
    int               int_file_count[MAX_DIMENSIONS];
    int               int_file_start[MAX_DIMENSIONS];
    int               slab_sizes[MAX_DIMENSIONS];
    int               v[MAX_DIMENSIONS];
    int               size0, size1, size2, size3, size4;
    Real              value;
    void              *array_data_ptr;
    multidim_array    array;

    for_less( file_ind, 0, file->n_file_dimensions )
    {
        int_file_start[file_ind] = (int) file_start[file_ind];
        int_file_count[file_ind] = (int) file_count[file_ind];

        ind = to_volume[file_ind];
        if( ind != INVALID_AXIS )
            volume_start[ind] = int_file_start[file_ind];
        else
            volume_start[ind] = 0;
    }

    if( volume->is_cached_volume )
    {
        /*--- must make a temporary hyperslab array to contain the volume */

        for_less( dim, 0, get_volume_n_dimensions(volume) )
            volume_sizes[dim] = 1;

        for_less( dim, 0, MAX_DIMENSIONS )
            array_to_volume[dim] = 0;

        for_less( dim, get_volume_n_dimensions(volume), MAX_DIMENSIONS )
        {
            volume_start[dim] = 0;
            volume_sizes[dim] = 1;
        }

        n_slab_dims = 0;
        for_less( file_ind, 0, file->n_file_dimensions )
        {
            ind = to_volume[file_ind];
            if( ind != INVALID_AXIS )
            {
                to_array[file_ind] = n_slab_dims;
                array_to_volume[n_slab_dims] = ind;
                slab_sizes[n_slab_dims] = int_file_count[file_ind];
                volume_sizes[ind] = int_file_count[file_ind];
                ++n_slab_dims;
            }
            else
            {
                to_array[file_ind] = INVALID_AXIS;
            }
        }

        create_multidim_array( &array, n_slab_dims, slab_sizes,
                               get_volume_data_type(volume) );

        /*--- copy from the cached volume to the temporary array */

        size0 = volume_sizes[0];
        size1 = volume_sizes[1];
        size2 = volume_sizes[2];
        size3 = volume_sizes[3];
        size4 = volume_sizes[4];

        for_less( v[0], 0, size0 )
        for_less( v[1], 0, size1 )
        for_less( v[2], 0, size2 )
        for_less( v[3], 0, size3 )
        for_less( v[4], 0, size4 )
        {
            value = get_volume_voxel_value( volume,
                                            volume_start[0] + v[0],
                                            volume_start[1] + v[1],
                                            volume_start[2] + v[2],
                                            volume_start[3] + v[3],
                                            volume_start[4] + v[4] );

            SET_MULTIDIM( array, v[array_to_volume[0]],
                                 v[array_to_volume[1]],
                                 v[array_to_volume[2]],
                                 v[array_to_volume[3]],
                                 v[array_to_volume[4]], value );
        }

        /*--- output the temporary array */

        GET_MULTIDIM_PTR( array_data_ptr, array, 0, 0, 0, 0, 0 );
        (void) output_minc_hyperslab( file, get_volume_data_type(volume),
                                      n_slab_dims, slab_sizes, array_data_ptr,
                                      to_array, int_file_start, int_file_count);
        delete_multidim_array( &array );
    }
    else
    {
        GET_MULTIDIM_PTR( array_data_ptr, volume->array,
                          volume_start[0], volume_start[1], volume_start[2],
                          volume_start[3], volume_start[4] );
        get_volume_sizes( volume, volume_sizes );

        (void) output_minc_hyperslab( file, get_volume_data_type(volume),
                                      get_volume_n_dimensions(volume),
                                      volume_sizes, array_data_ptr,
                                      to_volume,
                                      int_file_start, int_file_count );
    }
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : output_the_volume
@INPUT      : file
              volume
              volume_count
              file_start
@OUTPUT     : 
@RETURNS    : OK or ERROR
@DESCRIPTION: Outputs the volume to the file in the given position.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

static  Status  output_the_volume(
    Minc_file   file,
    Volume      volume,
    int         volume_count[],
    long        file_start[] )
{
    Status            status;
    int               d, n_volume_dims, sizes[MAX_DIMENSIONS];
    int               slab_size, n_slab, this_count;
    int               vol_index, step, n_steps, n_range_dims;
    int               to_volume_index[MAX_VAR_DIMS];
    int               to_file_index[MAX_DIMENSIONS];
    long              file_indices[MAX_VAR_DIMS];
    long              count[MAX_VAR_DIMS];
    Real              real_min, real_max;
    STRING            *vol_dimension_names;
    BOOLEAN           increment;
    progress_struct   progress;

    status = check_minc_output_variables( file );

    if( status != OK )
        return( status );

    /* --- check if dimension name correspondence between volume and file */

    n_volume_dims = get_volume_n_dimensions( volume );

    if( n_volume_dims > file->n_file_dimensions )
    {
        print_error( "output_volume_to_minc_file_position:" );
        print_error( " volume (%d) has more dimensions than file (%d).\n",
                     n_volume_dims, file->n_file_dimensions );
        return( ERROR );
    }

    /*--- find correspondence between volume dimensions and file dimensions */

    vol_dimension_names = get_volume_dimension_names( volume );

    status = get_dimension_ordering( n_volume_dims, vol_dimension_names,
                                     file->n_file_dimensions, file->dim_names,
                                     to_volume_index, to_file_index );

    delete_dimension_names( volume, vol_dimension_names );

    if( status != OK )
        return( ERROR );

    /*--- check sizes match between volume and file */

    get_volume_sizes( volume, sizes );

    for_less( d, 0, file->n_file_dimensions )
    {
        vol_index = to_volume_index[d];

        if( vol_index >= 0 )
        {
            if( volume_count[vol_index] < 0 ||
                volume_count[vol_index] > sizes[vol_index] )
            {
                print_error( "output_the_volume: invalid volume count.\n" );
                print_error( "    count[%d] = %d\n",
                       vol_index, volume_count[vol_index] );
                return( ERROR );
            }

            this_count = volume_count[vol_index];
        }
        else
        {
            this_count = 1;
        }

        if( file_start[d] < 0 || file_start[d] + (long) this_count >
            file->sizes_in_file[d] )
        {
            print_error( "output_the_volume:  invalid minc file position.\n" );
            print_error( "    start[%d] = %d     count[%d] = %d\n", d, file_start[d],
                      d, this_count );
            return( ERROR );
        }
    }

    /*--- if per slice image ranges, output the ranges corresponding to this
          volume */

    if( file->image_range[0] >= file->image_range[1] )
    {
        long     n_ranges, range_start[MAX_VAR_DIMS], range_count[MAX_VAR_DIMS];
        long     r;
        double   *image_range;

        n_range_dims = file->n_file_dimensions - 2;
        if( equal_strings( file->dim_names[file->n_file_dimensions-1],
                           MIvector_dimension ) )
            --n_range_dims;

        n_ranges = 1;
        for_less( d, 0, n_range_dims )
        {
            vol_index = to_volume_index[d];
            if( vol_index == INVALID_AXIS )
            {
                range_count[d] = 1;
                range_start[d] = file_start[d];
            }
            else
            {
                n_ranges *= (long) volume_count[vol_index];
                range_count[d] = (long) volume_count[vol_index];
                range_start[d] = 0;
            }
        }

        get_volume_real_range( volume, &real_min, &real_max );

        ALLOC( image_range, n_ranges );

        for_less( r, 0, n_ranges )
            image_range[r] = real_min;

        (void) mivarput( file->cdfid, file->min_id,
                         range_start, range_count,
                         NC_DOUBLE, MI_UNSIGNED, (void *) image_range );

        for_less( r, 0, n_ranges )
            image_range[r] = real_max;

        (void) mivarput( file->cdfid, file->max_id,
                         range_start, range_count,
                         NC_DOUBLE, MI_UNSIGNED, (void *) image_range );

        FREE( image_range );
    }

    /* --- determine which contiguous blocks of volume to output
       to max out the read/write buffer and make it like the
       chunking dimensions for compression (for efficiency) */

    file->n_slab_dims = 0;
    slab_size = 1;
    n_steps = 1;
    int unit_size = get_type_size( get_volume_data_type(volume) );

    for( d = file->n_file_dimensions-1; d >= 0; d-- ) {
      count[d] = 1;
      file_indices[d] = file_start[d];
      if( to_volume_index[d] != INVALID_AXIS ) {
        if( MI_MAX_VAR_BUFFER_SIZE > volume_count[to_volume_index[d]] * slab_size * unit_size 
            && n_steps == 1 ) {
          count[d] = volume_count[to_volume_index[d]];
          file->n_slab_dims++;  /* integral number of complete dimensions */
        } else {
          count[d] = MIN( volume_count[to_volume_index[d]], 
                          (size_t)( MI_MAX_VAR_BUFFER_SIZE / ( slab_size * unit_size ) ) );
          n_steps *= (int)( ( volume_count[to_volume_index[d]] + count[d] - 1 ) / count[d] );
        }
        slab_size *= count[d];
      }
    }

    if( file->n_slab_dims == 0 ) {
      fprintf( stderr, "Error: You seem to be processing a minc file\n" );
      fprintf( stderr, "with no valid axes. Please check your volume.\n" );
      exit(1);
    }

    /*--- now write entire volume in contiguous chunks (possibly only 1 req'd)*/

    step = 0;

    initialize_progress_report( &progress, FALSE, n_steps,"Outputting Volume" );

    increment = FALSE;
    while( !increment ) {

        /*--- set the indices of the file slab to write */

        long local_count[MAX_VAR_DIMS];
        for( d = 0; d < file->n_file_dimensions; d++ ) {
          vol_index = to_volume_index[d];
          local_count[d] = MIN( volume_count[vol_index] - file_indices[d], count[d] );
        }

        output_slab( file, volume, to_volume_index, file_indices, local_count );

        /*--- increment the file index dimensions which correspond
              for the next slab to write to output */

        increment = TRUE;

        d = file->n_file_dimensions-1;
        n_slab = 0;
        while( increment && d >= 0 ) {
            vol_index = to_volume_index[d];

            if( vol_index != INVALID_AXIS && n_slab >= file->n_slab_dims ) {
                file_indices[d] += local_count[d];
                if( file_indices[d] < file_start[d] + (long) volume_count[vol_index] ) {
                    increment = FALSE;
                } else {
                    file_indices[d] = file_start[d];
                }
            }

            if( vol_index != INVALID_AXIS )
                ++n_slab;

            --d;
        }

        ++step;

        if( n_steps > 1 )
            update_progress_report( &progress, step );
    }

    terminate_progress_report( &progress );

    if( step != n_steps ) {
      fprintf( stderr, "Error: Your output minc file may be incomplete\n" );
      fprintf( stderr, "(wrote only %d out of %d buffers)\n", step, n_steps );
      exit(1);
    }

    return( OK );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : output_volume_to_minc_file_position
@INPUT      : file
              volume
              volume_count
              file_start
@OUTPUT     : Outputs the volume to the specified file position.
@RETURNS    : 
@DESCRIPTION: 
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  Status  output_volume_to_minc_file_position(
    Minc_file   file,
    Volume      volume,
    int         volume_count[],
    long        file_start[] )
{
    if( file->ignoring_because_cached )
        return( OK );

    file->outputting_in_order = FALSE;

    return( output_the_volume( file, volume, volume_count, file_start ) );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : output_minc_volume
@INPUT      : file
@OUTPUT     : 
@RETURNS    : OK or ERROR
@DESCRIPTION: Outputs the attached volume to the MINC file.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  Status  output_minc_volume(
    Minc_file   file )
{
    int        d, volume_count[MAX_DIMENSIONS];
    BOOLEAN    increment;

    if( file->ignoring_because_cached )
        return( OK );

    /*--- check number of volumes written */

    d = 0;
    while( d < file->n_file_dimensions &&
           file->to_volume_index[d] != INVALID_AXIS )
        ++d;

    if( d < file->n_file_dimensions &&
        file->indices[d] >= file->sizes_in_file[d] )
    {
        print_error(
             "output_minc_volume: attempted to write too many subvolumes.\n");
        return( ERROR );
    }

    get_volume_sizes( file->volume, volume_count );

    if( output_the_volume( file, file->volume, volume_count,
                           file->indices ) != OK )
        return( ERROR );

    /*--- increment the file index dimensions which do not
          correspond to volume dimensions */

    increment = TRUE;

    d = file->n_file_dimensions-1;
    while( increment && d >= 0 )
    {
        if( file->to_volume_index[d] == INVALID_AXIS )
        {
            ++file->indices[d];

            if( file->indices[d] < file->sizes_in_file[d] )
                increment = FALSE;
            else
                file->indices[d] = 0;
        }

        --d;
    }

    if( increment )
        file->entire_file_written = TRUE;

    return( OK );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : close_minc_output
@INPUT      : file
@OUTPUT     : 
@RETURNS    : OK or ERROR
@DESCRIPTION: Closes the MINC file.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  Status  close_minc_output(
    Minc_file   file )
{
    int    d;

    if( file == (Minc_file) NULL )
    {
        print_error( "close_minc_output(): NULL file.\n" );
        return( ERROR );
    }

    if( !file->ignoring_because_cached )
    {
        if( file->outputting_in_order && !file->entire_file_written )
        {
            print_error( "Warning:  the MINC file has been " );
            print_error( "closed without writing part of it.\n");
        }

        (void) miattputstr( file->cdfid, file->img_var_id, MIcomplete, MI_TRUE);

        (void) miclose( file->cdfid );
        (void) miicv_free( file->minc_icv );

        for_less( d, 0, file->n_file_dimensions )
            delete_string( file->dim_names[d] );
    }

    delete_string( file->filename );

    FREE( file );

    return( OK );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : set_default_minc_output_options
@INPUT      : 
@OUTPUT     : options
@RETURNS    : 
@DESCRIPTION: Sets the minc output options to the default.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : May  22, 1997   D. MacDonald - added use_volume_starts_and_steps
---------------------------------------------------------------------------- */

VIOAPI  void  set_default_minc_output_options(
    minc_output_options  *options           )
{
    int   dim;

    for_less( dim, 0, MAX_DIMENSIONS )
        options->dimension_names[dim] = NULL;

    options->global_image_range[0] = 0.0;
    options->global_image_range[1] = -1.0;

    options->use_volume_starts_and_steps = FALSE;
    options->use_starts_set = FALSE;
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : copy_minc_output_options
@INPUT      : src
@OUTPUT     : dest
@RETURNS    : 
@DESCRIPTION: Copies the minc output options to a new structure.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : Nov. 12, 1995    David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  void  copy_minc_output_options(
    minc_output_options  *src,
    minc_output_options  *dest )
{
    int   dim;

    if( src == NULL )
        set_default_minc_output_options( dest );
    else
    {
        *dest = *src;

        for_less( dim, 0, MAX_DIMENSIONS )
        {
            if( src->dimension_names[dim] != NULL )
                dest->dimension_names[dim] = create_string(
                                                  src->dimension_names[dim] );
        }
    }
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : delete_minc_output_options
@INPUT      : options
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Deletes the minc output options.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  void  delete_minc_output_options(
    minc_output_options  *options           )
{
    int   i;

    for_less( i, 0, MAX_DIMENSIONS )
        delete_string( options->dimension_names[i] );
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : set_minc_output_dimensions_order
@INPUT      : n_dimensions
              dimension_names
@OUTPUT     : options
@RETURNS    : 
@DESCRIPTION: Sets the dimension ordering of the minc output options.
              This option is used by output_volume, but not by
              initialize_minc_output.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  void  set_minc_output_dimensions_order(
    minc_output_options  *options,
    int                  n_dimensions,
    STRING               dimension_names[] )
{
    int   i;

    for_less( i, 0, n_dimensions )
    {
        replace_string( &options->dimension_names[i],
                        create_string(dimension_names[i]) );
    }
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : set_minc_output_real_range
@INPUT      : real_min
              real_max
@OUTPUT     : options
@RETURNS    : 
@DESCRIPTION: Sets the global real range of the entire file, unless real_min
              >= real_max.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1993            David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  void  set_minc_output_real_range(
    minc_output_options  *options,
    Real                 real_min,
    Real                 real_max )
{
    options->global_image_range[0] = real_min;
    options->global_image_range[1] = real_max;
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : set_minc_output_use_volume_starts_and_steps_flag
@INPUT      : options
              flag
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Tells MINC output to use the exact starts and steps stored in
              the volume, not the voxel-to-world-transform.  This avoids
              round-off errors in converting to transform on input, then
              from transform on output.
@METHOD     : 
@GLOBALS    : 
@CALLS      :  
@CREATED    : May. 22, 1997    David MacDonald
@MODIFIED   : 
---------------------------------------------------------------------------- */

VIOAPI  void  set_minc_output_use_volume_starts_and_steps_flag(
    minc_output_options  *options,
    BOOLEAN              flag )
{
    options->use_volume_starts_and_steps = flag;
    options->use_starts_set = TRUE;
}