[Scipy-svn] r3397 - in trunk/scipy/io: . nifti nifti/bin nifti/man nifti/nifti nifti/tests nifti/tests/data

scipy-svn at scipy.org scipy-svn at scipy.org
Wed Oct 3 19:58:36 EDT 2007


Author: chris.burns
Date: 2007-10-03 18:58:26 -0500 (Wed, 03 Oct 2007)
New Revision: 3397

Added:
   trunk/scipy/io/nifti/
   trunk/scipy/io/nifti/AUTHOR
   trunk/scipy/io/nifti/COPYING
   trunk/scipy/io/nifti/Changelog
   trunk/scipy/io/nifti/MANIFEST.in
   trunk/scipy/io/nifti/Makefile
   trunk/scipy/io/nifti/PKG-INFO
   trunk/scipy/io/nifti/README.html
   trunk/scipy/io/nifti/TODO
   trunk/scipy/io/nifti/bin/
   trunk/scipy/io/nifti/bin/pynifti_pst
   trunk/scipy/io/nifti/man/
   trunk/scipy/io/nifti/man/pynifti_pst.1
   trunk/scipy/io/nifti/nifti/
   trunk/scipy/io/nifti/nifti/__init__.py
   trunk/scipy/io/nifti/nifti/nifticlib.i
   trunk/scipy/io/nifti/nifti/nifticlib.py
   trunk/scipy/io/nifti/nifti/niftiimage.py
   trunk/scipy/io/nifti/nifti/utils.py
   trunk/scipy/io/nifti/setup.py
   trunk/scipy/io/nifti/tests/
   trunk/scipy/io/nifti/tests/data/
   trunk/scipy/io/nifti/tests/data/example4d.nii.gz
   trunk/scipy/io/nifti/tests/test_fileio.py
   trunk/scipy/io/nifti/tests/test_main.py
   trunk/scipy/io/nifti/tests/test_utils.py
Log:
Check in current version of PyNifti which has an MIT License.

Added: trunk/scipy/io/nifti/AUTHOR
===================================================================
--- trunk/scipy/io/nifti/AUTHOR	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/AUTHOR	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,2 @@
+Michael Hanke <michael.hanke at gmail.com>
+

Added: trunk/scipy/io/nifti/COPYING
===================================================================
--- trunk/scipy/io/nifti/COPYING	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/COPYING	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2006-2007 Michael Hanke <michael.hanke at gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

