跳转到内容

MINC/软件开发/MINC1-volumeio-程序员参考

来自维基教科书,开放的书籍,开放的世界

本文档描述了蒙特利尔神经病学研究所麦康奈尔大脑成像中心 (BIC) 可用的例程库。它是由 David MacDonald 作为医学影像软件测试平台的一部分开发的,源代码和相当多的输入来自该中心的 Peter Neelin、Louis Collins 和其他人。

该库,被称为 BIC 体积 IO 库,包含一组用于读取和写入医学影像数据的体积,以及对许多编程任务有用的通用支持例程。大脑成像中心的图像以一种称为 *MINC* 的格式存储在磁盘上,它代表 *医学图像网络 CDF*。有关此格式和用于读取和写入 MINC 文件的 MINC 例程库的更多信息,请参见相关文档。

BIC 体积 IO 库建立在 MINC 库之上,为大多数一般操作提供对 MINC 文件的轻松访问,而无需学习太多 MINC 库的细节,MINC 库是一个用于处理大多数可想而知的情况的综合系统。BIC 体积 IO 库提供了用于内部存储体积的结构和例程,用于访问和修改体积。此外,它还提供了例程来操作标签点和变换,并在大脑成像中心的标准格式下对这些对象执行输入和输出。

本文档描述了 BIC 体积 IO 库的查找位置、提供的功能以及如何将其集成到用户的程序中。该库是用 C 源代码编写的,旨在与 C 源代码链接。它调用了另外两个库:MINC(BIC 文件格式库)和 netcdf(一个可移植文件格式管理器)。

编译和链接

[编辑 | 编辑源代码]

用于链接的库是 libvolume_io.a,它位于 /usr/local/lib 中,相关的包含文件位于 /usr/local/include 下的 volume_io 目录中。这两个目录通常都在编译器搜索路径中。调用体积 IO 函数的源文件必须在顶部包含以下行,以包含相关的类型定义和原型

  #include  <volume_io/volume_io.h>

为了与 BIC 体积 IO 库链接,必须指定相关的库

  cc test_volume_io.o  -o test_volume_io \
             -lvolume_io -lminc -lnetcdf -lm -lmalloc

这两个库 mincnetcdf 通常位于 /usr/local/lib 中,该目录自动在搜索路径中。-lm 选项包含数学库,该库有时由 BIC 体积 IO 库调用。-lmalloc 库提供的内存分配比默认的更快、更健壮。

为了使用此库,必须习惯某些风格上的细微差别,其中一些是特定于此软件作者的,而另一些则是随着软件历史的演变而演变的。因此,请务必注意以下与风格相关的注意事项,恕不道歉。

全局变量

[编辑 | 编辑源代码]

已经采用了全局变量通常很糟糕的理念。在许多情况下,在函数中使用全局变量会隐藏函数的行为,因此,最好在参数列表中指定所有与函数相关的 信息,而不是依赖任何全局变量。其次,给定函数修改全局变量会阻止在多线程环境中使用此函数(两个进程可能同时尝试修改全局变量)。由于采用了反全局变量的理念,BIC 体积 IO 库的用户通常必须为函数指定许多参数。这似乎是一个很小的代价,作为回报,我们能够在参数列表中看到控制每个函数行为的所有因素。

标识符名称

[编辑 | 编辑源代码]

此库的实现者喜欢输入大量文本,并且通常不喜欢缩写。因此,BIC 体积 IO 库中出现的函数和变量名称可能非常冗长,几乎到了完全语法正确的句子的程度。通常,您永远不会看到名称为 rem_garb() 的函数。相反,函数更可能具有诸如 remove_garbage() 的名称,并且在许多情况下,甚至 take_garbage_to_curb()。因此,BIC 体积 IO 库的用户将不得不键入比平时更长的函数和变量名称,但希望能够更清楚地了解相关的功能。

结构体参数

[编辑 | 编辑源代码]

在整个库中定义了许多对象,作为不同大小的结构体。由于按值传递结构体(将整个结构体复制到函数中)的效率低下,结构体通常通过引用传递给函数。因此,总是有可能将无效指针传递给期望指向结构体的指针的函数。以下示例说明了使用此类函数的正确和错误方法。给定一个结构体和一个初始化它的库函数,

使用此函数的正确方法

  typedef  struct
  {
      int   a, b, c, d;
  } big_struct;
  
  void    initialize( struct  big_struct   *s )
  {
      s->a = 1;   s->b = 2;   s->c = 3;    s->d = 4;
  }

使用此函数的错误方法

  int  main()
  {
      big_struct   *s;
  
      initialize( s );   /* WRONG */
  }

因为变量 s 是一个未初始化的指针。正确的方法是定义一个结构体变量,而不是指向结构体的指针,并将指向结构体的指针传递过去

  int  main()
  {
      big_struct   s;
  
      initialize( &s );
  }

或者,可以通过在调用 initialize 之前分配 s 指针来更正上面的错误示例。

类型和宏

[编辑 | 编辑源代码]

为与 BIC 体积 IO 库一起使用定义了几种类型和宏。库中的所有函数声明都以 publicprivate 开头,这指示函数是否可以从其所在的外部文件访问。库用户只对以 public 开头的函数感兴趣。它们定义如下

  #define  public
          #define  private   static

定义了用于逻辑值的类型

  typedef  int    BOOLEAN
          #define  FALSE  0
          #define  TRUE   1
          #define  OFF    FALSE
          #define  ON     TRUE

其他定义的有用类型包括

  typedef  double             Real;
          typedef  enum 
               { OK, ERROR, INTERNAL_ERROR, END_OF_FILE, QUIT }
                                      Status;
          typedef  char               Smallest_int;

一些对一般编程有用的宏包括

  N_DIMENSIONS

等于 3 的常量,代表现实世界中的维度数量。

  X

等于 0 的常量,用作各种 XYZ 结构体中的索引。

  Y

等于 1 的常量,用作各种 XYZ 结构体中的索引。

  Z

等于 2 的常量,用作各种 XYZ 结构体中的索引。

  SIZEOF_STATIC_ARRAY( array )

返回静态分配数组中的元素数量,例如,

  {
              int  size;
              int  array[] = { 1, 2, 3 };
  
              size = SIZEOF_STATIC_ARRAY( array );     /* == 3 */
          }
  ROUND( x )

返回最接近于 x 的整数。如果在两个整数之间,则返回较大的一个。对于负数、零和正数,都能正确运行。

  IS_INT( x )

如果实数参数恰好是整数,则返回 TRUE

  FRACTION( x )

返回参数的小数部分。

  FLOOR( x )

返回小于或等于参数的最大整数。

  CEILING( x )

返回大于或等于参数的最小整数。

  ABS( x )

返回整数或实数的绝对值。

  MAX( x, y )

返回两个整数或实数中的最大值。

  MAX3( x, y, z )

返回三个整数或实数中的最大值。

  MIN( x, y )

返回两个整数或实数中的最小值。

  MIN3( x, y, z )

返回三个整数或实数中的最小值。

  INTERPOLATE( alpha, a, b )

返回 ab 之间的插值,其中 alpha 的范围为零到一。

  PI

返回 Π 的值。

  DEG_TO_RAD

返回每度弧度数,用于将角度(以度为单位)乘以该值以得到弧度。

  RAD_TO_DEG

返回每弧度度数,用于将角度(以弧度为单位)乘以该值以得到度数。

  IJ( i, j, nj )

ninj 列二维数组的索引 [i, j] 转换为单个索引,基于行优先顺序。

  IJK( i, j, k, nj, nk )

ninjnk 层三维数组的索引 [i, j, k] 转换为单个索引,基于行优先顺序。

  for_less( i, start, end )

执行一个 for 循环,其中 istart 开始,递增直到大于或等于 end

等效于 for( i = start; i < end; ++i )

  for_inclusive( i, start, end )

执行一个 for 循环,其中 istart 开始,递增直到大于 end

等效于 for( i = start; i <= end; ++i )

  GLUE(x,y)

特殊的 C 源代码宏,用于将两个不同的标识符粘合在一起,例如 GLUE(colour,_name) 会得到 colour_name

  GLUE3(x,y,z)

特殊的 C 源代码宏,用于将三个不同的标识符粘合在一起,例如 GLUE(a,b,c) 会得到 abc

编程工具

[edit | edit source]

提供了一组函数,用于通用、可移植的编程。这些函数涵盖了字符串、时间、文件 I/O 等基本领域。BIC Volume IO 库的许多部分都引用了编程工具,建议 BIC Volume IO 库的用户尽可能使用这些函数。以下是所有编程工具的列表,按相关领域分组,并按字母顺序排列。

字符串

[edit | edit source]

提供了一些简单的字符串操作技术。字符串是任意长的以 NULL 结尾的字符数组,根据需要进行分配和删除。类型 STRING 定义为

  typedef  char   *STRING;

基本的字符串创建和删除例程是

  public  STRING  alloc_string(
      int   length )
  public  STRING  create_string(
      STRING    initial )

函数 alloc_string 为指定长度的字符串分配存储空间(通过分配 length+1 字节),但不为字符串分配任何值。函数 create_string 创建一个已分配的字符串,其值为 initial。如果 initial 是一个 NULL 指针,则创建一个空字符串(``)。

  public  void  delete_string(
      STRING   string )

可以使用此函数删除与字符串关联的存储空间。

  public  int  string_length(
      STRING   string )

返回字符串的长度。

  public  BOOLEAN  equal_strings(
      STRING   str1,
      STRING   str2 )

如果两个字符串完全相等,则返回 TRUE

  public  void  replace_string(
      STRING   *string,
      STRING   new_string )

一个方便的函数,它删除 string 参数,并将其重新分配给 new_string。它不会复制 new_string 的值,而只是将 string 设置为指针 new_string

  public  void  concat_char_to_string(
      STRING   *string,
      char     ch )

将一个字符连接到字符串的末尾。

  public  STRING  concat_strings(
      STRING   str1,
      STRING   str2 )

创建一个字符串,它是两个参数的连接,并返回该字符串,而不会改变字符串参数。

  public  void  concat_to_string(
      STRING   *string,
      STRING   str2 )

用参数 stringstr2 的连接替换参数 string

  public  BOOLEAN  is_lower_case(
      char  ch )

如果字符是小写字母,则返回 TRUE

  public  BOOLEAN  is_upper_case(
      char  ch )

如果字符是大写字母,则返回 TRUE

  public  char  get_lower_case(
      char   ch )

如果字符是大写字母,则返回字符的小写版本,否则返回字符本身。

  public  char  get_upper_case(
      char   ch )

如果字符是小写字母,则返回字符的大写版本,否则返回字符本身。

  public  BOOLEAN  string_ends_in(
      STRING   string,
      STRING   ending )

确定字符串是否以指定的 ending 结尾。例如,传递参数 "rainfall""fall" 会返回 TRUE

  public    STRING   strip_outer_blanks(
      STRING  str )

返回一个字符串,它是参数 str,去掉了所有前导和尾随空格。

  public  void  make_string_upper_case(
      STRING    string )

将字符串转换为全大写字符串。在原地修改字符串。

  public  int  find_character(
      STRING  string,
      char    ch )

在给定的 string 中搜索给定的字符,返回找到它的索引,如果未找到,则返回 -1。

  public  BOOLEAN  blank_string(
      STRING   string )

如果字符串为空或仅包含空格、制表符和换行符,则返回 true。

通用文件 I/O

[edit | edit source]

虽然可以使用标准的 UNIX 文件接口(例如 fprintf),但 BIC Volume IO 库包含一组例程,用于执行所有文件操作,并且有可能移植到其他操作系统。还有一些其他的优点,包括自动扩展 <function>~ </function> 和环境变量。此外,还提供了压缩文件的自动解压缩功能。

  public  STRING  expand_filename(
      STRING  filename )

在参数 filename 中搜索某些模式,并相应地扩展它们,返回扩展后的文件名。任何以 ~ 开始,但不包括斜杠 / 的字符序列将被更改为指定的用户的 home 目录。任何美元符号 $ 的出现都会导致后面的文本(直到下一个斜杠)被扩展为由文本指定的 environment 变量。可以通过在任何 ~ $ 之前放置一个反斜杠 \ 来避免这种扩展。在所有以下使用文件名的函数中,都会自动执行文件名扩展。

  public  BOOLEAN  file_exists(
      STRING        filename )

如果文件存在,则返回 TRUE

  public  BOOLEAN  check_clobber_file(
      STRING   filename )
  public  BOOLEAN  check_clobber_file_default_suffix(
      STRING   filename,
      STRING   default_suffix )