Added: trunk/scipy/io/nifti/Changelog
===================================================================
--- trunk/scipy/io/nifti/Changelog	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/Changelog	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,137 @@
+pynifti (0.20070930.1-1) unstable; urgency=low
+
+  * Relicense under the MIT license, to be compatible with SciPy license.
+    http://www.opensource.org/licenses/mit-license.php
+  * Updated documentation.
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Sun, 30 Sep 2007 13:24:15 +0200
+
+pynifti (0.20070917.1-1) unstable; urgency=low
+
+  [ Michael Hanke ]
+  * Bugfix: Can now update NIfTI header data when no filename is set
+    (Closes: #442175).
+  * Unloading of image data without a filename set is no checked and prevented
+    as it would damage data integrity and the image data could not be
+    recovered.
+
+  [ Yaroslav Halchenko ]
+  * Added 'pixdim' property.
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Mon, 17 Sep 2007 08:32:59 +0200
+
+pynifti (0.20070905.1-1) unstable; urgency=low
+
+  [ Yaroslav Halchenko ]
+  * Fixed a bug in the qform/quaternion handling that caused changes to the
+    qform to vanish when saving to file.
+
+  [ Michael Hanke ]
+  * Added more unit tests.
+  * 'dim' vector in the NIfTI header is now guaranteed to only contain
+    non-zero elements. This caused problems with some applications.
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Wed,  5 Sep 2007 20:54:18 +0200
+
+pynifti (0.20070803.1-1) unstable; urgency=low
+
+  * Does not depend on SciPy anymore.
+  * Initial steps towards a unittest suite.
+  * pynifti_pst can now print the peristimulus signal matrix for a single
+    voxel (onsets x time) for easier processing of this information in
+    external applications.
+  * utils.getPeristimulusTimeseries() can now be used to compute mean and
+    variance of the signal (among others).
+  * debian/{copyright|watch} now point to the new home of pynifti in the
+    niftilib project on SourceForge.
+  * pynifti_pst is able to compute more than just the mean peristimulus
+    timeseries (e.g. variance and standard deviation).
+  * Relaxed build-dependency on libniftiio to any SO-version of the library.
+  * Set default image description when saving a file if none is present.
+  * Improved documentation.
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Fri,  3 Aug 2007 15:47:26 +0200
+
+pynifti (0.20070425.1-1) unstable; urgency=low
+
+  * Improved documentation. Added note about the special usage of the header
+    property. Also added notes about the relevant properties in the docstring
+    of the corresponding accessor methods.
+  * Added property and accessor methods to access/modify the repetition time
+    of timeseries (dt).
+  * Added functions to manipulate the pixdim values.
+  * Added utils.py with some utility functions.
+  * Added functions/property to determine the bounding box of an image.
+  * Fixed a bug that caused a corrupted sform matrix when converting a NumPy
+    array and a header dictionary into a NIfTI image.
+  * Added script to compute peristimulus timeseries (pynifti_pst).
+  * Package now depends on python-scipy.
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Wed, 25 Apr 2007 23:12:22 +0200
+
+pynifti (0.20070315.1-1) unstable; urgency=low
+
+  [ Yaroslav Halchenko ]
+  * Removed functionality for "NiftiImage.save() raises an IOError
+    exception when writing the image file fails."
+
+  [ Michael Hanke ]
+  * Added ability to force a filetype when setting the filename or saving 
+    a file.
+  * Reverse the order of the 'header' and 'load' argument in the NiftiImage
+    constructor. 'header' is now first as it seems to be used more often.
+  * Improved the source code documentation.
+  * Added getScaledData() method to NiftiImage that returns a copy of the data
+    array that is scaled with the slope and intercept stored in the NIfTI
+    header.
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Thu, 15 Mar 2007 18:25:52 +0100
+
+pynifti (0.20070301.2-1) unstable; urgency=low
+
+  * Fixed wrong link to the source tarball in README.html. 
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Thu,  1 Mar 2007 22:27:26 +0100
+
+pynifti (0.20070301.1-1) unstable; urgency=low
+
+  [ Michael Hanke ]
+  * Updated build-depends to comply to the latest Python policy.
+  * Initial upload to the Debian archive. (Closes: #413049)
+  * NiftiImage.save() raises an IOError exception when writing the image file
+    fails.
+
+  [ Yaroslav Halchenko ]
+  * Added extent, volextent, and timepoints properties to NiftiImage
+    class.
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Thu,  1 Mar 2007 22:08:15 +0100
+
+pynifti (0.20070220.1-1) unstable; urgency=low
+
+  * NiftiFile class is renamed to NiftiImage.
+  * SWIG-wrapped libniftiio functions are no available in the nifticlib
+    module.
+  * Fixed broken NiftiImage from Numpy array constructor.
+  * Added initial documentation in README.html.
+  * Fulfilled a number of Yarik's wishes ;)
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Tue, 20 Feb 2007 17:36:08 +0100
+
+pynifti (0.20070214.1-1) unstable; urgency=low
+
+  * Does not depend on libfslio anymore.
+  * Up to seven-dimensional dataset are supported (as much as NIfTI can do).
+  * The complete NIfTI header dataset is modifiable.
+  * Most image properties are accessable via class attributes and accessor
+    methods.
+  * Improved documentation (but still a long way to go).
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Wed, 14 Feb 2007 10:11:55 +0100
+
+pynifti (0.20061114-1) unstable; urgency=low
+
+  * Initial release. 
+
+ -- Michael Hanke <michael.hanke at gmail.com>  Tue, 14 Nov 2006 19:36:51 +0100
+

Added: trunk/scipy/io/nifti/MANIFEST.in
===================================================================
--- trunk/scipy/io/nifti/MANIFEST.in	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/MANIFEST.in	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,5 @@
+include AUTHOR COPYING Makefile README.html MANIFEST.in
+include Changelog TODO
+include man/*
+include tests/* tests/data/*
+prune nifti/wrap.py

Added: trunk/scipy/io/nifti/Makefile
===================================================================
--- trunk/scipy/io/nifti/Makefile	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/Makefile	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,43 @@
+all:
+
+distclean:
+	-rm MANIFEST Changelog
+	-rm nifti/*.{c,pyc,so} nifti/nifticlib.py
+	-rm tests/*.pyc
+	-rm -r build
+	-rm -r dist
+
+	
+orig-src: distclean 
+	# clean existing dist dir first to have a single source tarball to process
+	-rm -rf dist
+	# the debian changelog is also the upstream changelog
+	cp debian/changelog Changelog
+
+	# update manpages
+	help2man -N -n "compute peristimulus timeseries of fMRI data" \
+		bin/pynifti_pst > man/pynifti_pst.1
+
+	if [ ! "$$(dpkg-parsechangelog | egrep ^Version | cut -d ' ' -f 2,2 | cut -d '-' -f 1,1)" == "$$(python setup.py -V)" ]; then \
+			printf "WARNING: Changelog version does not match tarball version!\n" ;\
+			exit 1; \
+	fi
+	# let python create the source tarball
+	python setup.py sdist --formats=gztar
+	# rename to proper Debian orig source tarball and move upwards
+	# to keep it out of the Debian diff
+	file=$$(ls -1 dist); ver=$${file%*.tar.gz}; ver=$${ver#pynifti-*}; mv dist/$$file ../pynifti_$$ver.orig.tar.gz
+
+bdist_wininst:
+	# THIS IS ONLY FOR WINDOWS!
+	# Consider this a set of notes on how to build PyNIfTI on win32, rather
+	# than an actually working target
+	#
+	# assumes Dev-Cpp to be installed at C:\Dev-Cpp
+	python setup.py build_ext -c mingw32 --swig-opts "-C:\Dev-Cpp\include/nifti -DWIN32" -IC:\Dev-Cpp\include nifti
+	
+	# for some stupid reason the swig wrapper is in the wrong location
+	move /Y nifticlib.py nifti
+	
+	# now build the installer
+	python setup.py bdist_wininst

Added: trunk/scipy/io/nifti/PKG-INFO
===================================================================
--- trunk/scipy/io/nifti/PKG-INFO	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/PKG-INFO	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: pynifti
+Version: 0.20070930.1
+Summary: Python interface for the NIfTI IO libraries
+Home-page: http://apsy.gse.uni-magdeburg.de/hanke
+Author: Michael Hanke
+Author-email: michael.hanke at gmail.com
+License: MIT License
+Description: 
+Platform: UNKNOWN

Added: trunk/scipy/io/nifti/README.html
===================================================================
--- trunk/scipy/io/nifti/README.html	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/README.html	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,391 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+  <title>PyNIfTI - Python bindings to NIfTI</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <link rel="stylesheet" type="text/css" href="nifti.css" />
+</head>
+
+<body>
+
+<h1>PyNIfTI - Python-style access to NIfTI and ANALYZE files</h1>
+
+
+<h2>1. What is NIfTI and what do I need PyNIfTI for?</h2>
+
+<h3>NIfTI</h3>
+<p><a href="http://nifti.nimh.nih.gov">NIfTI</a> is a new Analyze-style data
+format, proposed by the
+<a href="http://nifti.nimh.nih.gov/dfwg/beyond-nifti-1">NIfTI Data Format Working Group</a>
+as a <em>"short-term measure to facilitate inter-operation of functional MRI data
+analysis software packages"</em>.</p>
+<p>Meanwhile a number of toolkits are NIfTI-aware (e.g. FSL, AFNI, SPM,
+Freesurfer and a to a certain degree also Brainvoyager).
+Additionally, <a href="http://cbi.nyu.edu/software/dinifti.php">dicomnifti</a>
+allows the direct conversion from DICOM images into the NIfTI format.</p>
+<p>With <a href="http://niftilib.sourceforge.net/niftilib_overview.html">libniftiio</a>
+there is a reference implementation of a C library to read, write and manipulate
+NIfTI images. The library source code is put into the public domain and a
+corresponding project is hosted at
+<a href="http://sourceforge.net/projects/niftilib">SourceForge</a>.</p>
+<p>In addition to the C library, there is also an IO library written in Java and
+Matlab functions to make use of NIfTI files from within Matlab.</p>
+
+
+<h3>Python</h3>
+<p>Unfortunately, it is not that trivial to read NIfTI images with Python. This is
+particularly sad, because there is a large number of easy-to-use, high-quality
+libraries for signal processing available for Python (e.g. SciPy).</p>
+<p>Moreover Python has bindings to almost any important language/program
+in the fields of maths, statistics and/or engineering. If you want to use
+<a href="http://www.r-project.org">R</a> to calculate
+some stats in a Python script, simply use <a href="http://rpy.sourceforge.net/">RPy</a>
+and pass any data to R. If you don't care about R, but Matlab is your one and
+only friend, there are at least two different Python modules to control Matlab
+from within Python scripts. Python is the glue between all those helpers and the Python user is
+able to combine as many tools as necessary to solve a given problem
+-- the easiest way.</p>
+
+
+<h3>PyNIfTI</h3>
+<p>PyNIfTI aims to provide easy access to NIfTI images from within Python. It
+uses <a href="http://www.swig.org">SWIG</a>-generated wrappers for the NIfTI
+reference library and provides the <code>NiftiImage</code> class for
+Python-style access to the image data.</p>
+<p>While PyNIfTI is not yet complete (i.e. doesn't support everything the
+C library can do), it already provides access to the most
+important features of the NIfTI-1 data format and <em>libniftiio</em>
+capabilities. The following features are currently implemented:</p>
+<ul>
+
+<li>PyNIfTI can read and write any file format supported by libniftiio. This
+includes NIfTI (single and pairs) as well as ANALYZE files.</li>
+<li>PyNIfTI provides fast and convenient access to the image data via
+<a href="http://numpy.scipy.org">NumPy</a> arrays. This should enable users to
+process image data with most (if not all) numerical routines available for
+Python. The NumPy array automatically uses a datatype corresponding to the NIfTI image data -- no
+unnecessary upcasting is performed.</li>
+<li>PyNIfTI provides full read and write access to the NIfTI header data. Header information can
+be exported to a Python dictionary and can also be updated by using information
+from a dictionary.</li>
+<li>Instead of accessing NIfTI data from files, PyNIfTI is able to create NIfTI
+images from NumPy arrays. The appropriate NIfTI header information is determined
+from the array properties. Additional header information can be optionally
+specified -- making it easy to clone NIfTI images if necessary, but with minor
+modifications.</li>
+<li>Most properties of NIfTI images are accessible via attributes and/or accessor
+functions of the <code>NiftiImage</code>. Inter-dependent properties are
+automatically updated if necessary (e.g. modifying the Q-Form matrix also updates
+the pixdim properties and quaternion representation).</li>
+<li>All properties are accessible via Python-style datatypes: A 4x4 matrix is
+an array not 16 individual numbers.</li>
+<li>PyNIfTI should be resonably fast. Image data will only be loaded into the memory
+if necessary. Simply opening a NIfTI file to access some header data is
+performed with virtually no delay independent of the size of the image. Unless image
+resizing or datatype conversion must be performed the image data can be
+shared by the NIfTI image and accessing NumPy arrays, and therefore memory won't be wasted
+memory with redundant copies of the image data. However, one should be careful to make a
+copy of the image data if you intend to resize and cast the image data (see the
+docstring of the <code>NiftiImage.asarray()</code> method).</li>
+</ul>
+
+<h4>Scripts</h4>
+<p>Some functions provided by PyNIfTI also might be useful outside the Python
+environment. Therefore I plan to add some command line scripts to the package.</p>
+</p>Currently there is only one: <em>pynifti_pst</em> (pst: peristimulus
+timecourse). Using this script one can compute the signal timecourse for a
+certain condition for all voxels in a volume at once. This might be useful for
+exploring a dataset and accompanies similar tools like FSL's <em>tsplot</em>.</p>
+<p>The output of <em>pynifti_pst</em> can be loaded into FSLView to simultaneously
+look at statistics and signal timecourses. Please see the corresponding example below.</p>
+
+<h3>Known issues aka bugs</h3>
+<ul>
+  <li>PyNIfTI currently ignores the origin field of ANALYZE files - it is neither read
+  nor written. A possible workaround is to convert ANALYZE files into the NIfTI format
+  using FSL's <em>avwchfiletype</em>.</li>
+</ul>
+
+
+<h2>2. License</h2>
+<p>PyNIfTI is written by <a href="http://apsy.gse.uni-magdeburg.de/hanke">Michael Hanke</a>
+as free software (both beer and speech) and licensed under the
+<a href="http://www.opensource.org/licenses/mit-license.php">MIT License</a>.
+</p>
+
+
+<h2>3. Download</h2>
+<p>As PyNIfTI is still pretty young, a number of significant
+improvements/modifications are very likely to happen in the near future. If you
+discover any bugs or you are missing some features, please be sure to check the
+SVN repository (read below) if your problem is already solved.</p>
+
+</p>
+<h3>Source code</h3>
+<p>Since June 2007 PyNIfTI is part of the
+<a href="http://niftilib.sourceforge.net">niftilibs family</a>. The PyNIfTI
+source code can be obtained from the
+<a href="http://sourceforge.net/projects/niftilib">Sourceforge project site</a>.
+</p>
+
+<h3>Binary packages</h3>
+<h4>GNU/Linux</h4>
+<p>PyNIfTI is available in recent versions of the Debian (since lenny) and
+Ubuntu (since gutsy in universe) distributions. The name of the binary package
+is <em>python-nifti</em> in both cases.</p>
+<ul>
+<li><a href="http://packages.debian.org/python-nifti">PyNIfTI versions in Debian</a>
+<li><a href="http://packages.ubuntu.com/python-nifti">PyNIfTI versions in Ubuntu</a>
+</ul>
+<p>Binary packages for some additional Debian and (K)Ubuntu versions are also
+available. Please visit
+<a href="index.php?sec=1&page=hanke/debian&lang=en">this page</a> to read
+about how you have to setup your system to retrieve the PyNIfTI package via
+your package manager and stay in sync with future releases.</p>
+
+<h4>Windows</h4>
+<p>A binary installer for a recent Python version is available from the
+<a href="http://sourceforge.net/projects/niftilib">Sourceforge project site</a>.
+
+
+<h4>Macintosh</h4>
+<p>Unfortunately, no binary packages are available. I have no access to such
+a machine at the moment. But it is possible to build PyNIfTI from source on
+Mac OS X (see below for more information).</p>
+
+
+
+<h2>4. Installation</h2>
+
+<h3>Compile from source: General instructions</h3>
+<p>PyNIfTI needs a few things to build and run properly:</p>
+<ul>
+  <li><a href="http://www.python.org">Python</a> 2.4 or greater</li>
+  <li><a href="http://numpy.scipy.org">NumPy</a></li>
+  <li><a href="http://www.swig.org">SWIG</a></li>
+  <li><a href="http://niftilib.sourceforge.net">NIfTI C libraries</a></li>
+</ul>
+<p>Make sure that the compiled nifticlibs and the corresponding headers are
+available to your compiler. If they are located in a custom directory, you
+might have to specify <code>--include-dirs</code> and
+<code>--library-dirs</code> options to the build command below.</p>
+<p>Once you have downloaded the sources, extract the tarball and enter the root
+directory of the extracted sources. A simple</p>
+<p><code>python setup.py build_ext</code></p>
+<p>should build the SWIG wrappers. If this has been done
+successfully, all you need to do is install the modules by invoking</p>
+<p><code>sudo python setup.py install</code></p>
+<p>If sudo is not configured (or even installed) you might have to use
+<code>su</code> instead.</p>
+<p>Now fire up Python and try importing the module to see if everything is
+fine. It should look similar to this:</p>
+<pre>
+Python 2.4.4 (#2, Oct 20 2006, 00:23:25)
+[GCC 4.1.2 20061015 (prerelease) (Debian 4.1.1-16.1)] on linux2
+Type "help", "copyright", "credits" or "license" for more information.
+>>> import nifti
+>>> 
+</pre>
+<h4>Windows</h4>
+<p>It should be pretty straightforward to compile PyNIfTI for win32. The most
+convenient way seems to be using the
+<a href="http://www.bloodshed.net/devcpp.html">Dev-Cpp IDE</a>
+and the DevPak of the nifticlibs. Have a look into the toplevel Makefile of the
+PyNIfTI source distribution for some hints.</p>
+
+
+<h4>MacOS X and MacPython</h4>
+<p>When you are comiling PyNIfTI on MacOS X and want to use it with MacPython,
+please make sure that the NIfTI C libraries are compiled as fat binaries
+(compiled for both <em>ppc</em> and <em>i386</em>). Otherwise
+PyNIfTI extensions will not compile.</p>
+<p>One can achieve this by adding both architectures to the <code>CFLAGS</code>
+definition in the toplevel Makefile of the NIfTI C library source code. Like
+this</p>
+<p><code>CFLAGS = $(ANSI_FLAGS) -arch ppc -arch i386</code></p>
+
+
+<h3>Binary packages</h3>
+<h4>GNU/Linux</h4>
+<p>If you have configured your system as described on
+<a href="index.php?sec=1&page=hanke/debian&lang=en">this page</a> all you
+have to do to install PyNIfTI is this:</p>
+<p><code>apt-get update</code><br /><code>apt-get install python-nifti</code></p>
+<p>This should pull all necessary dependencies. If it doesn't, it's a bug that
+should be reported.</p>
+<h4>Windows</h4>
+<p>As always: click <em>Next</em> as long as necessary and finally <em>Finish</em>.
+
+<h4>Troubleshooting</h4>
+<p>If you get an error when importing the <em>nifti</em> module in Python
+complaining about missing symbols your niftiio library contains references to
+some unresolved symbols. Try adding <em>znzlib</em> and <em>zlib</em> to the
+linker options the PyNIfTI <code>setup.py</code>, like this:</p>
+
+<p><code>libraries = [ 'niftiio', 'znz', 'z' ],</code></p>
+
+<h2>5. Things to know</h2>
+<p>When accessing NIfTI image data through NumPy arrays the order of the
+dimensions is reversed. If the <em>x, y, z, t</em> dimensions of a NIfTI image
+are 64, 64, 32, 456 (as for example reported by <em>nifti_tool</em>), the shape
+of the NumPy array (e.g. as returned by <code>NiftiImage.asarray()</code>) will
+be: 456, 32, 64, 64.</p>
+<p>This is done to be able to slice the data array much easier in the most
+common cases. For example, if you are interested in a certain volume of a timeseries
+it is much easier to write <code>data[2]</code> instead of
+<code>data[:,:,:,2]</code>, right?.
+
+<h2>6. Examples</h2>
+<p>The next sections contains some examples showing ways to use PyNIfTI to
+read and write imaging data from within Python to be able to process it with
+some random Python library.</p>
+<p>All examples assume that you have imported the PyNIfTI module by invoking:</p>
+<p><code>from nifti import *</code></p>
+
+<h3>a) Fileformat conversion</h3>
+<p>Open the MNI standard space template that is shipped with FSL. No filename
+extension is necessary as libniftiio determines it automatically:</p>
+
+<p><code>nim = NiftiImage('avg152T1_brain')</code></p>
+
+<p>The filename is available via the 'filename' attribute:</p>
+<p><code>print nim.filename</code></p>
+<p>yields 'avg152T1_brain.img'. This indicates an ANALYZE image. If you want to
+save this image as a single gzipped NIfTI file simply do:</p>
+<p><code>nim.save('mni.nii.gz')</code></p>
+<p>The filetype is determined from the filename. If you want to save to gzipped
+ANALYZE file pairs instead the following would be an alternative to calling the
+<code>save()</code> with a new filename.</p>
+<p><code>nim.filename = 'mni_analyze.img.gz'<br />nim.save()</code></p>
+<p>Please see the docstring of the <code>NiftiImage.setFilename()</code> method
+to learn how the filetypes are determined from the filenames.</p>
+
+<h3>b) NIfTI files from array data</h3>
+<p>The next code snipped demonstrates how to create a 4d NIfTI image containing
+gaussian noise. First we need to import the NumPy module</p>
+<p><code>import numpy</code></p>
+<p>Now generate the noise dataset. Let's generate noise for 100 volumes with 16
+slices and a 32x32 inplane matrix.</p>
+<p><code>noise = numpy.random.randn(100,16,32,32)</code></p>
+<p>Please notice the order in which the dimensions are specified:
+(t, z, y, x).</p>
+<p>The datatype of the array will most likely be <em>float64</em> -- which can
+be verified by invoking <code>noise.dtype</code>.</p>
+<p>Converting this dataset into a NIfTI image is done by invoking the
+NiftiImage constructor with the noise dataset as argument:</p>
+<p><code>nim = NiftiImage(noise)</code></p>
+
+<p>The relevant header information is extracted from the NumPy array. If you
+query the header information about the dimensionality of the image, it returns
+the desired values:</p>
+<p><code>print nim.header['dim']  # yields: [4, 32, 32, 16, 100, 0, 0, 0]</code></p>
+First value shows the number of dimensions in the datset: 4 (good, that's what
+we wanted). The following numbers are dataset size on the x, y, z, t, u, v, w
+axis (NIfTI files can handle up to 7 dimensions). Please notice, that the order
+of dimensions is now 'correct': We have 32x32 inplane resolution, 16 slices in z
+direction and 100 volumes.</p>
+<p>Also the datatype was set appropriately. The exprression:</p>
+<p><code>nim.header['datatype'] == nifticlib.NIFTI_TYPE_FLOAT64</code></p>
+<p>will evaluate to <em>True</em>.</p>
+<p>To save the noise file to disk, just call the <code>save()</code> method:</p>
+<p><code>nim.save('noise.nii.gz')</code></p>
+
+
+<h3>c) Select ROIs</h3>
+<p>Suppose you want to have the first ten volumes of the noise dataset we have
+just created in a separate file. First open the file (can be skipped if it is
+still open):</p>
+<p><code>nim = NiftiImage('noise.nii.gz')</code></p>
+<p>Now select the first ten volumes and store them to another file, while
+preserving as much header information as possible:</p>
+<p><code>
+nim2 = NiftiImage(nim.data[:10], nim.header)<br />
+nim2.save('part.hdr.gz')
+</code></p>
+<p>The NiftiImage constructor takes a dictionary with header information as an
+optional argument. Settings that are not determined by the array (e.g. size,
+datatype) are copied from the dictionary and stored to the new NIfTI image.</p>
+
+
+<h3>d) Linear detrending of timeseries (SciPy module is required for this
+example)</h3>
+<p>Let's load another 4d NIfTI file and perform a linear detrending, by fitting
+a straight line to the timeseries of each voxel and substract that fit from the
+data. Although this might sound complicated at first, thanks to the excellent
+SciPy module it is just a few lines of code.</p>
+<p><code>nim = NiftiImage('timeseries.nii')</code></p>
+<p>Depending on the datatype of the input image the detrending process might
+change the datatype from integer to float. As operations that change the
+(binary) size of the NIfTI image are not supported, we need to make a copy
+of the data and later create a new NIfTI image.</p>
+<p><code>data = nim.asarray()</code></p>
+<p>Now detrend the data along the time axis. Remember that the array has the
+time axis as its first dimension (in contrast to the NIfTI file where it is
+the 4th).</p>
+<p><code>
+from scipy import signal<br />
+data_detrended = signal.detrend( data, axis=0 )</code></p>
+<p>Finally, create a new NIfTI image using header information from the original
+source image.</p>
+<p><code>nim_detrended = NiftiImage( data_detrended, nim.header)</code></p>
+
+<h3>e) Make a quick plot of a voxels timeseries (Gnuplot module is required)</h3>
+<p>Plotting is essential to get a 'feeling' for the data. The
+<a href="http://gnuplot-py.sourceforge.net">python interface</a>
+to <a href="http://www.gnuplot.info">Gnuplot</a> makes it really easy to plot
+something (e.g. when running Python
+interactively via <a href="http://ipython.scipy.org">IPython</a>). Please
+note, that there are many other possibilities for plotting. Some examples are:
+using <a href="http://www.r-project.org">R</a> via
+<a href="http://rpy.sourceforge.net">RPy</a> or Matlab-style plotting via
+<a href="http://matplotlib.sourceforge.net/">matplotlib</a>.</p>
+<p>However, using Gnuplot is really easy. First import the Gnuplot module
+and create the interface object.</p>
+<p><code>
+from Gnuplot import Gnuplot<br />
+gp = Gnuplot()</code></p>
+<p>We want the timeseries as a line plot and not just the datapoints, so
+let's talk with Gnuplot.</p>
+<p><code>gp('set data style lines')</code></p>
+<p>Now load a 4d NIfTI image</p>
+<p><code>nim = NiftiImage('perfect_subject.nii.gz')</code></p>
+<p>and finally plot the timeseries of voxel (x=20, y=30, z=12):</p>
+<p><code>gp.plot(nim.data[:,12,30,20])</code></p>
+<p>A Gnuplot window showing the timeseries should popup now (<a href="http://apsy.gse.uni-magdeburg.de/main/pics/hanke/pynifti/gnuplot_ts.png">screenshot</a>). Please read the
+Gnuplot Manual to learn what it can do -- and it can do a lot more than just
+simple line plots (have a look at <a href="http://gnuplot.sourceforge.net/demo_4.3/index.html">this</a>
+page if you are interested).</p>
+
+<h3>f) Show a slice of a 3d volume (Matplotlib module is required)</h3>
+<p>This example demonstrates howto use the Matlab-style plotting of <a href="matplotlib.sourceforge.net">Matplotlib</a> to view a slice from a 3d volume.</p>
+<p>This time I assume that a 3d nifti file is already opened and available in the <code>nim3d</code> object. At first we need to load the necessary Python module.</p>
+<p><code>from pylab import *</code></p>
+<p>If everything went fine, we can now view a slice (x,y):</p>
+<p><code>imshow(nim3d.data[200], interpolation='nearest', cmap=cm.gray)<br />
+show()</code></p>
+<p>It is necessary to call the <code>show()</code> function one time after importing pylab to actually see the image when running Python interactively (<a href="http://apsy.gse.uni-magdeburg.de/main/pics/hanke/pynifti/matplotlib_xyslice.png">screenshot</a>).</p><p>When you want to have a look at a yz-slice, NumPy array magic comes into play.</p>
+<p><code>imshow(nim3d.data[::-1,:,100], interpolation='nearest', cmap=cm.gray)</code></p>
+<p>The <code>::-1</code> notation causes the z-axis to be flipped in the images. This makes a much nicer <a href="http://apsy.gse.uni-magdeburg.de/main/pics/hanke/pynifti/matplotlib_yzslice.png">screenshot</a>, because the used example volume has the z-axis originally oriented upsidedown.</p>
+
+<h3>g) Compute and display peristimulus signal timecourse of multiple conditions with <em>pynifti_pst</em> and <em>FSLView</em></h3>
+<p>Sometimes one wants to look at the signal timecourse of some voxel after a certain stimulation onset. An easy way would be to have some fMRI data viewer that displays a statistical map and one could click on some activated voxel and the peristimulus signal timecourse of some condition in that voxel would be displayed.</p>
+<p>This can easily be done by using <em>pynifti_pst</em> and <em>FSLView</em>.</p>
+<p><em>pynifti_pst</em> comes with a manpage that explains all options and arguments. Basically <em>pynifti_pst</em> need a 4d image (e.g. an fMRI timeseries; possibly preprocessed/filtered) and some stimulus onset information. This information can either be given directly on the command line or is read from files. Additionally one can specify onsets as volume numbers or as onset times.</p>
+<p><em>pynifti_pst</em> understands the FSL custom EV file format so one can easily use those files as input.</p>
+<p>An example call could look like this:</p>
+<p><code>pynifti_pst --times --nvols 5 -p uf92.feat/filtered_func_data.nii.gz pst_cond_a.nii.gz uf92.feat/custom_timing_files/ev1.txt uf92.feat/custom_timing_files/ev2.txt</code></p>
+<p>This computes a peristimulus timeseries using the preprocessed fMRI from a FEAT output directory
+and two custom EV files that both together make up condition A. <code>--times</code> indicates that
+the EV files list onset times (not volume ids) and <code>--nvols</code> requests the mean peristimulus
+timecourse for 4 volumes after stimulus onset (5 including onset). <code>-p</code> recodes the
+peristimulus timeseries into percent signalchange, where the onset is always zero and any following
+value is the signal change with respect to the onset volume.</p>
+<p>This call produces a simple 4d NIfTI image that can be loaded into FSLView as any other timeseries. The following call can be used to display an FSL zmap from the above results path on top of some anatomy. Additionally the peristimulus timeseries of two conditions are loaded. <a href="http://apsy.gse.uni-magdeburg.de/main/pics/hanke/pynifti/fslview_pst.png">This screenshot</a> shows how it could look like. One of the nice features of FSLView is that its timeseries window can remember selected curves, which can be useful to compare signal timecourses from different voxels (blue and green line in the screenshot).</p>
+<p><code>fslview pst_cond_a.nii.gz pst_cond_b.nii.gz uf92_ana.nii.gz uf92.feat/stats/zstat1.nii.gz -b 3,5</code></p>
+
+<h2>History</h2>
+<p>The full changelog is <a href="Changelog">here</a>.</p>
+</body>
+</html>

Added: trunk/scipy/io/nifti/TODO
===================================================================
--- trunk/scipy/io/nifti/TODO	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/TODO	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,55 @@
+
+Yarik's wishes sorted - please remove or fulfill:
+-------------------------------------------------
+
+- as it is now: getHeader returns a header dictionary which is not
+   binded to the object. so direct manipulation to it such as
+
+   inNifti.header['bla']='bleh'
+   are lost and the only way to introduce a change to the field seems
+   to operate as
+   h = inNifti.header
+   h['bla'] = 'bleh'
+   inNifti.header = h
+   which is at least should be documented in capital letters! ;-)
+
+   Proposed solution:
+
+   introduce private __header, which would result in singleton behavior
+   of getHeader -- on first call it does all the querying from
+   self.__nimg and nhdr2dict,
+   but on consequtive calls - simply return __header.
+
+   may be nhdr (result of _nim2nhdr) should be also stored as __nhdr
+   to avoid consequtive invocations of a costly function
+
+    Then updateHeader should
+    * allow not specified hdrdict (default to None)
+    * not query __nimg if __header/__nhdr is not None
+      and simply use nhdr=self.__nhdr
+    * if hdrdict=None (ie not specified) make a copy
+      hdrdict == self.__header.copy(), so del commands
+      dont' do evil things, and proceed further ;-)
+
+   save method also should invoke updateHeader I guess so that if
+   there were any changes to the header dictionary, they get saved..
+
+   Michael's comment:
+   ------------------
+      Although this would be nice to have, I really think it could be the
+      source of some ugly bugs. Maintaining a separate copy of the header means
+      that PyNifit has to keep track of all possible dependencies between the
+      image properties by itself, instead of relying on the nifticlib to do.
+      this. I'd prefer to implement as much accessor methods or properties as
+      necessary to make calls to the header property superflous.
+
+      BTW: The same behavior is true for the qform and sform properties
+      (including their inverse versions).
+
+
+Possibly wrong/incorrect/wontfix suggestions:
+---------------------------------------------
+
+- [gs]etVoxDims should work with all dimensions up to 7 as they are
+   defined in nifti1_io.h
+   hanke: maybe, but that would mix different units, which I'd rather not do.

Added: trunk/scipy/io/nifti/bin/pynifti_pst
===================================================================
--- trunk/scipy/io/nifti/bin/pynifti_pst	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/bin/pynifti_pst	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,251 @@
+#!/usr/bin/python
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+#
+#    Copyright (C) 2007 by
+#    Michael Hanke <michael.hanke at gmail.com>
+#
+#    This is free software; you can redistribute it and/or
+#    modify it under the terms of the MIT License.
+#
+#    This package is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+#    file that comes with this package for more details.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+
+
+import os, sys
+from optparse import OptionParser
+import nifti.utils
+import numpy as np
+
+def loadEVFile( filename ):
+    try:
+        file = open( filename )
+    except IOError:
+        raise ValueError, "Cannot open EV file '%s'" % filename
+
+    onsets = []
+
+    # split each line and ignore empty lines
+    for line in file:
+        e = line.split()
+        if len(e):
+            onsets.append( float( e[0] ) )
+
+    return onsets
+
+
+def checkCmdLine(args, opts, min_args):
+    if len(args) < min_args:
+        raise RuntimeError, \
+            "%s: error: Incorrect number of arguments " \
+            "(supplied %i, needed %i)." % (sys.argv[0], len(args), min_args)
+
+
+def main():
+    parser = OptionParser( \
+        usage="%prog [options] <4dimage> <outfile> <vol_id | filename> [...]", \
+        version="%prog 0.20070424", description="""\
+%prog computes the peristimulus signal timecourse for all voxels in a volume at
+once. Stimulus onsets can be specified as volume numbers or times (will be
+converted into volume numbers using a supplied repetition time). Onsets can be
+specified directly on the command line, but can also be read from (multiple)
+files. Such file are assumed to list one onset per line (as the first value).
+Empty lines are ignored. This enables %prog to use e.g. FSL's custom EV files.
+If several files are specified, the read onsets are combined to a single onset
+vector.
+%prog writes a 4d timeseries image as output. This image can e.g. be loaded into
+FSLView to look at each voxels signal timecourse in a certain condition by
+simply clicking on it.
+""" )
+
+    # define options
+    parser.add_option('--verbose', action='store_true', dest='verbose',
+                      default=False, help='print status messages')
+    parser.add_option('-n', '--nvols', action='store', dest='nvols',
+                      default=10, type="int", help="""\
+Set the length of the computed peristimulus signal timecourse (in volumes).
+Default: 10
+""" )
+    parser.add_option('-t', '--times', action='store_true', dest='times',
+                      default=False, help="""\
+If supplied, the read values are treated as onset times and will be converted
+to volume numbers. For each onset the volume that is closest in time will be
+selected. Volumes are assumed to be recorded exactly (and completely) after
+tr/2, e.g. if 'tr' is 2 secs the first volume is recorded at exactly one
+second. Please see the --tr and --offset options to learn how to adjust the
+conversion. """ )
+    parser.add_option('--tr', action='store', dest='tr', default=None,
+                      type="float", help="""\
+Repetition time of the 4d image (temporal difference of two successive
+volumes). This can be used to override the setting in the 4d image. The
+repetition time is necessary to convert onset times into volume numbers.
+If the '--times' option is not set this value has no effect. Please note
+that repetitions time and the supplied onsets have to be in the same unit.
+
+Please note, that if --times is given the tr has to be specified in the
+same unit as the read onset times.
+""" )
+    parser.add_option('--offset', dest='offset', action='store', default=0.0,
+                      type='float', help="""\
+Constant offset applied to the onsets times when converting them to volume
+numbers. Without setting '--times' this option has no effect'.
+""" )
+    parser.add_option('-p', '--percsigchg', action='store_true',
+                      dest='perc_sig_chg', default=False, help="""\
+Convert the computed timecourse to percent signal change relative to the first
+(onset) volume. This might not be meaningful when --operation is set to
+something different than 'mean'. Please note, that the shape of the computes
+timeseries heavily depends on the first average volume. It might be more
+meaningful to use a real baseline condition as origin. However, this is not
+supported yet. Default: False
+""" )
+    parser.add_option('--printvoxel', action='store', dest='printvoxel',
+                      default=None, help="""\
+Print the peristimulus timeseries of a single voxel for all onsets separately.
+This will print a matrix (onsets x time), where the number of columns is
+identical to the value of --nvols and the number of rows corresponds to the
+number of specified onsets. (e.g. 'z,y,x')
+""" )
+    parser.add_option('--operation', action='store', dest='operation',
+                      default='mean', help="""\
+Choose the math operation that is performed to compute the peristimulus
+timeseries. By default this is the mean across all stimulations ('mean').
+Other possibilities are the standard deviation ('std') and standard error
+('sde').
+""" )
+    # parse options
+    (opts, args) = parser.parse_args() # read sys.argv[1:] by default
+
+    try:
+        checkCmdLine( args, opts, 3 )
+    except:
+        print sys.exc_info()[1]
+        sys.exit( 1 )
+
+    if opts.verbose:
+        print "Read 4d image '%s'" % args[0]
+    nimg = nifti.NiftiImage( args[0] )
+
+    if not nimg.timepoints > 1:
+        print "%s: error: Need 4d image as input. " \
+              "Supplied image only has one volume." \
+                % sys.argv[0]
+        sys.exit( 1 )
+
+    # determine the requested function for timeseries calculation
+    if opts.operation == 'mean':
+        pst_fx = np.mean
+    elif opts.operation == 'std' or opts.operation == 'sde':
+        pst_fx = np.std
+    else:
+        print "'%s' is not a supported operation." % opts.operation
+        sys.exit(1)
+ 
+    outfilename = args[1]
+    if opts.verbose:
+        print "Output will be written to '%s'" % outfilename
+
+    if opts.tr:
+        tr = opts.tr
+        if opts.verbose:
+            print "Using provide repetition time: %f" % tr
+    else:
+        tr = nimg.rtime
+        if opts.verbose:
+            print "Using repetition from 4d image: %f" % tr
+
+    onsets = []
+
+    for src in args[2:]:
+        if os.path.isfile( src ):
+            if opts.verbose:
+                print "Reading values from '%s'" % src
+            onsets += loadEVFile( src )
+        else:
+            try:
+                onsets.append( float( src ) )
+            except ValueError:
+                print "%s: error: '%s' cannot be converted into a " \
+                      "floating-point value" % (sys.argv[0], src)
+                sys.exit( 1 )
+
+    if opts.times:
+        if opts.verbose:
+            print "Convert supplied onset times into volumes " \
+                  "(tr: %f, offset: %f)" % (tr, opts.offset)
+
+        onsetvols = nifti.utils.time2vol( onsets,
+                                          tr,
+                                          opts.offset,
+                                          decimals = 0 ).astype('int')
+    else:
+        if opts.verbose:
+            print "Verify onset volume numbers"
+        onsetvols = [ int(i) for i in onsets ]
+
+    if opts.verbose:
+        print "Selected volumes (index starts at zero!):\n" + ', '.join([ str(o) for o in onsetvols])
+
+    if opts.verbose:
+        print "Compute mean peristimulus signal timecourse " \
+              "(length: %i volumes)" % opts.nvols
+
+    if opts.printvoxel:
+        # get timeseries for each onset
+        pst = nifti.utils.getPeristimulusTimeseries( nimg,
+                                                     onsetvols,
+                                                     opts.nvols,
+                                                     tuple ).copy()
+        # make matrix ( onset x time )
+        voxel_pst = eval('pst[:,:,' + opts.printvoxel +'].T')
+
+        for onset in voxel_pst:
+            for t in onset:
+                print t,
+            print ''
+
+
+    if opts.verbose:
+        print "Compute peristimulus timeseries"
+
+    pst = nifti.utils.getPeristimulusTimeseries( nimg,
+                                                 onsetvols,
+                                                 opts.nvols,
+                                                 pst_fx ).copy()
+
+    # divide by srqt of number of stimulations if standard error is requested
+    if opts.operation == 'sde':
+        pst /= np.sqrt( len(onsetvols) )
+
+    if opts.perc_sig_chg:
+        if opts.verbose:
+            print "Convert to percent signal change"
+        baseline = nifti.utils.getPeristimulusTimeseries( nimg,
+                                                          onsetvols,
+                                                          1,
+                                                          np.mean )[0].copy()
+
+        if opts.operation == 'mean':
+            pst -= baseline
+
+        # only divide if baseline is different from zero
+        pst[:,baseline != 0] /= baseline[baseline != 0]
+        pst *= 100.0
+
+    if opts.verbose:
+        print "Write output"
+    onimg = nifti.NiftiImage(pst,nimg.header)
+    onimg.save( outfilename )
+
+    if opts.verbose:
+        print "Done"
+
+
+if __name__ == "__main__":
+    main()
+
+
+


Property changes on: trunk/scipy/io/nifti/bin/pynifti_pst
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/scipy/io/nifti/man/pynifti_pst.1
===================================================================
--- trunk/scipy/io/nifti/man/pynifti_pst.1	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/man/pynifti_pst.1	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,82 @@
+.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.36.
+.TH PYNIFTI_PST "1" "September 2007" "pynifti_pst 0.20070424" "User Commands"
+.SH NAME
+pynifti_pst \- compute peristimulus timeseries of fMRI data
+.SH DESCRIPTION
+usage: pynifti_pst [options] <4dimage> <outfile> <vol_id | filename> [...]
+.PP
+pynifti_pst computes the peristimulus signal timecourse for all voxels in a
+volume at once. Stimulus onsets can be specified as volume numbers or times
+(will be converted into volume numbers using a supplied repetition time).
+Onsets can be specified directly on the command line, but can also be read
+from (multiple) files. Such file are assumed to list one onset per line (as
+the first value). Empty lines are ignored. This enables pynifti_pst to use
+e.g. FSL's custom EV files. If several files are specified, the read onsets
+are combined to a single onset vector. pynifti_pst writes a 4d timeseries
+image as output. This image can e.g. be loaded into FSLView to look at each
+voxels signal timecourse in a certain condition by simply clicking on it.
+.SS "options:"
+.TP
+\fB\-\-version\fR
+show program's version number and exit
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+show this help message and exit
+.TP
+\fB\-\-verbose\fR
+print status messages
+.TP
+\fB\-n\fR NVOLS, \fB\-\-nvols\fR=\fINVOLS\fR
+Set the length of the computed peristimulus signal
+timecourse (in volumes). Default: 10
+.TP
+\fB\-t\fR, \fB\-\-times\fR
+If supplied, the read values are treated as onset
+times and will be converted to volume numbers. For
+each onset the volume that is closest in time will be
+selected. Volumes are assumed to be recorded exactly
+(and completely) after tr/2, e.g. if 'tr' is 2 secs
+the first volume is recorded at exactly one second.
+Please see the \fB\-\-tr\fR and \fB\-\-offset\fR options to learn how
+to adjust the conversion.
+.TP
+\fB\-\-tr\fR=\fITR\fR
+Repetition time of the 4d image (temporal difference
+of two successive volumes). This can be used to
+override the setting in the 4d image. The repetition
+time is necessary to convert onset times into volume
+numbers. If the '\-\-times' option is not set this value
+has no effect. Please note that repetitions time and
+the supplied onsets have to be in the same unit.
+Please note, that if \fB\-\-times\fR is given the tr has to be
+specified in the same unit as the read onset times.
+.TP
+\fB\-\-offset\fR=\fIOFFSET\fR
+Constant offset applied to the onsets times when
+converting them to volume numbers. Without setting '\-\-
+times' this option has no effect'.
+.TP
+\fB\-p\fR, \fB\-\-percsigchg\fR
+Convert the computed timecourse to percent signal
+change relative to the first (onset) volume. This
+might not be meaningful when \fB\-\-operation\fR is set to
+something different than 'mean'. Please note, that the
+shape of the computes timeseries heavily depends on
+the first average volume. It might be more meaningful
+to use a real baseline condition as origin. However,
+this is not supported yet. Default: False
+.TP
+\fB\-\-printvoxel\fR=\fIPRINTVOXEL\fR
+Print the peristimulus timeseries of a single voxel
+for all onsets separately. This will print a matrix
+(onsets x time), where the number of columns is
+identical to the value of \fB\-\-nvols\fR and the number of
+rows corresponds to the number of specified onsets.
+(e.g. 'z,y,x')
+.TP
+\fB\-\-operation\fR=\fIOPERATION\fR
+Choose the math operation that is performed to compute
+the peristimulus timeseries. By default this is the
+mean across all stimulations ('mean'). Other
+possibilities are the standard deviation ('std') and
+standard error ('sde').

Added: trunk/scipy/io/nifti/nifti/__init__.py
===================================================================
--- trunk/scipy/io/nifti/nifti/__init__.py	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/nifti/__init__.py	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,18 @@
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+#
+#    Import helper for PyNifti
+#
+#    Copyright (C) 2006-2007 by
+#    Michael Hanke <michael.hanke at gmail.com>
+#
+#    This is free software; you can redistribute it and/or
+#    modify it under the terms of the MIT License.
+#
+#    This package is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+#    file that comes with this package for more details.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+
+from niftiimage import *

Added: trunk/scipy/io/nifti/nifti/nifticlib.i
===================================================================
--- trunk/scipy/io/nifti/nifti/nifticlib.i	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/nifti/nifticlib.i	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,227 @@
+/*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+ *
+ *    SWIG interface to wrap the low-level NIfTI IO libs for Python
+ *
+ *    Copyright (C) 2006-2007 by
+ *    Michael Hanke <michael.hanke at gmail.com>
+ *
+ *   This is free software; you can redistribute it and/or
+ *   modify it under the terms of the MIT License.
+ *
+ *   This package is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+ *   file that comes with this package for more details.
+ *
+ *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+ */
+
+%define DOCSTRING
+"
+This module provide the all functions and datatypes implemented
+in the low-level C library libniftiio and the NIfTI-1 header.
+
+At the moment no additional documentation is provided here. Please see the
+intensively documented source code of the nifti C libs to learn about the
+capabilities of the library.
+"
+%enddef
+
+%module (package="nifti", docstring=DOCSTRING) nifticlib
+%{
+#include <nifti1_io.h>
+#include <znzlib.h>
+
+#include <Python.h>
+#include <numpy/arrayobject.h>
+
+/* low tech wrapper function to set values of a mat44 struct */
+static void set_mat44(mat44* m,
+                      float a1, float a2, float a3, float a4,
+                      float b1, float b2, float b3, float b4,
+                      float c1, float c2, float c3, float c4,
+                      float d1, float d2, float d3, float d4 )
+{
+    m->m[0][0] = a1; m->m[0][1] = a2; m->m[0][2] = a3; m->m[0][3] = a4;
+    m->m[1][0] = b1; m->m[1][1] = b2; m->m[1][2] = b3; m->m[1][3] = b4;
+    m->m[2][0] = c1; m->m[2][1] = c2; m->m[2][2] = c3; m->m[2][3] = c4;
+    m->m[3][0] = d1; m->m[3][1] = d2; m->m[3][2] = d3; m->m[3][3] = d4;
+}
+
+/* convert mat44 struct into a numpy float array */
+static PyObject* mat442array(mat44 _mat)
+{
+    int dims[2] = {4,4};
+
+    PyObject* array = 0;
+    array = PyArray_FromDims ( 2, dims, NPY_FLOAT );
+
+    /* mat44 subscription is [row][column] */
+    PyArrayObject* a = (PyArrayObject*) array;
+
+    float* data = (float *)a->data;
+
+    int i,j;
+
+    for (i = 0; i<4; i+=1)
+    {
+        for (j = 0; j<4; j+=1)
+        {
+            data[4*i+j] = _mat.m[i][j];
+        }
+    }
+
+    return PyArray_Return ( (PyArrayObject*) array  );
+}
+
+static PyObject* wrapImageDataWithArray(nifti_image* _img)
+{
+    if (!_img)
+    {
+        PyErr_SetString(PyExc_RuntimeError, "Zero pointer passed instead of valid nifti_image struct.");
+        return(NULL);
+    }
+
+    int array_type=0;
+
+    /* translate nifti datatypes to numpy datatypes */
+    switch(_img->datatype)
+    {
+      case NIFTI_TYPE_UINT8:
+          array_type = NPY_UBYTE;
+          break;
+      case NIFTI_TYPE_INT8:
+          array_type = NPY_BYTE;
+          break;
+      case NIFTI_TYPE_UINT16:
+          array_type = NPY_USHORT;
+          break;
+      case NIFTI_TYPE_INT16:
+          array_type = NPY_SHORT;
+          break;
+      case NIFTI_TYPE_UINT32:
+          array_type = NPY_UINT;
+          break;
+      case NIFTI_TYPE_INT32:
+          array_type = NPY_INT;
+          break;
+      case NIFTI_TYPE_UINT64:
+      case NIFTI_TYPE_INT64:
+          array_type = NPY_LONG;
+          break;
+      case NIFTI_TYPE_FLOAT32:
+          array_type = NPY_FLOAT;
+          break;
+      case NIFTI_TYPE_FLOAT64:
+          array_type = NPY_DOUBLE;
+          break;
+      case NIFTI_TYPE_COMPLEX128:
+          array_type = NPY_CFLOAT;
+          break;
+      case NIFTI_TYPE_COMPLEX256:
+          array_type = NPY_CDOUBLE;
+          break;
+      default:
+          PyErr_SetString(PyExc_RuntimeError, "Unsupported datatype");
+          return(NULL);
+    }
+
+    /* array object */
+    PyObject* volarray = 0;
+
+    /* Get the number of dimensions from the niftifile and
+     * reverse the order for the conversion to a numpy array.
+     * Doing so will make users access to the data more convenient:
+     * a 3d volume from a 4d file can be index by a single number.
+     */
+    int ar_dim[7], ndims, k;
+    /* first item in dim array stores the number of dims */
+    ndims = (int) _img->dim[0];
+    /* reverse the order */
+    for (k=0; k<ndims; k++)
+    {
+        ar_dim[k] = (int) _img->dim[ndims-k];
+    }
+
+    /* create numpy array */
+    volarray = PyArray_FromDimsAndData ( ndims, ar_dim, array_type, ( char* ) _img->data );
+
+    return PyArray_Return ( (PyArrayObject*) volarray  );
+}
+
+int allocateImageMemory(nifti_image* _nim)
+{
+  if (_nim == NULL)
+  {
+    fprintf(stderr, "NULL pointer passed to allocateImageMemory()");
+    return(0);
+  }
+
+  if (_nim->data != NULL)
+  {
+    fprintf(stderr, "There seems to be allocated memory already (valid nim->data pointer found).");
+    return(0);
+  }
+
+  /* allocate memory */
+  _nim->data = (void*) calloc(1,nifti_get_volsize(_nim));
+
+  if (_nim->data == NULL)
+  {
+    fprintf(stderr, "Failed to allocate %d bytes for image data\n", (int)nifti_get_volsize(_nim));
+    return(0);
+  }
+
+  return(1);
+}
+
+%}
+
+
+%init
+%{
+    import_array();
+%}
+
+%include "typemaps.i"
+/* Need to put before nifti1_io.h to overwrite function prototype with this
+ * typemap. */
+void nifti_mat44_to_quatern( mat44 R ,
+                             float *OUTPUT, float *OUTPUT, float *OUTPUT,
+                             float *OUTPUT, float *OUTPUT, float *OUTPUT,
+                             float *OUTPUT, float *OUTPUT, float *OUTPUT, float *OUTPUT );
+
+void nifti_mat44_to_orientation( mat44 R , int *OUTPUT, int *OUTPUT, int *OUTPUT );
+
+
+%include znzlib.h
+%include nifti1.h
+%include nifti1_io.h
+
+static PyObject * wrapImageDataWithArray(nifti_image* _img);
+int allocateImageMemory(nifti_image* _nim);
+
+static PyObject* mat442array(mat44 _mat);
+static void set_mat44(mat44* m,
+                      float a1, float a2, float a3, float a4,
+                      float b1, float b2, float b3, float b4,
+                      float c1, float c2, float c3, float c4,
+                      float d1, float d2, float d3, float d4 );
+
+
+%include "cpointer.i"
+%pointer_functions(short, shortp);
+%pointer_functions(int, intp);
+%pointer_functions(unsigned int, uintp);
+%pointer_functions(float, floatp);
+%pointer_functions(double, doublep);
+%pointer_functions(char, charp);
+
+%include "carrays.i"
+%array_class(short, shortArray);
+%array_class(int, intArray);
+%array_class(unsigned int, uintArray);
+%array_class(float, floatArray);
+%array_class(double, doubleArray);
+
+

Added: trunk/scipy/io/nifti/nifti/nifticlib.py
===================================================================

Added: trunk/scipy/io/nifti/nifti/niftiimage.py
===================================================================
--- trunk/scipy/io/nifti/nifti/niftiimage.py	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/nifti/niftiimage.py	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,1244 @@
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+#
+#    Python interface to the NIfTI file format
+#
+#    Copyright (C) 2006-2007 by
+#    Michael Hanke <michael.hanke at gmail.com>
+#
+#    This is free software; you can redistribute it and/or
+#    modify it under the terms of the MIT License.
+#
+#    This package is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+#    file that comes with this package for more details.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+
+# the swig wrapper if the NIfTI C library
+import nifticlib
+import os
+import numpy
+
+
+class NiftiImage(object):
+    """Wrapper class for convenient access to NIfTI data.
+
+    The class can either load an image from file or convert a NumPy
+    array into a NIfTI file structure. Either way is automatically determined
+    by the type of the 'source' argument (string == filename, array == Numpy).
+
+    One can optionally specify whether the image data should be loaded into
+    memory when opening NIfTI data from files ('load'). When converting a NumPy
+    array one can optionally specify a dictionary with NIfTI header data as
+    available via the 'header' attribute.
+    """
+
+    filetypes = [ 'ANALYZE', 'NIFTI', 'NIFTI_PAIR', 'ANALYZE_GZ', 'NIFTI_GZ',
+                  'NIFTI_PAIR_GZ' ]
+
+    # map NumPy datatypes to NIfTI datatypes
+    numpy2nifti_dtype_map = { numpy.uint8: nifticlib.NIFTI_TYPE_UINT8,
+                              numpy.int8 : nifticlib.NIFTI_TYPE_INT8,
+                              numpy.uint16: nifticlib.NIFTI_TYPE_UINT16,
+                              numpy.int16 : nifticlib.NIFTI_TYPE_INT16,
+                              numpy.uint32: nifticlib.NIFTI_TYPE_UINT32,
+                              numpy.int32 : nifticlib.NIFTI_TYPE_INT32,
+                              numpy.uint64: nifticlib.NIFTI_TYPE_UINT64,
+                              numpy.int64 : nifticlib.NIFTI_TYPE_INT64,
+                              numpy.float32: nifticlib.NIFTI_TYPE_FLOAT32,
+                              numpy.float64: nifticlib.NIFTI_TYPE_FLOAT64,
+                              numpy.complex128: nifticlib.NIFTI_TYPE_COMPLEX128
+                            }
+
+
+    @staticmethod
+    def numpydtype2niftidtype(array):
+        """ Return the NIfTI datatype id for a corrsponding numpy array
+        datatype.
+        """
+        # get the real datatype from numpy type dictionary
+        dtype = numpy.typeDict[str(array.dtype)]
+
+        if not NiftiImage.numpy2nifti_dtype_map.has_key(dtype):
+            raise ValueError, "Unsupported datatype '%s'" % str(array.dtype)
+
+        return NiftiImage.numpy2nifti_dtype_map[dtype]
+
+
+    @staticmethod
+    def splitFilename(filename):
+        """ Split a NIfTI filename and returns a tuple of basename and
+        extension. If no valid NIfTI filename extension is found, the whole
+        string is returned as basename and the extension string will be empty.
+        """
+
+        parts = filename.split('.')
+
+        if parts[-1] == 'gz':
+            if not parts[-2] in [ 'nii', 'hdr', 'img' ]:
+                return filename, ''
+            else:
+                return '.'.join(parts[:-2]), '.'.join(parts[-2:])
+        else:
+            if not parts[-1] in [ 'nii', 'hdr', 'img' ]:
+                return filename, ''
+            else:
+                return '.'.join(parts[:-1]), parts[-1]
+
+
+    @staticmethod
+    def nhdr2dict(nhdr):
+        """ Convert a NIfTI header struct into a python dictionary.
+
+        While most elements of the header struct will be translated
+        1:1 some (e.g. sform matrix) are converted into more convenient
+        datatypes (i.e. 4x4 matrix instead of 16 separate values).
+        """
+        h = {}
+
+        # the following header elements are converted in a simple loop
+        # as they do not need special handling
+        auto_convert = [ 'session_error', 'extents', 'sizeof_hdr',
+                         'slice_duration', 'slice_start', 'xyzt_units',
+                         'cal_max', 'intent_p1', 'intent_p2', 'intent_p3',
+                         'intent_code', 'sform_code', 'cal_min', 'scl_slope',
+                         'slice_code', 'bitpix', 'descrip', 'glmin', 'dim_info',
+                         'glmax', 'data_type', 'aux_file', 'intent_name',
+                         'vox_offset', 'db_name', 'scl_inter', 'magic',
+                         'datatype', 'regular', 'slice_end', 'qform_code',
+                         'toffset' ]
+
+
+        # now just dump all attributes into a dict
+        for attr in auto_convert:
+            h[attr] = eval('nhdr.' + attr)
+
+        # handle a few special cases
+        # handle 'pixdim': carray -> list
+        pixdim = nifticlib.floatArray_frompointer(nhdr.pixdim)
+        h['pixdim'] = [ pixdim[i] for i in range(8) ]
+
+        # handle dim: carray -> list
+        dim = nifticlib.shortArray_frompointer(nhdr.dim)
+        h['dim'] = [ dim[i] for i in range(8) ]
+
+        # handle sform: carrays -> (4x4) numpy array
+        srow_x = nifticlib.floatArray_frompointer( nhdr.srow_x )
+        srow_y = nifticlib.floatArray_frompointer( nhdr.srow_y )
+        srow_z = nifticlib.floatArray_frompointer( nhdr.srow_z )
+
+        h['sform'] = numpy.array( [ [ srow_x[i] for i in range(4) ],
+                                    [ srow_y[i] for i in range(4) ],
+                                    [ srow_z[i] for i in range(4) ],
+                                    [ 0.0, 0.0, 0.0, 1.0 ] ] )
+
+        # handle qform stuff: 3 numbers -> list
+        h['quatern'] = [ nhdr.quatern_b, nhdr.quatern_c, nhdr.quatern_d ]
+        h['qoffset'] = [ nhdr.qoffset_x, nhdr.qoffset_y, nhdr.qoffset_z ]
+
+        return h
+
+
+    @staticmethod
+    def updateNiftiHeaderFromDict(nhdr, hdrdict):
+        """ Update a NIfTI header struct with data from a dictionary.
+
+        The supplied dictionary might contain additonal data elements
+        that do not match any nifti header element. These are silently ignored.
+
+        Several checks are performed to ensure validity of the resulting
+        nifti header struct. If any check fails a ValueError exception will be
+        thrown. However, some tests are still missing.
+        """
+        # this function is still incomplete. add more checks
+
+        if hdrdict.has_key('data_type'):
+            if len(hdrdict['data_type']) > 9:
+                raise ValueError, \
+                      "Nifti header property 'data_type' must not be longer " \
+                      + "than 9 characters."
+            nhdr.data_type = hdrdict['data_type']
+        if hdrdict.has_key('db_name'):
+            if len(hdrdict['db_name']) > 79:
+                raise ValueError, "Nifti header property 'db_name' must " \
+                                  + "not be longer than 17 characters."
+            nhdr.db_name = hdrdict['db_name']
+
+        if hdrdict.has_key('extents'):
+            nhdr.extents = hdrdict['extents']
+        if hdrdict.has_key('session_error'):
+            nhdr.session_error = hdrdict['session_error']
+
+        if hdrdict.has_key('regular'):
+            if len(hdrdict['regular']) > 1:
+                raise ValueError, \
+                      "Nifti header property 'regular' has to be a single " \
+                      + "character."
+            nhdr.regular = hdrdict['regular']
+        if hdrdict.has_key('dim_info'):
+            if len(hdrdict['dim_info']) > 1:
+                raise ValueError, \
+                      "Nifti header property 'dim_info' has to be a " \
+                      + "single character."
+            nhdr.dim_info = hdrdict['dim_info']
+
+        if hdrdict.has_key('dim'):
+            dim = nifticlib.shortArray_frompointer(nhdr.dim)
+            for i in range(8): dim[i] = hdrdict['dim'][i]
+        if hdrdict.has_key('intent_p1'):
+            nhdr.intent_p1 = hdrdict['intent_p1']
+        if hdrdict.has_key('intent_p2'):
+            nhdr.intent_p2 = hdrdict['intent_p2']
+        if hdrdict.has_key('intent_p3'):
+            nhdr.intent_p3 = hdrdict['intent_p3']
+        if hdrdict.has_key('intent_code'):
+            nhdr.intent_code = hdrdict['intent_code']
+        if hdrdict.has_key('datatype'):
+            nhdr.datatype = hdrdict['datatype']
+        if hdrdict.has_key('bitpix'):
+            nhdr.bitpix = hdrdict['bitpix']
+        if hdrdict.has_key('slice_start'):
+            nhdr.slice_start = hdrdict['slice_start']
+        if hdrdict.has_key('pixdim'):
+            pixdim = nifticlib.floatArray_frompointer(nhdr.pixdim)
+            for i in range(8): pixdim[i] = hdrdict['pixdim'][i]
+        if hdrdict.has_key('vox_offset'):
+            nhdr.vox_offset = hdrdict['vox_offset']
+        if hdrdict.has_key('scl_slope'):
+            nhdr.scl_slope = hdrdict['scl_slope']
+        if hdrdict.has_key('scl_inter'):
+            nhdr.scl_inter = hdrdict['scl_inter']
+        if hdrdict.has_key('slice_end'):
+            nhdr.slice_end = hdrdict['slice_end']
+        if hdrdict.has_key('slice_code'):
+            nhdr.slice_code = hdrdict['slice_code']
+        if hdrdict.has_key('xyzt_units'):
+            nhdr.xyzt_units = hdrdict['xyzt_units']
+        if hdrdict.has_key('cal_max'):
+            nhdr.cal_max = hdrdict['cal_max']
+        if hdrdict.has_key('cal_min'):
+            nhdr.cal_min = hdrdict['cal_min']
+        if hdrdict.has_key('slice_duration'):
+            nhdr.slice_duration = hdrdict['slice_duration']
+        if hdrdict.has_key('toffset'):
+            nhdr.toffset = hdrdict['toffset']
+        if hdrdict.has_key('glmax'):
+            nhdr.glmax = hdrdict['glmax']
+        if hdrdict.has_key('glmin'):
+            nhdr.glmin = hdrdict['glmin']
+
+        if hdrdict.has_key('descrip'):
+            if len(hdrdict['descrip']) > 79:
+                raise ValueError, \
+                      "Nifti header property 'descrip' must not be longer " \
+                      + "than 79 characters."
+            nhdr.descrip = hdrdict['descrip']
+        if hdrdict.has_key('aux_file'):
+            if len(hdrdict['aux_file']) > 23:
+                raise ValueError, \
+                      "Nifti header property 'aux_file' must not be longer " \
+                      + "than 23 characters."
+            nhdr.aux_file = hdrdict['aux_file']
+
+        if hdrdict.has_key('qform_code'):
+            nhdr.qform_code = hdrdict['qform_code']
+
+        if hdrdict.has_key('sform_code'):
+            nhdr.sform_code = hdrdict['sform_code']
+
+        if hdrdict.has_key('quatern'):
+            if not len(hdrdict['quatern']) == 3:
+                raise ValueError, \
+                      "Nifti header property 'quatern' must be float 3-tuple."
+
+            nhdr.quatern_b = hdrdict['quatern'][0]
+            nhdr.quatern_c = hdrdict['quatern'][1]
+            nhdr.quatern_d = hdrdict['quatern'][2]
+
+        if hdrdict.has_key('qoffset'):
+            if not len(hdrdict['qoffset']) == 3:
+                raise ValueError, \
+                      "Nifti header property 'qoffset' must be float 3-tuple."
+
+            nhdr.qoffset_x = hdrdict['qoffset'][0]
+            nhdr.qoffset_y = hdrdict['qoffset'][1]
+            nhdr.qoffset_z = hdrdict['qoffset'][2]
+
+        if hdrdict.has_key('sform'):
+            if not hdrdict['sform'].shape == (4,4):
+                raise ValueError, \
+                      "Nifti header property 'sform' must be 4x4 matrix."
+
+            srow_x = nifticlib.floatArray_frompointer(nhdr.srow_x)
+            for i in range(4): srow_x[i] = hdrdict['sform'][0][i]
+            srow_y = nifticlib.floatArray_frompointer(nhdr.srow_y)
+            for i in range(4): srow_y[i] = hdrdict['sform'][1][i]
+            srow_z = nifticlib.floatArray_frompointer(nhdr.srow_z)
+            for i in range(4): srow_z[i] = hdrdict['sform'][2][i]
+
+        if hdrdict.has_key('intent_name'):
+            if len(hdrdict['intent_name']) > 15:
+                raise ValueError, \
+                      "Nifti header property 'intent_name' must not be " \
+                      + "longer than 15 characters."
+            nhdr.intent_name = hdrdict['intent_name']
+
+        if hdrdict.has_key('magic'):
+            if hdrdict['magic'] != 'ni1' and hdrdict['magic'] != 'n+1':
+                raise ValueError, \
+                      "Nifti header property 'magic' must be 'ni1' or 'n+1'."
+            nhdr.magic = hdrdict['magic']
+
+
+    def __init__(self, source, header = {}, load=False ):
+        """ Create a Niftifile object.
+
+        This method decides whether to load a nifti image from file or create
+        one from array data, depending on the datatype of 'source'. If source
+        is a string, it is assumed to be a filename and an attempt will be made
+        to open the corresponding NIfTI file. If 'load' is set to True the image
+        data will be loaded into memory.
+
+        If 'source' is a numpy array the array data will be used for the to be
+        created nifti image and a matching nifti header is generated. Additonal
+        header data might be supplied in a dictionary. However, dimensionality
+        and datatype are determined from the numpy array and not taken from
+        a header dictionary.
+
+        If an object of a different type is supplied as 'source' a ValueError
+        exception will be thrown.
+        """
+
+        self.__nimg = None
+
+        if type( source ) == numpy.ndarray:
+            self.__newFromArray( source, header )
+        elif type ( source ) == str:
+            self.__newFromFile( source, load )
+        else:
+            raise ValueError, \
+                  "Unsupported source type. Only NumPy arrays and filename " \
+                  + "string are supported."
+
+
+    def __del__(self):
+        """ Do all necessary cleanups by calling __close().
+        """
+        self.__close()
+
+
+    def __close(self):
+        """Close the file and free all unnecessary memory.
+        """
+        if self.__nimg:
+            nifticlib.nifti_image_free(self.__nimg)
+            self.__nimg = None
+
+
+    def __newFromArray(self, data, hdr = {}):
+        """ Create a nifti image struct from a numpy array and optional header
+        data.
+        """
+
+        # check array
+        if len(data.shape) > 7:
+            raise ValueError, \
+                  "NIfTI does not support data with more than 7 dimensions."
+
+        # create template nifti header struct
+        niptr = nifticlib.nifti_simple_init_nim()
+        nhdr = nifticlib.nifti_convert_nim2nhdr(niptr)
+
+        # intermediate cleanup
+        nifticlib.nifti_image_free(niptr)
+
+        # convert virgin nifti header to dict to merge properties
+        # with supplied information and array properties
+        hdic = NiftiImage.nhdr2dict(nhdr)
+
+        # copy data from supplied header dict
+        for k, v in hdr.iteritems():
+            hdic[k] = v
+
+        # finally set header data that is determined by the data array
+        # convert numpy to nifti datatype
+        hdic['datatype'] = self.numpydtype2niftidtype(data)
+
+        # make sure there are no zeros in the dim vector
+        # especially not in #4 as FSLView doesn't like that
+        hdic['dim'] = [ 1 for i in hdic['dim'] ]
+
+        # set number of dims
+        hdic['dim'][0] = len(data.shape)
+
+        # set size of each dim (and reverse the order to match nifti format
+        # requirements)
+        for i, s in enumerate(data.shape):
+            hdic['dim'][len(data.shape)-i] = s
+
+        # set magic field to mark as nifti file
+        hdic['magic'] = 'n+1'
+
+        # update nifti header with information from dict
+        NiftiImage.updateNiftiHeaderFromDict(nhdr, hdic)
+
+        # make clean table
+        self.__close()
+
+        # convert nifti header to nifti image struct
+        self.__nimg = nifticlib.nifti_convert_nhdr2nim(nhdr, 'pynifti_none')
+
+        if not self.__nimg:
+            raise RuntimeError, "Could not create nifti image structure."
+
+        # kill filename for nifti images from arrays
+        self.__nimg.fname = ''
+        self.__nimg.iname = ''
+
+        # allocate memory for image data
+        if not nifticlib.allocateImageMemory(self.__nimg):
+            raise RuntimeError, "Could not allocate memory for image data."
+
+        # assign data
+        self.data[:] = data[:]
+
+
+    def __newFromFile(self, filename, load=False):
+        """Open a NIfTI file.
+
+        If there is already an open file it is closed first. If 'load' is True
+        the image data is loaded into memory.
+        """
+        self.__close()
+        self.__nimg = nifticlib.nifti_image_read( filename, int(load) )
+
+        if not self.__nimg:
+            raise RuntimeError, "Error while opening nifti header."
+
+        if load:
+            self.load()
+
+
+    def save(self, filename=None, filetype = 'NIFTI'):
+        """Save the image.
+
+        If the image was created using array data (not loaded from a file) one
+        has to specify a filename.
+
+        Setting the filename also determines the filetype (NIfTI/ANALYZE).
+        Please see the documentation of the setFilename() method for some 
+        details on the 'filename' and 'filetype' argument.
+
+        Calling save() without a specified filename on a NiftiImage loaded
+        from a file, will overwrite the original file.
+
+        If not yet done already, the image data will be loaded into memory
+        before saving the file.
+
+        Warning: There will be no exception if writing fails for any reason,
+        as the underlying function nifti_write_hdr_img() from libniftiio does
+        not provide any feedback. Suggestions for improvements are appreciated.
+        """
+
+        # If image data is not yet loaded, do it now.
+        # It is important to do it already here, because nifti_image_load
+        # depends on the correct filename set in the nifti_image struct
+        # and this will be modified in this function!
+        if not self.__haveImageData():
+            self.load()
+
+        # set a default description if there is none
+        if not self.description:
+            self.description = 'Created with PyNIfTI'
+
+        # update header information
+        self.updateCalMinMax()
+
+        # saving for the first time?
+        if not self.filename or filename:
+            if not filename:
+                raise ValueError, \
+                      "When saving an image for the first time a filename " \
+                      + "has to be specified."
+
+            self.setFilename(filename, filetype)
+
+        # now save it
+        nifticlib.nifti_image_write_hdr_img(self.__nimg, 1, 'wb')
+        # yoh comment: unfortunately return value of nifti_image_write_hdr_img
+        # can't be used to track the successful completion of save
+        # raise IOError, 'An error occured while attempting to save the image
+        # file.'
+
+
+    def __haveImageData(self):
+        """Returns true if the image data was loaded into memory.
+        or False if not.
+
+        See: load(), unload()
+        """
+        self.__ensureNiftiImage()
+
+        if self.__nimg.data:
+            return True
+        else:
+            return False
+
+
+    def load(self):
+        """Load the image data into memory.
+
+        It is save to call this method several times.
+        """
+        self.__ensureNiftiImage()
+
+        if nifticlib.nifti_image_load( self.__nimg ) < 0:
+            raise RuntimeError, "Unable to load image data."
+
+
+    def unload(self):
+        """Unload image data and free allocated memory.
+        """
+        # if no filename is se, the data will be lost and cannot be recovered
+        if not self.filename:
+            raise RuntimeError, "No filename is set, unloading the data would " \
+                              + "loose it completely without a chance of recovery. "
+        self.__ensureNiftiImage()
+
+        nifticlib.nifti_image_unload(self.__nimg)
+
+
+    def getDataArray(self):
+        """ Calls asarray(False) to return the NIfTI image data wrapped into
+        a NumPy array.
+
+        Attention: The array shares the data with the NiftiImage object. Any
+        resize operation or datatype conversion will most likely result in a
+        fatal error. If you need to perform such things, get a copy
+        of the image data by using asarray(copy=True).
+
+        The 'data' property is an alternative way to access this function.
+        """
+        return self.asarray(False)
+
+
+    def asarray(self, copy = True):
+        """Convert the image data into a multidimensional array.
+
+        Attention: If copy == False (the default) the array only wraps
+        the image data. Any modification done to the array is also done
+        to the image data.
+
+        If copy is true the array contains a copy of the image data.
+
+        Changing the shape, size or data of a wrapping array is not supported
+        and will most likely result in a fatal error. If you want to data
+        anything else to the data but reading or simple value assignment
+        use a copy of the data by setting the copy flag. Later you can convert
+        the modified data array into a NIfTi file again.
+        """
+        self.__ensureNiftiImage()
+
+        if not self.__haveImageData():
+            self.load()
+
+        a = nifticlib.wrapImageDataWithArray(self.__nimg)
+
+        if copy:
+            return a.copy()
+        else:
+            return a
+
+
+    def getScaledData(self):
+        """ Returns a copy of the data array scaled by multiplying with the
+        slope and adding the intercept that is stored in the NIfTI header.
+        """
+        data = self.asarray(copy = True)
+
+        return data * self.slope + self.intercept
+
+
+    def __ensureNiftiImage(self):
+        """Check whether a NIfTI image is present.
+
+        Returns True if there is a nifti image file structure or False
+        otherwise. One can create a file structure by calling open().
+        """
+        if not self.__nimg:
+            raise RuntimeError, "There is no NIfTI image file structure."
+
+
+    def updateCalMinMax(self):
+        """ Update the image data maximum and minimum value in the
+        nifti header.
+        """
+        self.__nimg.cal_max = float(self.data.max())
+        self.__nimg.cal_min = float(self.data.min())
+
+
+    def getVoxDims(self):
+        """ Returns a 3-tuple a voxel dimensions/size in (x,y,z).
+
+        The 'voxdim' property is an alternative way to access this function.
+        """
+        return ( self.__nimg.dx, self.__nimg.dy, self.__nimg.dz )
+
+
+    def setVoxDims(self, value):
+        """ Set voxel dimensions/size.
+
+        This method takes a 3-tuple of floats as argument. The qform matrix
+        and its inverse will be recalculated automatically.
+
+        Besides reading it is also possible to set the voxel dimensions by
+        assigning to the 'voxdim' property.
+        """
+        if len(value) != 3:
+            raise ValueError, 'Requires 3-tuple.'
+
+        self.__nimg.dx = float(value[0])
+        self.__nimg.dy = float(value[1])
+        self.__nimg.dz = float(value[2])
+
+        self.updateQFormFromQuaternion()
+
+
+    def setPixDims(self, value):
+        """ Set the pixel dimensions.
+
+        The methods takes a sequence of up to 7 values (max. number of
+        dimensions supported by the NIfTI format.
+
+        The supplied sequence can be shorter than seven elements. In this case
+        only present values are assigned starting with the first dimension
+        (spatial: x). Calling setPixDims() with a length-3 sequence equals
+        calling setVoxDims().
+        """
+        if len(value) > 7:
+            raise ValueError, \
+                  'The Nifti format does not support more than 7 dimensions.'
+
+        pixdim = nifticlib.floatArray_frompointer( self.__nimg.pixdim )
+
+        for i in value:
+            pixdim[i+1] = float(i)
+
+
+    def getPixDims(self):
+        """ Returns the pixel dimensions on all 7 dimensions.
+
+        The function is similar to getVoxDims(), but instead of the 3d spatial
+        dimensions of a voxel it returns the dimensions of an image pixel on
+        all 7 dimensions supported by the NIfTI dataformat.
+        """
+        return tuple( 
+                    [ nifticlib.floatArray_frompointer(self.__nimg.pixdim)[i] 
+                      for i in range(1,8) ]
+                )
+
+
+    def getExtent(self):
+        """ Returns a tuple describing the shape (size in voxel/timepoints)
+        of the dataimage.
+
+        The order of dimensions is (x,y,z,t,u,v,w). If the image has less 
+        dimensions than 7 the return tuple will be shortened accordingly.
+
+        Please note that the order of dimensions is different from the tuple
+        returned by calling NiftiImage.data.shape!
+
+        See also getVolumeExtent() and getTimepoints().
+
+        The 'extent' property is an alternative way to access this function.
+        """
+        # wrap dim array in nifti image struct
+        dims_array = nifticlib.intArray_frompointer(self.__nimg.dim)
+        dims = [ dims_array[i] for i in range(8) ]
+
+        return tuple( dims[1:dims[0]+1] )
+
+
+    def getVolumeExtent(self):
+        """ Returns the size/shape of the volume(s) in the image as a tuple.
+
+        This method returns either a 3-tuple or 2-tuple or 1-tuple depending 
+        on the available dimensions in the image.
+
+        The order of dimensions in the tuple is (x [, y [, z ] ] ).
+
+        The 'volextent' property is an alternative way to access this function.
+        """
+
+        # it is save to do this even if self.extent is shorter than 4 items
+        return self.extent[:3]
+
+
+    def getTimepoints(self):
+        """ Returns the number of timepoints in the image.
+
+        In case of a 3d (or less dimension) image this method returns 1.
+
+        The 'timepoints' property is an alternative way to access this
+        function.
+        """
+
+        if len(self.extent) < 4:
+            return 1
+        else:
+            return self.extent[3]
+
+
+    def getRepetitionTime(self):
+        """ Returns the temporal distance between the volumes in a timeseries.
+
+        The 'rtime' property is an alternative way to access this function.
+        """
+        return self.__nimg.dt
+
+
+    def setRepetitionTime(self, value):
+        """ Set the repetition time of a nifti image (dt).
+        """
+        self.__nimg.dt = float(value)
+
+
+    def getHeader(self):
+        """ Returns the header data of the nifti image in a dictionary.
+
+        Note, that modifications done to this dictionary do not cause any 
+        modifications in the NIfTI image. Please use the updateHeader() method
+        to apply changes to the image.
+
+        The 'header' property is an alternative way to access this function. 
+        But please note that the 'header' property cannot be used like this:
+
+            nimg.header['something'] = 'new value'
+
+        Instead one has to get the header dictionary, modify and later reassign
+        it:
+
+            h = nimg.header
+            h['something'] = 'new value'
+            nimg.header = h
+        """
+        h = {}
+
+        # Convert nifti_image struct into nifti1 header struct.
+        # This get us all data that will actually make it into a
+        # NIfTI file.
+        nhdr = nifticlib.nifti_convert_nim2nhdr(self.__nimg)
+
+        return NiftiImage.nhdr2dict(nhdr)
+
+
+    def updateHeader(self, hdrdict):
+        """ Update NIfTI header information.
+
+        Updated header data is read from the supplied dictionary. One cannot
+        modify dimensionality and datatype of the image data. If such
+        information is present in the header dictionary it is removed before
+        the update. If resizing or datatype casting are required one has to 
+        convert the image data into a separate array 
+        ( NiftiImage.assarray(copy=True) ) and perform resize and data 
+        manipulations on this array. When finished, the array can be converted
+        into a nifti file by calling the NiftiImage constructor with the
+        modified array as 'source' and the nifti header of the original
+        NiftiImage object as 'header'.
+
+        It is save to call this method with and without loaded image data.
+
+        The actual update is done by NiftiImage.updateNiftiHeaderFromDict().
+
+        Besides reading it is also possible to set the header data by assigning
+        to the 'header' property. Please see the documentation of the 
+        getHeader() method for important information about the special usage
+        of the 'header' property.
+        """
+        # rebuild nifti header from current image struct
+        nhdr = nifticlib.nifti_convert_nim2nhdr(self.__nimg)
+
+        # remove settings from the hdrdict that are determined by
+        # the data set and must not be modified to preserve data integrity
+        if hdrdict.has_key('datatype'):
+            del hdrdict['datatype']
+        if hdrdict.has_key('dim'):
+            del hdrdict['dim']
+
+        # update the nifti header
+        NiftiImage.updateNiftiHeaderFromDict(nhdr, hdrdict)
+
+        # if no filename was set already (e.g. image from array) set a temp
+        # name now, as otherwise nifti_convert_nhdr2nim will fail
+        have_temp_filename = False
+        if not self.filename:
+            self.filename = 'pynifti_updateheader_temp_name'
+            have_temp_filename = True
+
+        # recreate nifti image struct
+        new_nimg = nifticlib.nifti_convert_nhdr2nim(nhdr, self.filename)
+        if not new_nimg:
+            raise RuntimeError, \
+                  "Could not recreate NIfTI image struct from updated header."
+
+        # replace old image struct by new one
+        # be careful with memory leak (still not checked whether successful)
+
+        # rescue data ptr
+        new_nimg.data = self.__nimg.data
+
+        # and remove it from old image struct
+        self.__nimg.data = None
+
+        # to be able to call the cleanup function without lossing the data
+        self.__close()
+
+        # assign the new image struct
+        self.__nimg = new_nimg
+
+        # reset filename if temp name was set
+        if have_temp_filename:
+            self.filename = ''
+
+
+    def setSlope(self, value):
+        """ Set the slope attribute in the NIfTI header.
+
+        Besides reading it is also possible to set the slope by assigning
+        to the 'slope' property.
+        """
+        self.__nimg.scl_slope = float(value)
+
+
+    def setIntercept(self, value):
+        """ Set the intercept attribute in the NIfTI header.
+
+        Besides reading it is also possible to set the intercept by assigning
+        to the 'intercept' property.
+        """
+        self.__nimg.scl_inter = float(value)
+
+
+    def setDescription(self, value):
+        """ Set the description element in the NIfTI header.
+
+        Descriptions must not be longer than 79 characters.
+
+        Besides reading it is also possible to set the description by assigning
+        to the 'description' property.
+        """
+        if len(value) > 79:
+            raise ValueError, \
+                  "The NIfTI format only supports descriptions shorter than " \
+                  + "80 chars."
+
+        self.__nimg.descrip = value
+
+
+    def getSForm(self):
+        """ Returns the sform matrix.
+
+        The 'sform' property is an alternative way to access this function.
+
+        Please note, that the returned SForm matrix is not bound to the
+        NiftiImage object. Therefore it cannot be successfully modified
+        in-place. Modifications to the SForm matrix can only be done by setting
+        a new SForm matrix either by calling setSForm() or by assigning it to
+        the sform attribute.
+        """
+        return nifticlib.mat442array(self.__nimg.sto_xyz)
+
+
+    def setSForm(self, m):
+        """ Sets the sform matrix.
+        The supplied value has to be a 4x4 matrix. The matrix elements will be
+        converted to floats. By definition the last row of the sform matrix has
+        to be (0,0,0,1). However, different values can be assigned, but will
+        not be stored when the niftifile is saved.
+
+        The inverse sform matrix will be automatically recalculated.
+
+        Besides reading it is also possible to set the sform matrix by
+        assigning to the 'sform' property.
+        """
+        if m.shape != (4,4):
+            raise ValueError, "SForm matrix has to be of size 4x4."
+
+        # make sure it is float
+        m = m.astype('float')
+
+        nifticlib.set_mat44( self.__nimg.sto_xyz,
+                         m[0,0], m[0,1], m[0,2], m[0,3],
+                         m[1,0], m[1,1], m[1,2], m[1,3],
+                         m[2,0], m[2,1], m[2,2], m[2,3],
+                         m[3,0], m[3,1], m[3,2], m[3,3] )
+
+        # recalculate inverse
+        self.__nimg.sto_ijk = \
+            nifticlib.nifti_mat44_inverse( self.__nimg.sto_xyz )
+
+
+    def getInverseSForm(self):
+        """ Returns the inverse sform matrix.
+
+        The 'sform_inv' property is an alternative way to access this function.
+
+        Please note, that the inverse SForm matrix cannot be modified in-place.
+        One needs to set a new SForm matrix instead. The corresponding inverse
+        matrix is then re-calculated automatically.
+        """
+        return nifticlib.mat442array(self.__nimg.sto_ijk)
+
+
+    def getQForm(self):
+        """ Returns the qform matrix.
+
+        The 'qform' property is an alternative way to access this function.
+
+        Please note, that the returned QForm matrix is not bound to the
+        NiftiImage object. Therefore it cannot be successfully modified
+        in-place. Modifications to the QForm matrix can only be done by setting
+        a new QForm matrix either by calling setSForm() or by assigning it to
+        the sform attribute.
+        """
+        return nifticlib.mat442array(self.__nimg.qto_xyz)
+
+
+    def getInverseQForm(self):
+        """ Returns the inverse qform matrix.
+
+        The 'qform_inv' property is an alternative way to access this function.
+
+        Please note, that the inverse QForm matrix cannot be modified in-place.
+        One needs to set a new QForm matrix instead. The corresponding inverse
+        matrix is then re-calculated automatically.
+        """
+        return nifticlib.mat442array(self.__nimg.qto_ijk)
+
+
+    def setQForm(self, m):
+        """ Sets the qform matrix.
+        The supplied value has to be a 4x4 matrix. The matrix will be converted
+        to float.
+
+        The inverse qform matrix and the quaternion representation will be
+        automatically recalculated.
+
+        Besides reading it is also possible to set the qform matrix by
+        assigning to the 'qform' property.
+        """
+        if m.shape != (4,4):
+            raise ValueError, "QForm matrix has to be of size 4x4."
+
+        # make sure it is float
+        m = m.astype('float')
+
+        nifticlib.set_mat44( self.__nimg.qto_xyz,
+                         m[0,0], m[0,1], m[0,2], m[0,3],
+                         m[1,0], m[1,1], m[1,2], m[1,3],
+                         m[2,0], m[2,1], m[2,2], m[2,3],
+                         m[3,0], m[3,1], m[3,2], m[3,3] )
+
+        # recalculate inverse
+        self.__nimg.qto_ijk = \
+            nifticlib.nifti_mat44_inverse( self.__nimg.qto_xyz )
+
+        # update quaternions
+        ( self.__nimg.quatern_b, self.__nimg.quatern_c, self.__nimg.quatern_d,
+          self.__nimg.qoffset_x, self.__nimg.qoffset_y, self.__nimg.qoffset_z,
+          self.__nimg.dx, self.__nimg.dy, self.__nimg.dz,
+          self.__nimg.qfac ) = \
+            nifticlib.nifti_mat44_to_quatern( self.__nimg.qto_xyz )
+
+
+    def updateQFormFromQuaternion(self):
+        """ Recalculates the qform matrix (and the inverse) from the quaternion
+        representation.
+        """
+        # recalculate qform
+        self.__nimg.qto_xyz = nifticlib.nifti_quatern_to_mat44 (
+          self.__nimg.quatern_b, self.__nimg.quatern_c, self.__nimg.quatern_d,
+          self.__nimg.qoffset_x, self.__nimg.qoffset_y, self.__nimg.qoffset_z,
+          self.__nimg.dx, self.__nimg.dy, self.__nimg.dz,
+          self.__nimg.qfac )
+
+
+        # recalculate inverse
+        self.__nimg.qto_ijk = \
+            nifticlib.nifti_mat44_inverse( self.__nimg.qto_xyz )
+
+
+    def setQuaternion(self, value):
+        """ Set Quaternion from 3-tuple (qb, qc, qd).
+
+        The qform matrix and its inverse are re-computed automatically.
+
+        Besides reading it is also possible to set the quaternion by assigning
+        to the 'quatern' property.
+        """
+        if len(value) != 3:
+            raise ValueError, 'Requires 3-tuple.'
+
+        self.__nimg.quatern_b = float(value[0])
+        self.__nimg.quatern_c = float(value[1])
+        self.__nimg.quatern_d = float(value[2])
+
+        self.updateQFormFromQuaternion()
+
+
+    def getQuaternion(self):
+        """ Returns a 3-tuple containing (qb, qc, qd).
+
+        The 'quatern' property is an alternative way to access this function.
+        """
+        return( ( self.__nimg.quatern_b, 
+                  self.__nimg.quatern_c, 
+                  self.__nimg.quatern_d ) )
+
+
+    def setQOffset(self, value):
+        """ Set QOffset from 3-tuple (qx, qy, qz).
+
+        The qform matrix and its inverse are re-computed automatically.
+
+        Besides reading it is also possible to set the qoffset by assigning
+        to the 'qoffset' property.
+        """
+        if len(value) != 3:
+            raise ValueError, 'Requires 3-tuple.'
+
+        self.__nimg.qoffset_x = float(value[0])
+        self.__nimg.qoffset_y = float(value[1])
+        self.__nimg.qoffset_z = float(value[2])
+
+        self.updateQFormFromQuaternion()
+
+
+    def getQOffset(self):
+        """ Returns a 3-tuple containing (qx, qy, qz).
+
+        The 'qoffset' property is an alternative way to access this function.
+        """
+        return( ( self.__nimg.qoffset_x,
+                  self.__nimg.qoffset_y,
+                  self.__nimg.qoffset_z ) )
+
+
+    def setQFac(self, value):
+        """ Set qfac.
+
+        The qform matrix and its inverse are re-computed automatically.
+
+        Besides reading it is also possible to set the qfac by assigning
+        to the 'qfac' property.
+        """
+        self.__nimg.qfac = float(value)
+        self.updateQFormFromQuaternion()
+
+
+    def getQOrientation(self, as_string = False):
+        """ Returns to orientation of the i,j and k axis as stored in the
+        qform matrix.
+
+        By default NIfTI orientation codes are returned, but if 'as_string' is
+        set to true a string representation ala 'Left-to-right' is returned
+        instead.
+        """
+        codes = nifticlib.nifti_mat44_to_orientation(self.__nimg.qto_xyz)
+        if as_string:
+            return [ nifticlib.nifti_orientation_string(i) for i in codes ]
+        else:
+            return codes
+
+
+    def getSOrientation(self, as_string = False):
+        """ Returns to orientation of the i,j and k axis as stored in the
+        sform matrix.
+
+        By default NIfTI orientation codes are returned, but if 'as_string' is
+        set to true a string representation ala 'Left-to-right' is returned
+        instead.
+        """
+        codes = nifticlib.nifti_mat44_to_orientation(self.__nimg.sto_xyz)
+        if as_string:
+            return [ nifticlib.nifti_orientation_string(i) for i in codes ]
+        else:
+            return codes
+
+
+    def getBoundingBox(self):
+        """ Get the bounding box of the image.
+
+        This functions returns a tuple of (min, max) tuples. It contains as
+        many tuples as image dimensions. The order of dimensions is identical
+        to that in the data array.
+
+        The 'bbox' property is an alternative way to access this function.
+        """
+        nz = self.data.squeeze().nonzero()
+
+        bbox = []
+
+        for dim in nz:
+            bbox.append( ( dim.min(), dim.max() ) )
+
+        return tuple(bbox)
+
+
+    def setFilename(self, filename, filetype = 'NIFTI'):
+        """ Set the filename for the NIfTI image.
+
+        Setting the filename also determines the filetype. If the filename
+        ends with '.nii' the type will be set to NIfTI single file. A '.hdr'
+        extension can be used for NIfTI file pairs. If the desired filetype
+        is ANALYZE the extension should be '.img'. However, one can use the
+        '.hdr' extension and force the filetype to ANALYZE by setting the
+        filetype argument to ANALYZE. Setting filetype if the filename
+        extension is '.nii' has no effect, the file will always be in NIFTI
+        format.
+
+        If the filename carries an additional '.gz' the resulting file(s) will
+        be compressed.
+
+        Uncompressed NIfTI single files are the default filetype that will be
+        used if the filename has no valid extension. The '.nii' extension is
+        appended automatically. The 'filetype' argument can be used to force a
+        certain filetype when no extension can be used to determine it. 
+        'filetype' can be one of the nifticlibs filtetypes or any of 'NIFTI',
+        'NIFTI_GZ', 'NIFTI_PAIR', 'NIFTI_PAIR_GZ', 'ANALYZE', 'ANALYZE_GZ'.
+
+        Setting the filename will cause the image data to be loaded into memory
+        if not yet done already. This has to be done, because without the
+        filename of the original image file there would be no access to the
+        image data anymore. As a side-effect a simple operation like setting a
+        filename may take a significant amount of time (e.g. for a large 4d
+        dataset).
+
+        By passing an empty string or none as filename one can reset the
+        filename and detach the NiftiImage object from any file on disk.
+
+        Examples:
+
+          Filename          Output of save()
+          ----------------------------------
+          exmpl.nii         exmpl.nii (NIfTI)
+          exmpl.hdr         exmpl.hdr, exmpl.img (NIfTI)
+          exmpl.img         exmpl.hdr, exmpl.img (ANALYZE)
+          exmpl             exmpl.nii (NIfTI)
+          exmpl.hdr.gz      exmpl.hdr.gz, exmpl.img.gz (NIfTI)
+
+        ! exmpl.gz          exmpl.gz.nii (uncompressed NIfTI)
+
+        Setting the filename is also possible by assigning to the 'filename'
+        property.
+        """
+        # If image data is not yet loaded, do it now.
+        # It is important to do it already here, because nifti_image_load
+        # depends on the correct filename set in the nifti_image struct
+        # and this will be modified in this function!
+        if not self.__haveImageData():
+            self.load()
+
+        # if no filename is given simply reset it to nothing
+        if not filename:
+            self.__nimg.fname = ''
+            self.__nimg.iname = ''
+            return
+
+        # separate basename and extension
+        base, ext = NiftiImage.splitFilename(filename)
+
+        # if no extension default to nifti single files
+        if ext == '': 
+            if filetype == 'NIFTI' \
+               or filetype == nifticlib.NIFTI_FTYPE_NIFTI1_1:
+                ext = 'nii'
+            elif filetype == 'NIFTI_PAIR' \
+                 or filetype == nifticlib.NIFTI_FTYPE_NIFTI1_2:
+                ext = 'hdr'
+            elif filetype == 'ANALYZE' \
+                 or filetype == nifticlib.NIFTI_FTYPE_ANALYZE:
+                ext = 'img'
+            elif filetype == 'NIFTI_GZ':
+                ext = 'nii.gz'
+            elif filetype == 'NIFTI_PAIR_GZ':
+                ext = 'hdr.gz'
+            elif filetype == 'ANALYZE_GZ':
+                ext = 'img.gz'
+            else:
+                raise RuntimeError, "Unhandled filetype."
+
+        # Determine the filetype and set header and image filename
+        # appropriately.
+
+        # nifti single files are easy
+        if ext == 'nii.gz' or ext == 'nii':
+            self.__nimg.fname = base + '.' + ext
+            self.__nimg.iname = base + '.' + ext
+            self.__nimg.nifti_type = nifticlib.NIFTI_FTYPE_NIFTI1_1
+        # uncompressed nifti file pairs
+        elif ext in [ 'hdr', 'img' ]:
+            self.__nimg.fname = base + '.hdr'
+            self.__nimg.iname = base + '.img'
+            if ext == 'hdr' and not filetype.startswith('ANALYZE'):
+                self.__nimg.nifti_type = nifticlib.NIFTI_FTYPE_NIFTI1_2
+            else:
+                self.__nimg.nifti_type = nifticlib.NIFTI_FTYPE_ANALYZE
+        # compressed file pairs
+        elif ext in [ 'hdr.gz', 'img.gz' ]:
+            self.__nimg.fname = base + '.hdr.gz'
+            self.__nimg.iname = base + '.img.gz'
+            if ext == 'hdr.gz' and not filetype.startswith('ANALYZE'):
+                self.__nimg.nifti_type = nifticlib.NIFTI_FTYPE_NIFTI1_2
+            else:
+                self.__nimg.nifti_type = nifticlib.NIFTI_FTYPE_ANALYZE
+        else:
+            raise RuntimeError, "Unhandled filetype."
+
+
+    def getFilename(self):
+        """ Returns the filename.
+
+        To be consistent with setFilename() the image filename is returned
+        for ANALYZE images while the header filename is returned for NIfTI
+        files.
+
+        The 'filename' property is an alternative way to access this function.
+        """
+        if self.__nimg.nifti_type == nifticlib.NIFTI_FTYPE_ANALYZE:
+            return self.__nimg.iname
+        else:
+            return self.__nimg.fname
+
+    # class properties
+    # read only
+    nvox =          property(fget=lambda self: self.__nimg.nvox)
+    max =           property(fget=lambda self: self.__nimg.cal_max)
+    min =           property(fget=lambda self: self.__nimg.cal_min)
+    data =          property(fget=getDataArray)
+    sform_inv =     property(fget=getInverseSForm)
+    qform_inv =     property(fget=getInverseQForm)
+    extent =        property(fget=getExtent)
+    volextent =     property(fget=getVolumeExtent)
+    timepoints =    property(fget=getTimepoints)
+    bbox =          property(fget=getBoundingBox)
+
+    # read and write
+    filename =      property(fget=getFilename, fset=setFilename)
+    slope =         property(fget=lambda self: self.__nimg.scl_slope,
+                             fset=setSlope)
+    intercept =     property(fget=lambda self: self.__nimg.scl_inter,
+                             fset=setIntercept)
+    voxdim =        property(fget=getVoxDims, fset=setVoxDims)
+    pixdim =        property(fget=getPixDims, fset=setPixDims)
+    description =   property(fget=lambda self: self.__nimg.descrip,
+                             fset=setDescription)
+    header =        property(fget=getHeader, fset=updateHeader)
+    sform =         property(fget=getSForm, fset=setSForm)
+    qform =         property(fget=getQForm, fset=setQForm)
+    quatern =       property(fget=getQuaternion, fset=setQuaternion)
+    qoffset =       property(fget=getQOffset, fset=setQOffset)
+    qfac =          property(fget=lambda self: self.__nimg.qfac, fset=setQFac)
+    rtime =         property(fget=getRepetitionTime, fset=setRepetitionTime)
+

Added: trunk/scipy/io/nifti/nifti/utils.py
===================================================================
--- trunk/scipy/io/nifti/nifti/utils.py	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/nifti/utils.py	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,129 @@
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+#
+#    Utility function for PyNifti
+#
+#    Copyright (C) 2007 by
+#    Michael Hanke <michael.hanke at gmail.com>
+#
+#    This is free software; you can redistribute it and/or
+#    modify it under the terms of the MIT License.
+#
+#    This package is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+#    file that comes with this package for more details.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+
+import nifti
+import numpy
+
+def time2vol( t, tr, lag=0.0, decimals=0 ):
+    """ Translates a time 't' into a volume number. By default function returns
+    the volume number that is closest in time. Volumes are assumed to be
+    recorded exactly (and completely) after tr/2, e.g. if 'tr' is 2 secs the
+    first volume is recorded at exactly one second.
+
+    't' might be a single value, a sequence or an array.
+
+    The repetition 'tr' might be specified directly, but can also be a 
+    NiftiImage object. In the latter case the value of 'tr' is determined from
+    the 'rtime' property of the NiftiImage object.
+
+    't' and 'tr' can be given in an arbitrary unit (but both have to be in the
+    same unit).
+
+    The 'lag' argument can be used to shift the times by constant offset.
+
+    Please note that numpy.round() is used to round to interger value (rounds
+    to even numbers). The 'decimals' argument will be passed to numpy.round().
+    """
+    # transform to numpy array for easy handling
+    tmp = numpy.array(t)
+    
+    # determine tr if NiftiImage object
+    if isinstance( tr, nifti.NiftiImage ):
+        tr = tr.rtime
+
+    vol = numpy.round( ( tmp + lag + tr/2 ) / tr, decimals )
+
+    return vol
+
+
+def applyFxToVolumes( ts, vols, fx, **kwargs ):
+    """ Apply a function on selected volumes of a timeseries.
+
+    'ts' is a 4d timeseries. It can be a NiftiImage or a numpy array.
+    In case of a numpy array one has to make sure that the time is on the
+    first axis. 'ts' can actually be of any dimensionality, but datasets aka
+    volumes are assumed to be along the first axis.
+
+    'vols' is either a sequence of sequences or a 2d array indicating which 
+    volumes fx should be applied to. Each row defines a set of volumes.
+
+    'fx' is a callable function to get an array of the selected volumes as
+    argument. Additonal arguments may be specified as keyword arguments and
+    are passed to 'fx'.
+
+    The output will be a 4d array with one computed volume per row in the 'vols'
+    array.
+    """
+    # get data array from nifti image or assume data array is
+    # already present
+    if isinstance( ts, nifti.NiftiImage ):
+        data = ts.data
+    else:
+        data = ts
+
+    out = []
+
+    for vol in vols:
+        out.append( fx( data[ numpy.array( vol ) ], **kwargs ) )
+
+    return numpy.array( out )
+
+
+def cropImage( nimg, bbox ):
+    """ Crop an image.
+
+    'bbox' has to be a sequency of (min,max) tuples (one for each image
+    dimension).
+
+    The function returns the cropped image. The data is not shared with the
+    original image, but is copied.
+    """
+
+    # build crop command
+    cmd = 'nimg.data.squeeze()['
+    cmd += ','.join( [ ':'.join( [ str(i) for i in dim ] ) for dim in bbox ] )
+    cmd += ']'
+
+    # crop the image data array
+    cropped = eval(cmd).copy()
+
+    # return the cropped image with preserved header data
+    return nifti.NiftiImage(cropped, nimg.header)
+
+
+def getPeristimulusTimeseries( ts, onsetvols, nvols = 10, fx = numpy.mean ):
+    """ Returns 4d array with peristimulus timeseries.
+
+    Parameters:
+        ts        - source 4d timeseries
+        onsetvols - sequence of onsetvolumes to be averaged over
+        nvols     - length of the peristimulus timeseries in volumes
+                    (starting from onsetvol)
+        fx        - function to be applied to the list of corresponding
+                    volumes. Typically this will be mean(), so it is default,
+                    but it could also be var() or something different. The
+                    supplied function is to be able to handle an 'axis=0'
+                    argument similiar to NumPy's mean(), var(), ...
+    """
+    selected = [ [ o + offset for o in onsetvols ] \
+                    for offset in range( nvols ) ]
+
+    if fx == tuple:
+        return applyFxToVolumes( ts, selected, fx )
+    else:
+        return applyFxToVolumes( ts, selected, fx, axis=0 )
+

Added: trunk/scipy/io/nifti/setup.py
===================================================================
--- trunk/scipy/io/nifti/setup.py	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/setup.py	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+#
+#    Python distutils setup for PyNifti
+#
+#    Copyright (C) 2006-2007 by
+#    Michael Hanke <michael.hanke at gmail.com>
+#
+#    This is free software; you can redistribute it and/or
+#    modify it under the terms of the MIT License.
+#
+#    This package is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+#    file that comes with this package for more details.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+
+from distutils.core import setup, Extension
+import os
+import numpy
+from glob import glob
+
+nifti_wrapper_file = os.path.join('nifti', 'nifticlib.py')
+
+# create an empty file to workaround crappy swig wrapper installation
+if not os.path.isfile(nifti_wrapper_file):
+    open(nifti_wrapper_file, 'w')
+
+# find numpy headers
+numpy_headers = os.path.join(os.path.dirname(numpy.__file__),'core','include')
+
+
+# Notes on the setup
+# Version scheme is:
+# 0.<4-digit-year><2-digit-month><2-digit-day>.<ever-increasing-integer>
+
+setup(name       = 'pynifti',
+    version      = '0.20070930.1',
+    author       = 'Michael Hanke',
+    author_email = 'michael.hanke at gmail.com',
+    license      = 'MIT License',
+    url          = 'http://apsy.gse.uni-magdeburg.de/hanke',
+    description  = 'Python interface for the NIfTI IO libraries',
+    long_description = """ """,
+    packages     = [ 'nifti' ],
+    scripts      = glob( 'bin/*' ),
+    ext_modules  = [ Extension( 'nifti._nifticlib', [ 'nifti/nifticlib.i' ],
+            include_dirs = [ '/usr/include/nifti', numpy_headers ],
+            libraries    = [ 'niftiio' ],
+            swig_opts    = [ '-I/usr/include/nifti',
+                             '-I' + numpy_headers ] ) ]
+    )
+

Added: trunk/scipy/io/nifti/tests/data/example4d.nii.gz
===================================================================
(Binary files differ)


Property changes on: trunk/scipy/io/nifti/tests/data/example4d.nii.gz
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/scipy/io/nifti/tests/test_fileio.py
===================================================================
--- trunk/scipy/io/nifti/tests/test_fileio.py	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/tests/test_fileio.py	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,81 @@
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+#
+#    Unit tests for PyNIfTI file io
+#
+#    Copyright (C) 2007 by
+#    Michael Hanke <michael.hanke at gmail.com>
+#
+#    This is free software; you can redistribute it and/or
+#    modify it under the terms of the MIT License.
+#
+#    This package is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+#    file that comes with this package for more details.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+
+import nifti
+import unittest
+import md5
+import tempfile
+import shutil
+import os
+import numpy as np
+
+
+def md5sum(filename):
+    """ Generate MD5 hash string.
+    """
+    file = open( filename )
+    sum = md5.new()
+    while True:
+        data = file.read()
+        if not data:
+            break
+        sum.update(data)
+    return sum.hexdigest()
+
+
+class FileIOTests(unittest.TestCase):
+    def setUp(self):
+        self.workdir = tempfile.mkdtemp('pynifti_test')
+
+    def tearDown(self):
+        shutil.rmtree(self.workdir)
+
+    def testIdempotentLoadSaveCycle(self):
+        """ check if file is unchanged by load/save cycle.
+        """
+        md5_orig = md5sum('data/example4d.nii.gz')
+        nimg = nifti.NiftiImage('data/example4d.nii.gz')
+        nimg.save( os.path.join( self.workdir, 'iotest.nii.gz') )
+        md5_io =  md5sum( os.path.join( self.workdir, 'iotest.nii.gz') )
+
+        self.failUnlessEqual(md5_orig, md5_io)
+
+    def testQFormSetting(self):
+        nimg = nifti.NiftiImage('data/example4d.nii.gz')
+        # 4x4 identity matrix
+        ident = np.identity(4)
+        self.failIf( (nimg.qform == ident).all() )
+
+        # assign new qform
+        nimg.qform = ident
+        self.failUnless( (nimg.qform == ident).all() )
+
+        # test save/load cycle
+        nimg.save( os.path.join( self.workdir, 'qformtest.nii.gz') )
+        nimg2 = nifti.NiftiImage( os.path.join( self.workdir, 
+                                               'qformtest.nii.gz') )
+
+        self.failUnless( (nimg.qform == nimg2.qform).all() )
+
+
+def suite():
+    return unittest.makeSuite(FileIOTests)
+
+
+if __name__ == '__main__':
+    unittest.main()
+

Added: trunk/scipy/io/nifti/tests/test_main.py
===================================================================
--- trunk/scipy/io/nifti/tests/test_main.py	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/tests/test_main.py	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,42 @@
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+#
+#    Main unit test interface for PyNIfTI
+#
+#    Copyright (C) 2007 by
+#    Michael Hanke <michael.hanke at gmail.com>
+#
+#    This is free software; you can redistribute it and/or
+#    modify it under the terms of the MIT License.
+#
+#    This package is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+#    file that comes with this package for more details.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+
+import unittest
+
+# list all test modules (without .py extension)
+tests = [ 'test_fileio',
+          'test_utils',
+        ]
+
+
+# import all test modules
+for t in tests:
+    exec 'import ' + t
+
+
+if __name__ == '__main__':
+
+    # load all tests suites
+    suites = [ eval(t + '.suite()') for t in tests ]
+
+    # and make global test suite
+    ts = unittest.TestSuite( suites )
+
+    # finally run it
+    unittest.TextTestRunner().run( ts )
+
+

Added: trunk/scipy/io/nifti/tests/test_utils.py
===================================================================
--- trunk/scipy/io/nifti/tests/test_utils.py	2007-10-03 22:43:43 UTC (rev 3396)
+++ trunk/scipy/io/nifti/tests/test_utils.py	2007-10-03 23:58:26 UTC (rev 3397)
@@ -0,0 +1,37 @@
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+#
+#    Unit tests for PyNIfTI file io
+#
+#    Copyright (C) 2007 by
+#    Michael Hanke <michael.hanke at gmail.com>
+#
+#    This is free software; you can redistribute it and/or
+#    modify it under the terms of the MIT License.
+#
+#    This package is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the COPYING
+#    file that comes with this package for more details.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
+
+import nifti.utils
+import numpy
+import unittest
+
+
+class UtilsTests(unittest.TestCase):
+    def testZScoring(self):
+        # dataset: mean=2, std=1
+        data = numpy.array( (0,1,3,4,2,2,3,1,1,3,3,1,2,2,2,2) )
+        self.failUnlessEqual( data.mean(), 2.0 )
+        self.failUnlessEqual( data.std(), 1.0 )
+
+
+def suite():
+    return unittest.makeSuite(UtilsTests)
+
+
+if __name__ == '__main__':
+    unittest.main()
+




More information about the Scipy-svn mailing list