检查文件是否存在,如果存在,则提示用户是否可以覆盖它。如果文件不存在,或如果文件存在,用户对提示输入 `y',则返回 TRUE。该函数的第二种形式在文件名后面追加一个默认后缀,如果文件名还没有后缀的话。

  public  void  remove_file(
      STRING  filename )

删除指定的文件,这将导致该文件在所有对它的引用关闭后才会被删除。

  public  BOOLEAN  filename_extension_matches(
      STRING   filename,
      STRING   extension )

如果文件以给定的扩展名结尾(前面有一个点),则返回 TRUE。例如,<function>filename_extension_matches( "volume.mnc" , "mnc" )</function> 返回 TRUE。正确处理压缩文件,例如,传递参数 "volume.mnc.Z""mnc" 返回 TRUE

  public  STRING  remove_directories_from_filename(
      STRING  filename )

从文件名中剥离目录,返回结果。

  public  STRING  extract_directory(
      STRING    filename )

从文件名中提取目录,返回结果。

  public  STRING  get_absolute_filename(
      STRING    filename,
      STRING    directory )

给定一个文件名和当前目录,返回一个绝对文件名(以斜杠 / 开头的文件名)。

  public  Status  open_file(
      STRING             filename,
      IO_types           io_type,
      File_formats       file_format,
      FILE               **file )
  public  Status  open_file_with_default_suffix(
      STRING             filename,
      STRING             default_suffix,
      IO_types           io_type,
      File_formats       file_format,
      FILE               **file )

函数 open_file() 打开指定的文件,其中 io_type 必须是 WRITE_FILEREAD_FILE 之一,file_format 必须是 ASCII_FORMATBINARY_FORMAT 之一。如果成功,则将文件指针传递到最后一个参数中,并返回状态 OK。否则,将返回一个空指针,并返回状态 ERROR。自动执行文件名扩展。第二个函数执行与 open_file 相同的任务,此外还自动添加指定的后缀扩展名,如果需要的话。在输入时,如果指定的文件不存在,并且文件没有扩展名,则查找带有默认扩展名的指定文件。

  public  Status  close_file(
      FILE     *file )

关闭文件,如果成功则返回 OK

  public  Status  set_file_position(
      FILE     *file,
      long     byte_position )

将文件指针定位到文件中的指定位置,其中 0 对应于文件开头,如果成功则返回 OK

  public  Status  flush_file(
      FILE     *file )

刷新文件缓冲区中的任何待定输出,如果成功则返回 OK

  public  Status  input_character(
      FILE  *file,
      char   *ch )

从文件中输入一个字符,并将其作为第二个参数传递回来。如果字符是文件结尾字符,则返回状态 ERROR,否则返回 OK

  public  Status  unget_character(
      FILE  *file,
      char  ch )

将字符放回输入队列。

  public  Status  input_nonwhite_character(
      FILE   *file,
      char   *ch )

输入下一个非空白字符,即下一个不是空格、制表符或换行符的字符。

  public  Status   skip_input_until(
      FILE   *file,
      char   search_char )

从文件中输入字符,直到找到指定的搜索字符。

  public  Status  input_string(
      FILE    *file,
      STRING  *str,
      char    termination_char )

从文件中输入一个字符串。从文件中读取字符串,直到遇到终止字符或换行符。如果字符串以换行符结尾,并且终止字符不是换行符,则从文件中获取的下一个字符将是换行符。否则,从文件中获取的下一个字符将是终止字符之后的字符。

  public  Status  input_quoted_string(
      FILE            *file,
      STRING          *str )

从文件中输入一个字符串,以引号分隔,返回 OKERROR。成功读取一个字符串后,从文件中获取的下一个字符将是结束引号之后的字符。

  public  Status  input_possibly_quoted_string(
      FILE            *file,
      STRING          *str )

从文件中输入一个字符串,以引号或空白符分隔,返回 OKERROR。成功读取一个字符串后,从文件中获取的下一个字符将是结束引号或最后一个非空白字符之后的字符。

  public  Status  input_line(
      FILE    *file,
      STRING  line )

将文件中的字符输入到参数 line 中,直到遇到下一个换行符。如果找到了换行符,则从文件中获取的下一个字符将是换行符之后的字符。换行符不会存储在字符串中。

  public  Status  input_boolean(
      FILE            *file,
      BOOLEAN         *b )

输入下一个非空白字符。如果它是 ``f ``F,则传递值 FALSE 回来。如果它是 ``t 或 ``T,则传递值 TRUE 回来。否则返回 ERROR

  public  Status  input_short(
      FILE            *file,
      short           *s )
  public  Status  input_unsigned_short(
      FILE            *file,
      unsigned short  *s )
  public  Status  input_int(
      FILE            *file,
      int             *i )
  public  Status  input_real(
      FILE            *file,
      Real            *r )
  public  Status  input_float(
      FILE            *file,
      float           *f )
  public  Status  input_double(
      FILE            *file,
      double          *d )

从 ascii 文件中输入指定类型的值。

  public  Status  input_newline(
      FILE            *file )

从文件读取字符并丢弃字符,直到并包括下一个换行符。如果找到换行符,则返回 OK;如果先到达文件末尾,则返回 ERROR

  public  Status  input_binary_data(
      FILE            *file,
      void            *data,
      size_t          element_size,
      int             n )

从文件读取二进制格式的数据数组。该数组包含 n 个元素,每个元素的大小为 element_size。返回 OKERROR

  public  Status  output_character(
      FILE   *file,
      char   ch )

将一个字符输出到文件,返回 OKERROR

  public  Status  output_string(
      FILE    *file,
      STRING  str )

将指定字符串输出到文件,返回 OKERROR

  public  Status  output_quoted_string(
      FILE            *file,
      STRING          str )

输出一个引号,指定字符串,以及一个结束引号。

  public  Status  output_newline(
      FILE            *file )

输出一个换行符,返回 OKERROR

  public  Status  output_boolean(
      FILE            *file,
      BOOLEAN         b )

如果参数为 TRUE,则输出一个空格和字母“T”;否则,输出一个空格和字母“F”。

  public  Status  output_short(
      FILE            *file,
      short           s )
  public  Status  output_unsigned_short(
      FILE            *file,
      unsigned short  s )
  public  Status  output_int(
      FILE            *file,
      int             i )
  public  Status  output_real(
      FILE            *file,
      Real            r )
  public  Status  output_float(
      FILE            *file,
      float           f )
  public  Status  output_double(
      FILE            *file,
      double          d )

输出一个空格,然后将指定值输出到 ASCII 文件。

  public  Status  output_binary_data(
      FILE            *file,
      void            *data,
      size_t          element_size,
      int             n )

将数据数组输出到文件,以二进制格式。该数组包含 n 个元素,每个元素的大小为 element_size。返回 OKERROR

  public  Status  io_binary_data(
      FILE            *file,
      IO_types        io_flag,
      void            *data,
      size_t          element_size,
      int             n )

根据参数 io_flagREAD_FILEWRITE_FILE,输入或输出指定二进制数据。

  public  Status  io_newline(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format )

根据参数 io_flagREAD_FILEWRITE_FILE,输入或输出 ASCII 换行符。如果 format 参数为 BINARY_FORMAT,则该函数什么也不做。

  public  Status  io_quoted_string(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      STRING          str )

如果 format 参数为 ASCII_FORMAT,则根据参数 io_flagREAD_FILEWRITE_FILE,输入或输出以引号分隔的字符串。如果 format 参数为 BINARY_FORMAT,则输入或输出以整数开头表示字符串长度的字符串。

  public  Status  io_boolean(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      BOOLEAN         *b )
  public  Status  io_short(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      short           *short_int )
  public  Status  io_unsigned_short(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      unsigned short  *unsigned_short )
  public  Status  io_unsigned_char(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      unsigned  char  *c )
  public  Status  io_int(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      int             *i )
  public  Status  io_real(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      Real            *r )
  public  Status  io_float(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      float           *f )
  public  Status  io_double(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      double          *d )

输入或输出指定类型的二进制或 ASCII 值。

  public  Status  io_ints(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      int             n,
      int             *ints[] )

输入或输出二进制或 ASCII 格式的整数数组。

  public  Status  io_unsigned_chars(
      FILE            *file,
      IO_types        io_flag,
      File_formats    format,
      int             n,
      unsigned char   *unsigned_chars[] )

输入或输出二进制或 ASCII 格式的无符号字符数组。

内存分配

[编辑 | 编辑源代码]

提供了一组宏,允许轻松分配和释放内存,最多可分配 5 维数组。还执行内存分配检查,以捕获错误,例如释放未分配的内存。此外,内存分配会自动跟踪所有分配的内存,以便检测内存泄漏(孤立的内存)。

基本内存分配

[编辑 | 编辑源代码]

基本宏如下所示

  ALLOC( ptr, n_items )

分配 n_items 个正确类型元素,并将结果分配给参数 ptr

  FREE( ptr )

释放参数 ptr 指向的内存。

  REALLOC( ptr, n_items )

更改参数 ptr 指向的内存大小,使其为 n_items 个元素,在此过程中可能会更改 ptr 的值。

  ALLOC_VAR_SIZED_STRUCT( ptr, element_type, n_elements )

分配一个可变大小的结构,该结构必须具有特定形式。结构的最后一个元素必须是一个大小为 1 的数组,该数组将构成结构的可变大小部分。参数 element_type 必须是该最后一个元素的类型,参数 n_elements 是除了结构第一部分的内存之外,要为该数组分配的所需元素数量。一个使用示例是

  {
              struct {  int   a;
                        float b;
                        char  data[1];
                     }                    *ptr;
  
              ALLOC_VAR_SIZED_STRUCT( ptr, char, 10 );
  
              ptr->a = 1;
              ptr->b = 2.5;
              ptr->data[0] = 'a';
              ptr->data[9] = 'i';
          }
  ALLOC2D( ptr, n1, n2 )
      ALLOC3D( ptr, n1, n2, n3 )
      ALLOC4D( ptr, n1, n2, n3, n4 )
      ALLOC5D( ptr, n1, n2, n3, n4, n5 )

分配一个大小为 n1n2 等的 2 到 5 维数组,并将结果存储在指定的指针 ptr 中。在二维情况下,这仅通过两次内存分配来完成,一次分配 n1 乘以 n2 个元素用于存储,另一次分配 n1 个指向第一个内存区域的指针。一般来说,每个维度都需要一次内存分配。

  FREE2D( ptr )
      FREE3D( ptr )
      FREE4D( ptr )
      FREE5D( ptr )

释放与多维数组关联的内存。

  public  void  set_alloc_checking(
      BOOLEAN state )

启用或禁用分配检查。它通常在程序开始时被调用,以提供对分配的双重检查。它在每次调用之前的分配宏时都会进行额外的分配。如果未调用此函数,则可以通过将环境变量 DEBUG_ALLOC 设置为任何内容来打开分配检查。

分配检查检测三种类型的内存编程错误:释放未分配的指针,通过检查从系统 malloc 例程返回的内存块是否与现有内存块重叠,有时检测到系统内存是否已损坏,以及通过记录所有分配的内存并打印出当前分配的内存,可以检测内存泄漏。

  public  void  output_alloc_to_file(
      STRING   filename )

如果启用了内存检查,则可以使用此函数检测内存泄漏。在程序结束时,程序员应该释放所有已知的内存,然后调用此函数。任何剩余的已分配内存将被打印到文件中,指示分配内存的文件和行号。

高级数组分配

[编辑 | 编辑源代码]

除了前面描述的基本内存分配宏之外,还提供了一组用于处理动态更改大小的数组的有用宏

  SET_ARRAY_SIZE( array, previous_n_elems, new_n_elems,
                      chunk_size )

此宏通过指定先前分配给数组的元素数量(从零开始)来增加或减少数组的大小。chunk_size 参数定义分配的内存块的大小。例如,如果 chunk_size 为 100,则此宏仅在大小更改跨越到 100 的不同倍数时才会重新分配数组,从而避免每次调用时都进行内存分配。此内存分配粒度规范必须始终一致地指定;如果此宏使用给定变量和块大小被调用,则随后使用相同变量调用此宏必须指定相同的块大小。另请注意,作为 new_n_elems 传入的数字必须在下次调用此宏时作为 previous_n_elems 传入。

  ADD_ELEMENT_TO_ARRAY( array, n_elems,
                            elem_to_add, chunk_size )

将参数 elem_to_add 添加到数组的 n_elems 个索引处,然后递增 n_elems。参数 chunk_size 指定内存分配的粒度。

  DELETE_ELEMENT_FROM_ARRAY( array, n_elems, index_to_remove,
                                 chunk_size )

从数组中删除 index_to_remove 个元素,减少数组中的元素数量(n_elems)并减少内存分配,如果跨越 chunk_size 的倍数。同样,chunk_size 必须在涉及给定指针的前面三个宏的所有调用中指定为相同。

  ADD_ELEMENT_TO_ARRAY_WITH_SIZE( array, n_alloced, n_elems,
                                      elem_to_add, chunk_size )

向数组添加一个元素(elem_to_add),递增 n_elems。如果需要,内存将按 chunk_size 中指定的数量增加,并且 n_alloced 变量将按此数量递增。此用法与 ADD_ELEMENT_TO_ARRAY 的用法不同,因为元素数量(n_elems)可以任意减少,而不会导致内存被释放。

进度报告

[编辑 | 编辑源代码]

为了提供对特定处理任务进度的简单监控,可以使用一个进度报告模块。当任务正在进行时,进度报告会在行上打印点,表示任务完成的进度。如果任务将花费很长时间(超过 2 分钟),进度报告会定期打印当前完成的百分比和估计的剩余时间。一个使用示例,后面跟着函数描述,如下所示

  {
      int                 i, n_slices;
      progress_struct     progress;
  
      n_slices = 100;
  
      initialize_progress_report( &progress, FALSE,
                  n_slices, "Processing Slices" );
  
      for( i = 0;  i < n_slices;  ++i )
      {
          process_slice( i );
  
          update_progress_report( &progress, i + 1 );
      }
  
      terminate_progress_report( &progress );
  }
  public  void  initialize_progress_report(
      progress_struct   *progress,
      BOOLEAN           one_line_only,
      int               n_steps,
      STRING            title )

初始化进度报告结构,指定处理中将发生的步骤数量,以及要打印的进度报告标题。在进度报告期间,显示屏将自动从打印一行点的短作业模式切换到定期打印完成百分比和估计剩余时间的长作业模式。如果 one_line_only 标志为 TRUE,则此功能将被禁用,并且只会显示一行点。

  public  void  update_progress_report(
      progress_struct   *progress,
      int               current_step )

告诉进度报告模块已完成多少个步骤,并导致更新行上点的显示或估计剩余时间。

  public  void  terminate_progress_report(
      progress_struct   *progress )

终止进度报告。

文本输出

[编辑 | 编辑源代码]

与使用标准 UNIX 函数 printf 进行例行文本输出相比,提供了一个外观相似的模块,该模块允许安装任意打印输出函数。例如,这可以用来将输出镜像到日志文件,或将文本发送到 X 窗口。主要变化是使用名为 printprint_error 的函数,该函数与标准 printf 函数具有完全相同的参数。

  public  void  print( STRING format, ... )
  public  void  print_error( STRING format, ... )

接受与 printf() 相同的参数,但允许为最终输出阶段安装用户输出函数。

  public  void  set_print_function(
      void     (*function) ( STRING ) )
  public  void  set_print_error_function(
      void     (*function) ( STRING )

设置输出函数,print()print_error 调用中的所有文本将被发送到该函数。默认情况下,没有打印函数,输出被发送到 printf()。

  public  void  push_print_function()
  public  void  push_print_error_function()

临时将打印或错误打印函数设置为标准输出,并允许用户设置另一个打印函数,该函数将在相应的 pop 函数被调用时消失。

  public  void  pop_print_function()
  public  void  pop_print_error_function()

恢复先前的用户打印或错误打印函数。

提供了一些与时间、日期和 CPU 时间相关的基本实用程序。

  public  Real  current_cpu_seconds( void )

返回当前进程到目前为止使用的 CPU 秒数。

  public  Real  current_realtime_seconds( void )

返回当前进程运行的秒数。

  public  STRING  get_clock_time()

返回表示当前时钟时间的字符串(小时和分钟),调用者负责删除该字符串。

  public  STRING  get_date()

返回表示当前日期的字符串,调用者负责删除该字符串。

  public  STRING  format_time(
      STRING   format,
      Real     seconds )

接受以秒为单位的时间和具有格式组件的格式字符串,例如“%g %s”,并将时间以毫秒、秒、分钟、天等适当单位打印到字符串中。字符串被返回,调用程序负责删除该字符串。

  public  void  print_time(
      STRING   format,
      Real     seconds )

format_time 相同,但使用结果调用 print()

  public  void  sleep_program(
      Real seconds )

将程序挂起指定的秒数。请注意,在大多数系统上,这只会执行到某个特定时间增量的最接近倍数。在 Silicon Graphics 系统上,这将是到最接近的百分之一秒。

在这个软件开发的实验室中,处理的任务涉及到多维数据体,例如由磁共振和PET扫描仪产生的数据。因此,提供了一套广泛的例程来表示体积,以及以MINC格式读取和写入体积。

体积的基本类型是Volume,它实际上是指向已分配结构的指针,该结构包含有关体积类型、维度数量、体素值等的所有信息。为了使用体积结构,必须首先创建体积,然后设置体积的大小,然后分配体素值的大数组。或者,可以通过调用相应的函数来读取MINC文件并创建体积,从而自动创建体积。

一个体积与一定数量的维度相关联,这些维度必须在1到5之间,但通常为3。体积被认为是任何一种类型的多维数组,包括所有大小的整数和实数类型。即使一个体积可能存储在例如1字节类型中,其值为0到255,但有一个实际值范围,它提供了从体素值到任何任意实数范围的映射。这样,实际范围可以是任何有效的实数区间,并且与特定存储类型无关。

由于大多数体积将通过从MINC文件读取而创建,因此将首先介绍这种方法,然后描述如何从头开始创建体积。最后,将介绍更高级应用程序的MINC输入和输出的较低级别。

体积输入

[编辑 | 编辑源代码]
  public  Status  input_volume(
      STRING               filename,
      int                  n_dimensions,
      STRING               dim_names[],
      nc_type              volume_nc_data_type,
      BOOLEAN              volume_signed_flag,
      Real                 volume_voxel_min,
      Real                 volume_voxel_max,
      BOOLEAN              create_volume_flag,
      Volume               *volume,
      minc_input_options   *options )

此例程从MINC文件读取一个体积,首先在create_volume_flag指定为TRUE(通常情况)的情况下创建体积。维度数量是体积所需的维度数量。如果此数量小于文件中的维度数量,则只读取与该维度数量相对应文件的第一个部分。如果指定的维度数量小于1,则使用文件中的维度数量。参数dim_names指定体积在体积变量中存储的顺序。对于存储体积中的每个维度,都有一个相应的名称,该名称是MIxspaceMIyspaceMIzspaceANY_SPATIAL_DIMENSION或空字符串之一。这些与文件中的对应维度相匹配,并且体积数组的维度排序在输入时重新排序。因此,如果用户希望以X-Z-Y顺序表示体积,则作为dim_names数组传递的值应为三个字符串MIxspaceMIzspaceMIyspace。此排序选择很重要,因为它定义了随后对体素索引的任何引用的顺序。

四个参数volume_nc_data_typevolume_voxel_max可用于指定体积变量中所需的存储类型,这些类型会从文件中的存储类型自动转换。volume_nc_data_typeMI_ORIGINAL_TYPENC_BYTENC_SHORTNC_LONGNC_FLOATNC_DOUBLE之一。对于整数类型,如果需要有符号类型,则volume_signed_flagTRUE,否则为FALSEvolume_voxel_minvolume_voxel_max指定有效体素值的范围,并且通常设置为相等以指示使用类型的完整范围,例如对于无符号NC_BYTE,范围为0到255.0。如果传递MI_ORIGINAL_TYPE,则使用文件中的类型、符号和体素范围。

如果create_volume_flagTRUE(通常情况),则会自动创建体积。否则,假定体积已经存在,并且只有在当前大小与文件导致的新大小不同时才会重新创建,从而减少读取多个文件时的内存分配量。

options参数指定输入过程的一些特殊选项,通常只传递一个NULL指针,表示应使用默认选项。目前,可能的选项是

  public  void  set_default_minc_input_options(
      minc_input_options  *options )

将默认选项填充到options结构中,该结构随后将传递给input_volume()

  public  void  set_minc_input_promote_invalid_to_min_flag(
      minc_input_options  *options,
      BOOLEAN             flag )

默认情况下,任何超出有效体素值范围的体素值都会提升到最小有效体素值。如果将此flag设置为FALSE,则会禁用此提升。

  public  void  set_minc_input_vector_to_scalar_flag(
      minc_input_options  *options,
      BOOLEAN             flag )

默认情况下,任何包含类型为向量的维度的体积(例如RGB颜色体积)都会通过对每个向量的分量进行平均来转换为标量体积。可以通过将flag传递为FALSE来禁用此功能。

  public  void  set_minc_input_vector_to_colour_flag(
      minc_input_options  *options,
      BOOLEAN             flag )
  public  void  set_minc_input_colour_dimension_size(
      minc_input_options  *options,
      int                 size )
  public  void  set_minc_input_colour_indices(
      minc_input_options  *options,
      int                 indices[4] )

默认情况下,任何包含类型为向量的维度的体积(例如RGB颜色体积)都会通过对每个向量的分量进行平均来转换为标量体积。如果将此选项设置为true,则任何具有set_minc_input_colour_dimension_size(默认值为3)所指定的组件数量的向量体积将使用set_minc_input_colour_indices(默认值为0、1、2)所指定的索引转换为颜色体积。

  public  void  set_minc_input_user_real_range(
      minc_input_options  *options,
      double              minimum,
      double              maximum )

默认情况下,读取到整数类型体积中的文件将进行缩放,以便实际范围是输入文件中数据的完整范围。用户可以通过使用适当的实际最小值和最大值调用此函数来定义另一个范围。对于浮点类型体积,设置范围将更改体积的实际范围,但不会更改体积中的实际值。如果最小值大于或等于最大值,则恢复默认行为。

替代体积输入方法

[编辑 | 编辑源代码]

除了使用input_volume()函数一次性输入体积外,还有另一种方法可以允许在体积输入与其他处理任务之间穿插。以下是input_volume()函数为了输入体积而调用的例程集。

  public  Status  start_volume_input(
      STRING               filename,
      int                  n_dimensions,
      STRING               dim_names[],
      nc_type              volume_nc_data_type,
      BOOLEAN              volume_signed_flag,
      Real                 volume_voxel_min,
      Real                 volume_voxel_max,
      BOOLEAN              create_volume_flag,
      Volume               *volume,
      minc_input_options   *options,
      volume_input_struct  *input_info )

这将初始化一个文件名,以便增量输入体积。此函数的参数与input_volume()函数的参数相同,此外还添加了input_info结构,该结构被初始化以供在剩余函数中使用。如果需要,将创建体积,但不分配,并且不会读取体积的任何部分。

  public  void  delete_volume_input(
      volume_input_struct   *input_info )

关闭文件并终止体积的输入。通过调用start_volume_input()函数,然后调用delete_volume_input()函数,可以创建一个体积,该体积具有有关文件体积的所有信息,但未分配体素。

  public  void  cancel_volume_input(
      Volume                volume,
      volume_input_struct   *input_info )

关闭文件,终止体积的输入,并删除体积。只需调用delete_volume_input(),然后调用delete_volume()

  public  BOOLEAN  input_more_of_volume(
      Volume                volume,
      volume_input_struct   *input_info,
      Real                  *fraction_done )

输入体积的一小部分,计算结果既有效率,又不会太大,以至于不能在调用此函数时穿插其他处理的调用。如果还有更多工作要做,则返回TRUE,即应再次调用此函数。已完成的部分作为0到1之间的数字传递回来。

  public  Minc_file   get_volume_input_minc_file(
      volume_input_struct   *volume_input )

将体积输入中的Minc_file结构传递回来,以允许在需要此类型例程中使用此结构。

体积输出

[编辑 | 编辑源代码]

体积输出可以通过两种例程之一完成,具体取决于体积是否被视为另一个体积的修改版本或独立体积。

  public  Status  output_volume(
      STRING                filename,
      nc_type               file_nc_data_type,
      BOOLEAN               file_signed_flag,
      Real                  file_voxel_min,
      Real                  file_voxel_max,
      Volume                volume,
      STRING                history,
      minc_output_options   *options )

将指定的体积输出到指定的文件名。如果参数file_nc_data_typeMI_ORIGINAL_TYPE,则体积将以体积变量中的相同类型存储在MINC文件中。否则,四个参数file_nc_data_typefile_signed_flagfile_voxel_minfile_voxel_max指定在文件中存储体积的类型和有效体素范围。如果history参数非空,则它表示体积的描述,并将放置在MINC体积中。如果options参数为NULL,则将使用默认选项。否则,可以通过此参数设置一些特定输出选项,以及以下函数

  public  void  set_default_minc_output_options(
      minc_output_options  *options )

options结构设置为默认值。然后,用户可以覆盖默认值并将结构传递给output_volume()函数。目前,只有一个输出选项

  public  void  delete_minc_output_options(
      minc_output_options  *options           )

删除options数据。

  public  void  set_minc_output_dimensions_order(
      minc_output_options  *options,
      int                  n_dimensions,
      STRING               dimension_names[] )

定义文件的输出顺序。每个维度名称字符串必须在体积中具有匹配的维度名称,这定义了输出文件中维度的顺序。例如,可能以<function>(x, y, z)</function>顺序输入体积。要以(z, y, x)顺序输出它,请将dimension_names设置为三个字符串MIzspaceMIyspaceMIxspace

  public  void  set_minc_output_real_range(
      minc_output_options  *options,
      Real                 real_min,
      Real                 real_max )

定义文件中将出现的图像范围。默认情况下,不指定任何内容,并且output_volume使用卷的实际最小值和最大值。若要设置卷的实际范围,请参阅set_volume_real_range()的相关文档。

如果卷是当前存储在文件中的另一个卷的修改,那么使用以下函数输出卷更为合适

  public  Status  output_modified_volume(
      STRING                filename,
      nc_type               file_nc_data_type,
      BOOLEAN               file_signed_flag,
      Real                  file_voxel_min,
      Real                  file_voxel_max,
      Volume                volume,
      STRING                original_filename,
      STRING                history,
      minc_output_options   *options )

此函数与其他卷输出方法(output_volume())之间的唯一区别在于,此函数将辅助数据从原始文件(original_filename)复制到新文件。此辅助数据包括患者姓名和其他扫描数据等项目,并且不会读入卷,因此将它正确传递到新的 MINC 文件的唯一方法是使用此函数。

卷操作

[编辑 | 编辑源代码]

创建和分配卷后,有很多函数用于操作卷。请注意,与每个卷相关联的是一个有效的体素范围,该范围指示实际存储在卷中的值的范围,例如,0 到 200 是无符号字节卷的一种可能的范围。还有一个第二个范围,即real范围,它将有效体素范围映射到任意实际范围,例如,有效体素的 0 到 200 可以映射到real范围内的 -1.5 到 1.5。在处理卷时,通常会对real范围感兴趣。

  public  Real  get_volume_voxel_value(
      Volume   volume,
      int      v0,
      int      v1,
      int      v2,
      int      v3,
      int      v4 )

给定一个卷和一个到五个体素索引(取决于卷的维数),返回相应的体素值。例如,如果卷是三维的,那么最后两个参数将被忽略。

  value = convert_voxel_to_value( volume, voxel );

给定一个卷和一个体素值,将它转换为实际范围内的值,并返回它。

  voxel = convert_value_to_voxel( volume, value )

给定一个卷和一个实际值,将它转换为有效体素范围内的体素值,并返回它。

  public  Real  get_volume_real_value(
      Volume   volume,
      int      v0,
      int      v1,
      int      v2,
      int      v3,
      int      v4 )

给定一个卷和一个到五个体素索引(取决于卷的维数),返回相应的实际值。例如,如果卷是三维的,那么最后两个参数将被忽略。

  public  void  set_volume_voxel_value(
      Volume   volume,
      int      v0,
      int      v1,
      int      v2,
      int      v3,
      int      v4,
      Real     voxel )

给定一个卷、一个到五个体素索引和一个voxel值,将此值分配给卷中相应的体素。请注意,不会执行从有效实际范围到有效体素范围的转换,因此用户可能需要首先使用convert_value_to_voxel函数或使用set_volume_real_value函数。

  public  void  set_volume_real_value(
      Volume   volume,
      int      v0,
      int      v1,
      int      v2,
      int      v3,
      int      v4,
      Real     value )

给定一个卷、一个到五个体素索引和一个value,在转换为体素值后,将此值分配给卷中相应的体素。

  public  void  delete_volume(
      Volume   volume )

删除与卷相关联的所有内存。

  public  nc_type  get_volume_nc_data_type(
      Volume       volume,
      BOOLEAN      *signed_flag )

返回卷的存储类型(例如,NC_SHORT),并传递回一个指示它是有符号还是无符号的信息。

  public  int  get_volume_n_dimensions(
      Volume   volume )

返回卷的维数。

  public  void  get_volume_sizes(
      Volume   volume,
      int      sizes[] )

将每个维度的尺寸存储在数组sizes中。这是每个维度的体素数。数组sizes必须至少与卷的维数一样大。

  public  void  get_volume_separations(
      Volume   volume,
      Real     separations[] )

将每个维度的切片间距存储在数组separations中。数组separations必须至少与卷的维数一样大。

  public  Real  get_volume_voxel_min(
      Volume   volume )
  public  Real  get_volume_voxel_max(
      Volume   volume )
  public  void  get_volume_voxel_range(
      Volume     volume,
      Real       *voxel_min,
      Real       *voxel_max )

前两个函数返回允许的最小或最大体素值。第三个函数传递回这两个值。

  public  Real  get_volume_real_min(
      Volume     volume )
  public  Real  get_volume_real_max(
      Volume     volume )
  public  void  get_volume_real_range(
      Volume     volume,
      Real       *min_value,
      Real       *max_value )

前两个函数返回最小或最大实际值。第三个函数传递回这两个值。映射到此实际空间将最小voxel值线性映射到最小real值,并将最大voxel值映射到最大real值。

  public  STRING  *get_volume_dimension_names(
      Volume   volume )

返回指向每个维度名称列表的指针。此内存必须由调用函数使用以下函数释放。

  public  void  delete_dimension_names(
      Volume   volume,
      STRING   dimension_names[] )

释放为维度名称分配的内存。

  public  void  set_rgb_volume_flag(
      Volume   volume,
      BOOLEAN  flag )

设置指示卷是否为红-绿-蓝颜色的卷标志。只有数据类型为无符号长的卷才能是 rgb 卷。

  public  BOOLEAN  is_an_rgb_volume(
      Volume   volume )

如果卷是红-绿-蓝颜色卷,则返回TRUE,因为可以在卷输入时创建此卷。

卷坐标系

[编辑 | 编辑源代码]

一个卷有两个坐标系。体素坐标系只是卷的 n 维索引坐标系。例如,体素坐标 (0.0, 0.0, 0.0) 对应于三维卷中第一个体素的中心。体素坐标 ( 99.0, 0.0, 0.0 ) 对应于大小为 ( 100, 200, 150 ) 的三维卷的第一个方向中的最后一个体素的中心。第二个坐标系是任意的三维坐标系,通常称为世界坐标系,通常是 Talairach 坐标系。以下函数提供在这两个坐标系之间进行转换的方法

  public  void  convert_voxel_to_world(
      Volume   volume,
      Real     voxel[],
      Real     *x_world,
      Real     *y_world,
      Real     *z_world )

给定一个卷和一个实值体素索引,传递回相应的世界坐标。

  public  void  convert_3D_voxel_to_world(
      Volume   volume,
      Real     voxel1,
      Real     voxel2,
      Real     voxel3,
      Real     *x_world,
      Real     *y_world,
      Real     *z_world )

convert_voxel_to_world相同,只是它仅适用于三维卷。

  public  void  convert_world_to_voxel(
      Volume   volume,
      Real     x_world,
      Real     y_world,
      Real     z_world,
      Real     voxel[] )

将世界坐标转换为体素。为了将这些体素坐标用作整数索引(例如,作为GET_VALUE宏的参数),参数voxel的每个分量必须首先四舍五入到最接近的整数。

  public  void  convert_3D_world_to_voxel(
      Volume   volume,
      Real     x_world,
      Real     y_world,
      Real     z_world,
      Real     *voxel1,
      Real     *voxel2,
      Real     *voxel3 )

convert_world_to_voxel相同,只是它仅适用于三维卷。

  public  void  convert_voxel_vector_to_world(
      Volume          volume,
      Real            voxel_vector[],
      Real            *x_world,
      Real            *y_world,
      Real            *z_world )
  public  void  convert_world_vector_to_voxel(
      Volume          volume,
      Real            x_world,
      Real            y_world,
      Real            z_world,
      Real            voxel_vector[] )

这两个函数在体素空间和世界空间之间转换向量。这是通过将点 (0,0,0) 转换为另一个空间来完成的,此外,将向量视为点并将其转换为另一个空间,并将这两个结果之间的向量传递回。

  public  void  convert_voxel_normal_vector_to_world(
      Volume          volume,
      Real            voxel1,
      Real            voxel2,
      Real            voxel3,
      Real            *x_world,
      Real            *y_world,
      Real            *z_world )

将被假定为世界坐标系中的表面法线的向量转换。

卷插值

[编辑 | 编辑源代码]

除了访问特定体素值的例程之外,还可以使用最近邻、线性或三次插值对卷进行插值,这些插值在体素空间或世界空间中指定。

  public  int   evaluate_volume(
      Volume         volume,
      Real           voxel[],
      BOOLEAN        interpolating_dimensions[],
      int            degrees_continuity,
      BOOLEAN        use_linear_at_edge,
      Real           outside_value,
      Real           values[],
      Real           **first_deriv,
      Real           ***second_deriv )

在指定的体素处对卷进行插值,其中degrees_continuity的值分别为 -1、0 或 2,表示最近邻、线性或三次插值。如果use_linear_at_edge为真,则任何三次插值都将降级为卷边缘附近的线性插值。此选项仅在特殊情况下需要。参数outside_value是卷外部任何点的值。参数interpolating_dimensions指示哪些维度正在进行插值,通常是NULL指针,表示所有维度都在进行插值。插值后的值将放置在 values 数组中。值的个数等于未插值维度的值的个数,如果所有维度都在进行插值,则为 1。如果导数参数不为空,则生成的导数将放置在相应的位置。first_deriv是一个大小为 values 个数乘以插值维度个数的二维数组。second_deriv是一个大小为 values 个数乘以插值维度个数乘以插值维度个数的三维数组。

  public  void   evaluate_volume_in_world(
      Volume         volume,
      Real           x,
      Real           y,
      Real           z,
      int            degrees_continuity,
      BOOLEAN        use_linear_at_edge,
      Real           outside_value,
      Real           values[],
      Real           deriv_x[],
      Real           deriv_y[],
      Real           deriv_z[],
      Real           deriv_xx[],
      Real           deriv_xy[],
      Real           deriv_xz[],
      Real           deriv_yy[],
      Real           deriv_yz[],
      Real           deriv_zz[] )

在指定的世界位置处对卷进行插值,其中degrees_continuity的值分别为 -1、0 或 2,表示最近邻、线性或三次插值。如果use_linear_at_edge为真,则任何三次插值都将降级为卷边缘附近的线性插值。此选项仅在特殊情况下需要。参数outside_value是卷外部任何点的值。插值后的值将放置在 values 数组中。值的个数等于非空间维度的值的个数,如果所有维度都是空间维度,则为 1。如果导数参数不为空,则生成的导数将放置在相应的位置。

  public  void  set_volume_interpolation_tolerance(
      Real   tolerance )

出于速度考虑,如果在体素中心或其附近评估卷,则不执行插值,而是返回体素值。容差定义了此操作发生的距离与体素中心之间的距离,默认值为 0。请注意,如果需要导数并且指定的连续性程度为 0 或更大,则即使在指定的体素容差范围内,也会执行插值。

从头开始创建卷

[编辑 | 编辑源代码]

在某些情况下,希望以与从文件读取不同的方式创建卷。以下函数提供从头开始创建卷或创建与现有卷相似的卷的方法。

  public   Volume   create_volume(
      int         n_dimensions,
      STRING      dimension_names[],
      nc_type     nc_data_type,
      BOOLEAN     signed_flag,
      Real        voxel_min,
      Real        voxel_max )

创建并返回给定类型(例如,NC_BYTEsigned_flag 等于FALSE)和给定有效体素范围的卷。dimension_names用于描述卷的每个维度,目前仅在将卷写入文件时使用。

  public  void  set_volume_voxel_range(
      Volume   volume,
      Real     voxel_min,
      Real     voxel_max )
  public  void  set_volume_real_range(
      Volume   volume,
      Real     real_min,
      Real     real_max )

创建卷后,可以使用这些函数随后更改有效体素范围或有效实际范围。

  public  void  set_volume_sizes(
      Volume       volume,
      int          sizes[] )

设置卷的尺寸,即每个维度的体素数。请注意,这必须在调用函数alloc_volume_data分配体素之前完成。

  public  void  alloc_volume_data(
      Volume   volume )

创建卷并设置其大小后,此函数将为体素分配内存。请注意,体素值不会被初始化,用户必须用所需的值填充卷。

与每个卷相关联的是从voxel空间到world空间的变换。有几种方法可以定义这种变换。最简单的方法是直接指定它

  public  void  set_voxel_to_world_transform(
      Volume             volume,
      General_transform  *transform )

将给定变换分配给卷。

  public  General_transform  *get_voxel_to_world_transform(
      Volume   volume )

返回指向卷的体素到世界变换的指针。

  public  void  set_volume_separations(
      Volume   volume,
      Real     separations[] )

设置每个卷维度的体素间距。请注意,这会导致体素到世界变换相应地更新。

  public  void  set_volume_translation(
      Volume  volume,
      Real    voxel[],
      Real    world_space_voxel_maps_to[] )

设置体素到世界变换的平移部分。指定一个体素坐标(voxel),以及希望此体素映射到的实际世界位置(world_space_voxel_maps_to)。体素到世界变换将更新以提供此映射。

  public  void  set_volume_direction_cosine(
      Volume   volume,
      int      dimension,
      Real     dir[] )

设置特定体素维度的真实世界轴。例如,如果dimension为 1,而dir为 (0.0, 1.0, 1.0),则体积第二维度的体素将映射到真实世界轴 (0.0, 1.0, 1.0),该轴已归一化为单位长度,然后按第二体积维度的体积间距进行缩放。

  public  void  get_volume_direction_cosine(
      Volume   volume,
      int      dimension,
      Real     dir[] )

返回特定体素维度的真实世界轴。请注意,dimension必须是体积中的空间维度。

  public  void  set_volume_space_name(
      Volume   volume,
      STRING   name )

使用字符串名称设置体积的坐标系类型。这可以是任何字符串,但 MINC 提供了一组定义的常量:MI_NATIVEMI_TALAIRACHMI_CALLOSAL

  public  STRING  get_volume_space_name(
      Volume   volume )

返回体积的坐标系类型的副本,以字符串名称的形式表示。调用函数在完成使用后必须释放此字符串。

复制体积

[编辑 | 编辑源代码]

创建体积的另一种方法是简单地从现有体积中复制整个体积定义。

  public  Volume   copy_volume_definition(
      Volume   existing_volume,
      nc_type  nc_data_type,
      BOOLEAN  signed_flag,
      Real     voxel_min,
      Real     voxel_max )
  public  Volume   copy_volume_definition_no_alloc(
      Volume   volume,
      nc_type  nc_data_type,
      BOOLEAN  signed_flag,
      Real     voxel_min,
      Real     voxel_max )

这两个函数都创建并返回一个新体积,该体积与现有体积具有相同的定义(大小、体素到世界空间变换等)。如果参数nc_data_type不是MI_ORIGINAL_TYPE,则新体积的存储类型将与原始体积不同,并由nc_data_typesigned_flagvoxel_minvoxel_max 指定。在第一个函数中,体素值已分配但未初始化。在第二个函数中,它们未分配。

  public  Volume   copy_volume(
      Volume   volume )

创建体积的精确副本。

体积缓存

[编辑 | 编辑源代码]

为了有效地处理对虚拟内存而言过大的体积,已经实现了一个简单的缓存方案。磁盘文件按需读取和写入,以提供将大型体积完全驻留在内存中的外观。之前描述的所有体积例程都透明地支持此功能,但有一些例程用于配置体积缓存。

  public  void  set_n_bytes_cache_threshold(
      int  threshold )

设置体积缓存的阈值字节数。如果创建的体积大于此值(无论显式创建还是从文件输入创建),则该体积将在内部由缓存方案表示。如果未调用此函数,则默认值为 80 兆字节,或环境变量VOLUME_CACHE_THRESHOLD的值(如果存在)。阈值为零将导致所有体积被缓存。阈值小于零将导致没有体积被缓存。

  typedef  enum  { SLICE_ACCESS, RANDOM_VOLUME_ACCESS }
                 Cache_block_size_hints;
  public  void  set_cache_block_sizes_hint(
      Cache_block_size_hints  hint )
  public  void  set_default_cache_block_sizes(
      int   block_sizes[] )

当在缓存的体积中访问体素时,将根据需要从磁盘读取或写入包含该体素的相应块。这两个函数控制块的默认大小,因此控制快速访问相邻体素(使用较大的块大小)和快速访问任意分布的体素(使用较小的块大小)之间的权衡。函数set_cache_block_sizes_hint 表示未来创建的体积的默认块大小应基于这样的假设来计算,即应用程序将主要以逐片的方式访问体积体素(SLICE_ACCESS)或以不可预测的顺序访问体素(RANDOM_VOLUME_ACCESS)。第二个函数set_default_cache_block_sizes 提供了一种替代方案,其中显式设置了默认块大小。对任一函数的调用都会覆盖之前对另一个函数的任何调用。如果未调用这两个函数中的任何一个,则默认值为每个维度的块大小为 8 个体素,或环境变量VOLUME_CACHE_BLOCK_SIZE指定的值。这些函数只影响之后创建的体积。要更改给定体积的值,可以使用以下函数

  public  void  set_volume_cache_block_sizes(
      Volume    volume,
      int       block_sizes[] )

设置给定体积的缓存块大小。由于此函数会导致缓存完全刷新,因此要更改块大小,可能会在后续体素访问过程中造成暂时速度损失,这些访问会导致缓存从文件读取块。

  public  void  set_default_max_bytes_in_cache(
      int  n_bytes )

设置每个体积允许在缓存中使用的默认最大字节数。由于有更大的机会在缓存中找到特定体素,因此较高的数字可能会提供更快的访问速度,因此此值应尽可能大,前提是考虑到可用的虚拟内存量。如果未调用此函数,则默认值为 80 兆字节,或环境变量VOLUME_CACHE_SIZE的值(如果存在)。设置值为 0 将导致缓存的体积在其缓存中只有一个块。此函数只影响之后创建的体积。要更改特定体积的此值,可以使用以下函数

  public  void  set_volume_cache_size(
      Volume    volume,
      int       max_memory_bytes )

设置特定体积允许在缓存中使用的最大字节数。调用此函数将刷新缓存,以便重新分配数据结构以反映新的大小。与调用函数set_volume_cache_block_sizes 一样,访问像素时可能会出现暂时的速度损失。

  public  void  set_cache_output_volume_parameters(
      Volume                      volume,
      STRING                      filename,
      nc_type                     file_nc_data_type,
      BOOLEAN                     file_signed_flag,
      Real                        file_voxel_min,
      Real                        file_voxel_max,
      STRING                      original_filename,
      STRING                      history,
      minc_output_options         *options )

当修改缓存的体积时,将创建一个临时文件来保存体素值。当删除体积时,此文件也将被删除。当输出缓存的体积时,临时文件将被复制到输出文件,这会导致同时存在体积的两个副本,这可能会对磁盘存储造成不可接受的需求,尤其是对于大型体积而言。为避免这种情况,应用程序可以指定一个文件名来放置体素值,这将覆盖使用临时文件。当删除体积时,此文件将被关闭并保留在磁盘上,从而无需应用程序输出体积。

如果应用程序随后调用output_volume() 将此体积输出到与由此函数设置的文件名相同的文件,则输出请求将被忽略,因为该体积已经存在于此文件中。基本上,程序员应使用与最终调用output_volume() 时传递的参数相同的参数调用set_cache_output_volume_parameters()。如果体积是缓存的体积,则输出文件将在设置体积体素时立即创建,并且随后对output_volume() 的调用不会重新创建文件,而只是将缓存缓冲区刷新,使文件完全更新。如果体积不是缓存的体积,则对set_cache_output_volume_parameters() 的调用将被忽略,而随后对output_volume() 的调用将按照指定的方式创建文件。请注意,在调用此函数时,重要的是在退出程序之前删除体积,以便缓存的体积可以刷新其缓冲区并关闭文件,否则,对体积的最新更改将不会写入文件。

体积源代码示例

[编辑 | 编辑源代码]

这里介绍了读取、写入和操作体积的示例。

一个完整的程序,用于读取 MINC 体积,将所有大于 100.0 的值更改为 100.0,然后将结果写入一个新文件。

  #include  <volume_io.h>
  /* ------------------------------------------------------------------
  @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.
  ------------------------------------------------------------------ */
  int  main(
      int   argc,
      char  *argv[] )
  {
      int        v1, v2, v3, sizes[MAX_DIMENSIONS];
      Real       value;
      Volume     volume;
      /*--- input the volume */
      if( input_volume( "volume.mnc", 3, NULL, MI_ORIGINAL_TYPE, FALSE,
              0.0, 0.0, TRUE, &volume,
              (minc_input_options *) NULL ) != OK )
          return( 1 );
      get_volume_sizes( volume, sizes );
      /*--- change all values over 100 to 100 */
      for( v1 = 0;  v1 < sizes[0];  ++v1 ) {
          for( v2 = 0;  v2 < sizes[1];  ++v2 ) {
              for( v3 = 0;  v3 < sizes[2];  ++v3 ) {
                  value = get_volume_real_value( volume, v1, v2, v3,
                                                 0, 0 );
                  if( value > 100.0 ) {
                      set_volume_real_value( volume, v1, v2, v3,
                                             0, 0, 100.0 );
                  }
              }
          }
      }
      /*--- output the modified volume */
      if( output_modified_volume( "output.mnc", MI_ORIGINAL_TYPE,
               FALSE, 0.0, 0.0, volume, "volume.mnc",
               "Modified by clamping to 100",
               (minc_output_options *) NULL ) != OK )
          return( 1 );
      return( 0 );
  }

多体积输入

[编辑 | 编辑源代码]

之前描述的input_volume 函数旨在为大多数应用程序提供一个简单的单函数接口,用于读取体积。但是,应用程序有时可能不想一次读取整个体积,或者可能需要将输入文件分解成多个体积,例如,3D 体积的切片。可以使用以下函数来实现这一点。

  public  Minc_file  initialize_minc_input(
      STRING               filename,
      Volume               volume,
      minc_input_options   *options )

此函数打开一个 MINC 文件以输入到指定的体积。文件中的维度数量必须大于或等于体积中的维度数量,并且体积中的每个维度名称都必须在文件中具有匹配的维度名称。将返回一个文件句柄,用于以下例程中。

  public  Minc_file  initialize_minc_input_from_minc_id(
      int                  minc_id,
      Volume               volume,
      minc_input_options   *options )

此函数执行与initialize_minc_input 相同的任务,只是它接受一个先前打开的 MINC 文件作为参数,而不是文件名。

  public  int  get_minc_file_id(
      Minc_file  file )

返回文件的 MINC ID,该句柄可用于对文件执行 MINC 函数。

  public  int  get_n_input_volumes(
      Minc_file  file )

确定打开的 MINC 文件中的体积总数。例如,如果文件包含大小为 100 x 200 x 300 的 x、y、z 数据,并且该文件已使用二维 x-z 体积打开,则返回的体积数将为 200,即 y 切片的数量。

  public  BOOLEAN  input_more_minc_file(
      Minc_file   file,
      Real        *fraction_done )

将 MINC 文件的一部分读取到initialize_minc_input 中指定的体积。必须调用此函数,直到它返回 FALSE,此时体积已完全读入。要读取文件中下一个体积(如果有),必须调用以下函数。

  public  BOOLEAN  advance_input_volume(
      Minc_file   file )

将输入文件推进到下一个体积。

  public  BOOLEAN  reset_input_volume(
      Minc_file   file )

将输入重置为文件中的第一个体积的开头。

  public  Status  close_minc_input(
      Minc_file   file )

关闭 MINC 文件。

多体积输出

[编辑 | 编辑源代码]

与多体积输入类似,还有一些例程用于通过子体积将输出提供给文件。

  public  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,
      minc_output_options    *options )

打开一个 MINC 文件以供创建。参数指定每个维度的数量和名称,以及每个维度的尺寸。文件的类型由四个参数控制:file_nc_data_typefile_signed_flagfile_voxel_minfile_voxel_max。到世界坐标的变换由voxel_to_world_transform 参数指定。volume 附加到文件以供输出。体积的维度不能超过文件中的维度,并且每个维度的名称必须与文件中的一个维度匹配。optionsoutput_volume 函数中指定的一致。请注意,与output_volume 函数不同,如果未指定图像最小值和最大值,则输出文件将为每个图像具有单独的图像最小值和最大值。

  public  int  get_minc_file_id(
      Minc_file  file )

返回文件的 MINC ID,该句柄可用于对文件执行 MINC 函数。

  public  Status  copy_auxiliary_data_from_minc_file(
      Minc_file   file,
      STRING      filename,
      STRING      history_string )

filename 参数中指定的 MINC 文件中的辅助信息复制到打开的 MINC file 中。

  public  Status  output_minc_volume(
      Minc_file   file )

将附加的体积输出到当前文件位置的 MINC 文件。文件位置将根据体积的大小前进。

  public  Status  output_volume_to_minc_file_position(
      Minc_file   file,
      Volume      volume,
      int         volume_count[],
      long        file_start[] )

更通用的例程,用于将指定的体积输出到指定位置的 MINC 文件。由于此例程不会更新文件位置,因此它不会干扰使用函数output_minc_volume 进行的正常体积输出。由volume_count 定义的体积超立方体将从给定的文件位置开始写入文件。

  public  Status  close_minc_output(
      Minc_file   file )

关闭 MINC 文件。

标签点

[编辑 | 编辑源代码]

标签点是三维点集,用于标记感兴趣的位置。通常,这些点来自手动定位多个体积中的对应位置,以提供这些体积之间的映射。标签点存储在麦康奈尔脑成像中心设计的 ASCII 格式中,文件名约定以.tag结尾。一个标签文件可以包含单个标签点集或两个标签点集,具体取决于文件是否对应一个或两个体积。每个标签点包含三个坐标:x、y 和 z。每一组一个或两个标签点还存储着一些额外的可选信息。一个标签点集可能包含也可能不包含一组三个值:一个实数值权重、一个整型结构 ID 和一个整型患者 ID。一个标签点可能包含也可能不包含一个标签字符串。本文档描述了读取和写入此格式文件的函数。

  public  Status  input_tag_points(
      FILE      *file,
      int       *n_volumes,
      int       *n_tag_points,
      Real      ***tags_volume1,
      Real      ***tags_volume2,
      Real      **weights,
      int       **structure_ids,
      int       **patient_ids,
      STRING    *labels[] )
  public  Status  input_tag_file(
      STRING    filename,
      int       *n_volumes,
      int       *n_tag_points,
      Real      ***tags_volume1,
      Real      ***tags_volume2,
      Real      **weights,
      int       **structure_ids,
      int       **patient_ids,
      STRING    *labels[] )

这两个函数从文件中读取一组标签点。第一个形式假设文件已经打开,并将由调用函数关闭,而第二个形式打开并关闭由文件名指定的,扩展名为.tag的文件。每个集合中的体积数(每个集合中标签的数量,目前为一个或两个)在n_volumes参数中返回。标签点数在n_tag_points参数中返回。每个集合中第一个标签点的三维坐标在tags_volume1参数中返回。如果体积数为两个,则每个集合中的第二个标签点在tags_volume2参数中返回。最后四个参数返回与每个标签点集相关的辅助信息。如果调用程序对这四个数据中的任何一个都不感兴趣,则可以传入一个NULL指针,文件中的值将不会被返回。

  public  void  free_tag_points(
      int       n_volumes,
      int       n_tag_points,
      Real      **tags_volume1,
      Real      **tags_volume2,
      Real      weights[],
      int       structure_ids[],
      int       patient_ids[],
      STRING    labels[] )

完成标签点列表的处理后,可以通过调用此函数释放相关的内存。

  public  Status  initialize_tag_file_input(
      FILE      *file,
      int       *n_volumes )
  public  BOOLEAN  input_one_tag(
      FILE      *file,
      int       n_volumes,
      Real      tag_volume1[],
      Real      tag_volume2[],
      Real      *weight,
      int       *structure_id,
      int       *patient_id,
      STRING    *label,
      Status    *status )

这两个例程提供了一种更节省内存的方法来输入标签点。打开文件后,调用第一个例程来初始化标签的输入。接下来,重复调用第二个例程,直到它返回 FALSE,每次读取一个标签。

  public  Status  output_tag_points(
      FILE      *file,
      STRING    comments,
      int       n_volumes,
      int       n_tag_points,
      Real      **tags_volume1,
      Real      **tags_volume2,
      Real      weights[],
      int       structure_ids[],
      int       patient_ids[],
      STRING    labels[] )
  public  Status  output_tag_file(
      STRING    filename,
      STRING    comments,
      int       n_volumes,
      int       n_tag_points,
      Real      **tags_volume1,
      Real      **tags_volume2,
      Real      weights[],
      int       structure_ids[],
      int       patient_ids[],
      STRING    labels[] )

这两个函数将标签点列表写入标签点文件。第一个形式假设文件已经打开,并将在稍后由调用函数关闭,而第二个形式打开并关闭文件,扩展名为.tagcomments参数是任何任意字符串,用于记录此文件的内容。体积数(n_volumes)必须为一个或两个。标签点数由n_tag_points参数指定。一组或两组标签点的坐标分别在tags_volume1tags_volume2参数中。如果weightsstructure_idspatient_ids三个参数中的任何一个被指定为NULL,则这三部分信息都不会写入文件。同样,如果labels参数为NULL,则不会写入任何标签。

  public  STRING  get_default_tag_file_suffix()

返回一个指向字符串的指针,该字符串包含标签点文件的默认后缀,目前为``tag。此指针不应该被释放,其内容也不应该被修改。

  public  Status  initialize_tag_file_output(
      FILE      *file,
      STRING    comments,
      int       n_volumes )
  public  Status  output_one_tag(
      FILE      *file,
      int       n_volumes,
      Real      tag_volume1[],
      Real      tag_volume2[],
      Real      *weight,
      int       *structure_id,
      int       *patient_id,
      STRING    label )
  public  void  terminate_tag_file_output(
      FILE    *file )

这两个例程提供了一种更节省内存的方法来输出标签点。打开文件后,调用第一个例程来初始化标签的输出。接下来,重复调用第二个例程来每次输出一个标签点。第三个例程被调用以指示标签文件的结束。

标签点源代码示例

[编辑 | 编辑源代码]

以下是一个读取标签体积、删除所有 x 坐标为负的标签、忽略每个集合中的第二个标签点(如果存在)并将结果写入新文件的示例。

  #include  <volume_io.h>
  int  main(
      int   argc,
      char  *argv[] )
  {
      int        i, n_volumes, n_tag_points, *structure_ids, *patient_ids;
      Real       **tags1, **tags2, *weights;
      STRING     *labels;
      int        new_n_tag_points, *new_structure_ids, *new_patient_ids;
      Real       **new_tags1, *new_weights;
      STRING     *new_labels;
      /*--- input the tag file */
      if( input_tag_file( "input_tags.tag", &n_volumes, &n_tag_points,
                          &tags1, &tags2, &weights, &structure_ids,
                          &patient_ids, &labels ) != OK )
          return( 1 );
      /*--- create a new tag point list of only those tag points
            whose x coordinate is nonnegative */
      new_n_tag_points = 0;
      for_less( i, 0, n_tag_points )
      {
          if( tags1[i][0] >= 0.0 )
          {
              /*--- increase the memory allocation of the tag points */
              SET_ARRAY_SIZE( new_tags1, new_n_tag_points,
                              new_n_tag_points+1, 10 );
              ALLOC( new_tags1[new_n_tag_points], 3 );
              SET_ARRAY_SIZE( new_weights, new_n_tag_points,
                              new_n_tag_points+1, 10 );
              SET_ARRAY_SIZE( new_structure_ids, new_n_tag_points,
                              new_n_tag_points+1, 10 );
              SET_ARRAY_SIZE( new_patient_ids, new_n_tag_points,
                              new_n_tag_points+1, 10 );
              SET_ARRAY_SIZE( new_labels, new_n_tag_points,
                              new_n_tag_points+1, 10 );
              ALLOC( new_labels[new_n_tag_points], strlen(labels[i])+1 );
              /*--- copy from the input tags to the new tags */
              new_tags1[new_n_tag_points][0] = tags1[i][0];
              new_tags1[new_n_tag_points][1] = tags1[i][1];
              new_tags1[new_n_tag_points][2] = tags1[i][2];
              new_weights[new_n_tag_points] = weights[i];
              new_structure_ids[new_n_tag_points] = structure_ids[i];
              new_patient_ids[new_n_tag_points] = patient_ids[i];
              (void) strcpy( new_labels[new_n_tag_points], labels[i] );
              /*--- increment the number of new tags */
              ++new_n_tag_points;
          }
      }
      /*--- output the new tags, the subset of the input tags */
      if( output_tag_file( "output.tag", "Removed negative X's",
                           1, new_n_tag_points, new_tags1, NULL,
                           new_weights, new_structure_ids,
                           new_patient_ids, new_labels ) != OK )
          return( 1 );
      return( 0 );
  }

在处理诸如跨模态和跨受试者配准等任务时,不同坐标系之间的变换概念就出现了。提供了一个模块来处理线性(仿射)和非线性变换,并在标准化的脑成像中心格式中提供输入和输出,通常用于扩展名为.xfm的文件名。

为了支持这些功能,提供了两种结构类型。第一个(Transform)是一个四乘四的线性(仿射)变换,它可以促进刚性变换,包括缩放、旋转、平移和剪切。第二个是更高层次的变换(General_transform),它表示线性变换、非线性变换(薄板样条)、平滑插值的网格变换、用户定义的变换或这些变换的级联。

线性变换

[编辑 | 编辑源代码]

线性变换函数都处理类型为Transform的对象。

  #define   Transform_elem( transform, i, j )

用于设置或检索变换的第i行和第j列的值,其中<function>0<=i, j<=3</function>

  public  void  make_identity_transform(
      Transform   *transform )

创建一个四乘四的单位变换。

  public  void  transform_point(
      Transform  *transform,
      Real       x,
      Real       y,
      Real       z,
      Real       *x_trans,
      Real       *y_trans,
      Real       *z_trans )

通过给定的变换变换三维点,并返回三个变换后的坐标。

  public  void  transform_vector(
      Transform  *transform,
      Real       x,
      Real       y,
      Real       z,
      Real       *x_trans,
      Real       *y_trans,
      Real       *z_trans )

通过给定的变换变换三维向量,并返回三个变换后的坐标。变换点和变换向量之间的唯一区别在于,变换向量不涉及变换的平移部分。

  public  void  inverse_transform_point(
      Transform  *transform,
      Real       x,
      Real       y,
      Real       z,
      Real       *x_trans,
      Real       *y_trans,
      Real       *z_trans )

假设变换是正交变换(没有剪切分量),则通过变换的逆变换变换点。

  public  void  inverse_transform_vector(
      Transform  *transform,
      Real       x,
      Real       y,
      Real       z,
      Real       *x_trans,
      Real       *y_trans,
      Real       *z_trans )

假设变换是正交变换(没有剪切分量),则通过变换的逆变换变换向量。

  public  void   concat_transforms(
      Transform   *result,
      Transform   *t1,
      Transform   *t2 )

将变换t2乘以变换t1,并将乘积存储在result中。通过result变换一个点等效于通过t1变换该点,然后通过t2变换结果。

一般变换

[编辑 | 编辑源代码]

一般变换可以表示线性变换、薄板样条变换、网格变换和用户定义的变换,以及这些变换的级联。所有处理一般变换的函数都涉及类型为General_transform的对象。

  public  void  create_linear_transform(
      General_transform   *transform,
      Transform           *linear_transform )

创建一个由单个线性变换组成的一般变换,该线性变换由linear_transform指定。如果传入一个NULL作为linear_transform参数,则创建一个单位变换。

  public  void  create_thin_plate_transform(
      General_transform    *transform,
      int                  n_dimensions,
      int                  n_points,
      float                **points,
      float                **displacements )

创建一个由薄板样条组成的一般变换,它提供了多维空间的平滑非线性映射。points参数是一个大小为n_points乘以n_dimensions的数组,表示一组点。displacements是一个(n_points + n_dimensions + 1)乘以n_dimensions的数组,它是由get_nonlinear_warp()函数创建的,该函数不包含在这个库中。

  public  void  create_grid_transform(
      General_transform    *transform,
      Volume               displacement_volume )

创建一个由网格变换组成的一般变换,它提供了多维空间的平滑非线性映射。displacment_volume参数是一个四维体积,表示 x、y 和 z 方向的位移。体积的三个维度必须对应于 x、y 和 z 空间维度,第四个维度必须恰好有三个分量,即世界空间中每个方向的位移。维度可以以任何顺序排列。

  typedef  void   (*User_transform_function)( void  *user_data,
                                              Real  x,
                                              Real  y,
                                              Real  z,
                                              Real  *x_trans,
                                              Real  *y_trans,
                                              Real  *z_trans )
  public  void  create_user_transform(
      General_transform         *transform,
      void                      *user_data,
      size_t                    size_user_data,
      User_transform_function   transform_function,
      User_transform_function   inverse_transform_function )

通过复制用户数据(从user_data开始的size_user_data字节的数据)来创建一个用户定义的变换。还需要两个函数指针,用来指定变换点和逆变换点的方法。这些函数的类型为User_transform_function,上面已指定。

  public  void  delete_general_transform(
      General_transform   *transform )

释放一般变换结构中存储的内存。

  public  Transform_types  get_transform_type(
      General_transform   *transform )

返回一般变换类型,可以是LINEARTHIN_PLATE_SPLINEUSER_TRANSFORMCONCATENATED_TRANSFORM之一。

  public  Transform  *get_linear_transform_ptr(
      General_transform   *transform )

如果一般变换的类型为LINEAR,则返回指向线性变换(类型为Transform)的指针,以便与前面描述的特定于线性变换的例程一起使用。否则,打印错误消息。

  public  void  concat_general_transforms(
      General_transform   *first,
      General_transform   *second,
      General_transform   *result )

级联两个一般变换。如果这两个变换都是类型LINEAR,则结果也是这种类型,是两个矩阵的乘积。否则,结果变换只是两个变换的级联。

  public  int  get_n_concated_transforms(
      General_transform   *transform )

返回给定变换中级联变换的数量。如果变换的类型为CONCATENATED_TRANSFORM,则返回的数量是变换的数量,否则为 1。

  public  General_transform  *get_nth_general_transform(
      General_transform   *transform,
      int                 n )

如果变换的类型为CONCATENATED_TRANSFORM,则返回指向第n个变换的指针,其中n大于等于零且小于级联变换中的变换数量。

使用一般变换

[编辑 | 编辑源代码]
  public  void  general_transform_point(
      General_transform   *transform,
      Real                x,
      Real                y,
      Real                z,
      Real                *x_transformed,
      Real                *y_transformed,
      Real                *z_transformed )

通过一般变换变换三维点,并将结果传递回最后三个参数。

  public  void  general_inverse_transform_point(
      General_transform   *transform,
      Real                x,
      Real                y,
      Real                z,
      Real                *x_transformed,
      Real                *y_transformed,
      Real                *z_transformed )

通过一般变换的逆变换变换三维点,并将结果传递回最后三个参数。

  public  void  copy_general_transform(
      General_transform   *transform,
      General_transform   *copy )

创建一般变换的副本,根据需要在结构中分配内存。

  public  void  create_inverse_general_transform(
      General_transform   *transform,
      General_transform   *inverse )

创建一个新的一般变换,它是给定变换的逆变换。

  public  void  invert_general_transform(
      General_transform   *transform )

更改变换使其成为其逆变换。在同一个变换上调用它两次等同于根本不调用该函数。

读取和写入一般变换

[编辑 | 编辑源代码]

一般变换存储在以麦康奈尔脑成像中心设计的 ASCII 格式的文件中,通常具有 .xfm 的文件名扩展名。输入和输出函数是

  public  Status  output_transform_file(
      STRING              filename,
      STRING              comments,
      General_transform   *transform )
  public  Status  output_transform(
      FILE                *file,
      STRING              filename,
      int                 *volume_count_ptr,
      STRING              comments,
      General_transform   *transform )

这两个函数将一般变换写入文件,以适当的格式。comments 行是一个任意字符串,它存储在文件中以供文档目的。comments 中的换行符会正确地导致多行注释,并在每行开头插入注释字符。第一种形式打开文件,使用默认扩展名 .xfm,写入变换,然后关闭文件。函数的第二种形式假定文件已经打开,稍后将关闭。由于变换可能包含指向定义变换的 MINC 文件的指针,因此必须将文件的名称传递给此函数,以用于创建 MINC 文件的名称。volume_count_ptr 必须指向一个已初始化的整数。每次创建变换的辅助 MINC 文件时,该整数都会用于创建唯一的文件名,然后 volume_count_ptr 会递增。这两个函数都返回 ERROROK

  public  Status  input_transform(
      FILE                *file,
      STRING              filename,
      General_transform   *transform )
  public  Status  input_transform_file(
      STRING              filename,
      General_transform   *transform )

从文件输入一般变换。第一种形式假定文件已经打开以供输入,并且稍后将关闭。第二种形式打开文件以供输入,使用默认扩展名 .xfm,读取变换,然后关闭文件。这两个函数都返回 ERROROK

  public  STRING  get_default_transform_file_suffix()

返回指向字符串的指针,该字符串包含变换文件的默认后缀,目前为 ``xfm。此指针不应释放或修改其内容。

最终源代码示例

[编辑 | 编辑源代码]

这是一个示例,它试图说明一个典型的处理任务,包括使用体积、标签点和变换。这是一个完整的程序,当与 BIC 体积 IO 库链接时,它会读取 MINC 格式的两个体积,从文件读取一组标签点,以及从文件读取变换。假定标签点位于第一个体积的世界空间中,并且假定变换会将第一个体积的世界空间中的点变换到第二个体积的世界空间中。该程序使用输入的变换变换每个标签点(大概是从第一个体积的世界空间变换到第二个体积的世界空间),然后将结果变换到第二个体积的体素空间。如果体素坐标在第二个体积内,则打印相应体素的值。

  #include  <volume_io.h>
  /* ------------------------------------------------------------------
  @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.
  ------------------------------------------------------------------ */
  int  main()
  {
      int                 v1, v2, v3, sizes[MAX_DIMENSIONS];
      Real                x_world2, y_world2, z_world2;
      Real                voxel2[MAX_DIMENSIONS];
      Real                voxel_value;
      Volume              volume1, volume2;
      int                 i, n_volumes, n_tag_points;
      int                 *structure_ids, *patient_ids;
      Real                **tags1, **tags2, *weights;
      STRING              *labels;
      General_transform   transform;
      /*--- input the two volumes */
      if( input_volume( "volume1.mnc", 3, NULL, MI_ORIGINAL_TYPE, FALSE,
              0.0, 0.0, TRUE, &volume1,
              (minc_input_options *) NULL ) != OK )
          return( 1 );
      if( input_volume( "volume2.mnc", 3, NULL, MI_ORIGINAL_TYPE, FALSE,
              0.0, 0.0, TRUE, &volume2,
              (minc_input_options *) NULL ) != OK )
          return( 1 );
      /*--- input the tag points */
      if( input_tag_file( "tags_volume1.tag", &n_volumes, &n_tag_points,
                          &tags1, &tags2, &weights, &structure_ids,
                          &patient_ids, &labels ) != OK )
          return( 1 );
      /*--- input the general transform */
      if( input_transform_file( "vol1_to_vol2.xfm", &transform ) != OK )
          return( 1 );
      /*--- convert each tag point */
      get_volume_sizes( volume2, sizes );
      for_less( i, 0, n_tag_points )
      {
          /*--- transform the tag points from volume 1 to volume 2
                world space */
          general_transform_point( &transform,
                                   tags1[i][X], tags1[i][Y], tags1[i][Z],
                                   &x_world2, &y_world2, &z_world2 );
          /*--- transform from volume 2 world space to
                volume 2 voxel space */
          convert_world_to_voxel( volume2, x_world2, y_world2, z_world2,
                                  voxel2 );
          /*--- convert voxel coordinates to voxel indices */
          v1 = ROUND( voxel2[0] );
          v2 = ROUND( voxel2[1] );
          v3 = ROUND( voxel2[2] );
          /*--- check if voxel indices inside volume */
       
          if( v1 >= 0 && v1 < sizes[0] &&
              v2 >= 0 && v2 < sizes[1] &&
              v3 >= 0 && v3 < sizes[2] )
          {
              voxel_value = get_volume_real_value( volume2, v1, v2, v3,
                                                   0, 0 );
              print( "The value for tag point %d (%s) is: %g\n",
                     i, labels[i], voxel_value );
          }
          else
              print( "The tag point %d (%s) is outside.\n" );
      }
      /*--- free up memory */
      delete_volume( volume1 );
      delete_volume( volume2 );
      free_tag_points( n_volumes, n_tag_points, tags1, tags2,
                   weights, structure_ids, patient_ids, labels );
      delete_general_transform( &transform );
      return( 0 );
  }
华夏公益教科书