From 46997422b17224a7e3b61a95066a0579fede085b Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Thu, 28 Nov 2013 21:59:24 +0100 Subject: [PATCH 1/9] First implementation. Copy of LabeledWrapStruct without inheritance. Implement Vtc File NF: implement read and write for BrainVoyager QX VTC files NF+RF: implement read and write support for BrainVoyager QX MSK files and refactor bv.py NF: implement read and write support for BrainVoyager QX NR-VMP files TST: add test data for BrainVoyager QX files (VTC, MSK, and NR-VMP files) RF: create additional CArrayProxy class to work with C array memory layouts. RF: BvFileHeader inherits from LabeledWrapStruct BF: implement default_structarr for VMP files; NF: implement new set_shape method (with alternative propoerty zyx) for BrainVoyager file formats NF+BF+RF+TST: major refactor of BV formats implementation. Add tests for BV formats. Add affine for BV formats. RF: change side effect of update_header methods NF: VMP files can change shape (with _add_submap and _rem_submap) BF: change default values for vmp submaps; look for value (instead of key) when setting a string in the header BF: save old hdr when for transferring keys when changing the number of maps BF: change dtype of RGB fields to u1 BF: correct they way that nested dtypes of strings (length) are changed for VMP files BF: default_structarr for VMP header fills map1 and not map2 BF: in __setitem__ check for basestring; remove setString method NF: Implement basic support for some BrainVoyager QX file formats. This is a squashed commit for implementing basic BrainVoyager QX files support. See below for the single commit messages. First implementation. Copy of LabeledWrapStruct without inheritance. Implement Vtc File NF: implement read and write for BrainVoyager QX VTC files NF+RF: implement read and write support for BrainVoyager QX MSK files and refactor bv.py NF: implement read and write support for BrainVoyager QX NR-VMP files TST: add test data for BrainVoyager QX files (VTC, MSK, and NR-VMP files) RF: create additional CArrayProxy class to work with C array memory layouts. RF: BvFileHeader inherits from LabeledWrapStruct BF: implement default_structarr for VMP files; NF: implement new set_shape method (with alternative propoerty zyx) for BrainVoyager file formats NF+BF+RF+TST: major refactor of BV formats implementation. Add tests for BV formats. Add affine for BV formats. RF: change side effect of update_header methods NF: VMP files can change shape (with _add_submap and _rem_submap) BF: change default values for vmp submaps; look for value (instead of key) when setting a string in the header BF: save old hdr when for transferring keys when changing the number of maps BF: change dtype of RGB fields to u1 BF: correct they way that nested dtypes of strings (length) are changed for VMP files BF: default_structarr for VMP header fills map1 and not map2 BF: in __setitem__ check for basestring; remove setString method RF+TEST: Change the header parsing for BV file formats and implement tests. The BV file headers are now parsed using an OrderedDict. Tests are implemented for VtcImage. Only the VtcImage class is adapted to the new header parsing. DOC+PL+RF: Adding documentation and linting to bv.py. Let readCString return a list instead of a generator. PL: Removing blank lines. RF: Simplify and centralize the bv hdr prototype. Header prototypes are defined per BV filetype as nested tuples. The creation of an OrderedDict header is done centrally for all filetypes in bv.py based on these prototypes. WIP: separating prototype and default header Refactor the prototype format to make it a little easier to use. Pass the prototype into functions working with the header, to provide read / write format information. Return headers with (key, value) pairs, where the `value` is a value or a list, rather than a dict. WIP: Adapt BV MSK file implementation to new hdr. STY: flake8-compatible code. RF: move files into brainvoyager subdirectory. STY: Prepend "Bv" to all brainvoyager classes. BF: merge new protoHeader implementation into refactored bv files. BF: fix automatic filetype guessing for BV formats. NF : support for VMR file format TEST: add test_bv_vmr TEST: add test_bv_vmr TEST: add test_bv_vmr TEST: add test_bv_vmr Update check_vmr.txt TEST: add test_bv_vmr TEST: add test_bv_vmr TEST: add test_bv_vmr BF: BvVmr __init__ imports correct. BF: Adapt naming convention in bv_vmr.py. BF: Change set_data_shape() to include start values. BF: Change set_data_shape() to include start values in bv_msk.py. BF: Adapt bv_vmp to new hdr_dict format. WIP: Adapt bv_vmr implementation to naming standards. TST: Add image_api tests for BrainVoyager file formats. PL: Lint bv_vmr.py. PL: Lint bv_msk.py. BF: Add nibabel.brainvoyager to setup.py. BF: Enable automatic filetype guessing for bv_vmr. TEST: Adapt test_bv.py to new naming convention. TEST: Fix and clean test_bv_vmr.py. BF: Delete double function definitions in bv.py. TEST: Move get_base_affine docstring text to test_bv_vtc. PL: Lint all BV modules. BF: Correct parameter order for BvVmrHeader methods (ZYX). TEST: Move BV tests into brainvoyager/tests. DOC: Correct docstring for get_base_affine in BvFileHeader. TEST: Fix import paths after move of BV tests. TEST: Fix file handling error in test_bv. TEST: Fix test_bv file read ('rb'). TEST: Add BV file formats to more test cases. OPT: Make MGHHeader raise HeaderDataError in set_data_dtype instead of MGHError. BF: Raise HeaderDataError in BV file formats and continue when parsing conditional BV header fields. NF: Add function to check for supported dimensions in spatialimages. TEST: Fix test_files_interface tests for file formats with unsupported dtypes/dimensions. TEST: test_bad_dtype_mgh asserts an HeaderDataError. TEST+BF: Fix tests for BV and MGH file formats. Add update_BV_header function to account for nested header fields. NF: Compute combined inverse spatial transformation for BV VMR files. BF: correct import for BV EXAMPLE_IMAGES. TEST: Correct test_image_api for BV VMR file format. BF: Fix import of nibabel.brainvoyager.tests module. BF: Correct usage of bytes literals in bv.py and corresponding tests. TEST: Fix BV tests for Python 3.x TEST: Remove del command from BV test. TEST: Add temporary debug prints to test_image_api. TEST: Add file close command to test_bv. TEST: Fix supported_dimensions tests for numpy>=1.12. PL: Correct BV files. PL: Correct bv_vmr.py. OPT: Make BV variable names and header fields more pythonic. TEST: Fix numpy.rint warning issue in test_image_api. TEST+BF: Add a lot of BV tests. --- nibabel/__init__.py | 2 + nibabel/arrayproxy.py | 5 + nibabel/brainvoyager/__init__.py | 18 + nibabel/brainvoyager/bv.py | 952 +++++++++++++++++++++ nibabel/brainvoyager/bv_msk.py | 105 +++ nibabel/brainvoyager/bv_vmp.py | 195 +++++ nibabel/brainvoyager/bv_vmr.py | 255 ++++++ nibabel/brainvoyager/bv_vtc.py | 155 ++++ nibabel/brainvoyager/tests/__init__.py | 23 + nibabel/brainvoyager/tests/test_bv.py | 284 ++++++ nibabel/brainvoyager/tests/test_bv_msk.py | 61 ++ nibabel/brainvoyager/tests/test_bv_vmp.py | 357 ++++++++ nibabel/brainvoyager/tests/test_bv_vmr.py | 142 +++ nibabel/brainvoyager/tests/test_bv_vtc.py | 121 +++ nibabel/freesurfer/mghformat.py | 4 +- nibabel/freesurfer/tests/test_mghformat.py | 3 +- nibabel/imageclasses.py | 32 +- nibabel/spatialimages.py | 33 +- nibabel/tests/data/test.msk | Bin 0 -> 1014 bytes nibabel/tests/data/test.vmp | Bin 0 -> 4164 bytes nibabel/tests/data/test.vmr | Bin 0 -> 380 bytes nibabel/tests/data/test.vtc | Bin 0 -> 20048 bytes nibabel/tests/data/test2.vmp | Bin 0 -> 8241 bytes nibabel/tests/data/test3.vmp | Bin 0 -> 537 bytes nibabel/tests/test_arrayproxy.py | 7 +- nibabel/tests/test_files_interface.py | 42 +- nibabel/tests/test_image_api.py | 102 ++- nibabel/tests/test_image_load_save.py | 24 +- nibabel/tests/test_image_types.py | 9 +- nibabel/tests/test_loadsave.py | 6 +- setup.py | 2 + 31 files changed, 2900 insertions(+), 39 deletions(-) create mode 100644 nibabel/brainvoyager/__init__.py create mode 100644 nibabel/brainvoyager/bv.py create mode 100644 nibabel/brainvoyager/bv_msk.py create mode 100644 nibabel/brainvoyager/bv_vmp.py create mode 100644 nibabel/brainvoyager/bv_vmr.py create mode 100644 nibabel/brainvoyager/bv_vtc.py create mode 100644 nibabel/brainvoyager/tests/__init__.py create mode 100644 nibabel/brainvoyager/tests/test_bv.py create mode 100644 nibabel/brainvoyager/tests/test_bv_msk.py create mode 100644 nibabel/brainvoyager/tests/test_bv_vmp.py create mode 100644 nibabel/brainvoyager/tests/test_bv_vmr.py create mode 100644 nibabel/brainvoyager/tests/test_bv_vtc.py create mode 100644 nibabel/tests/data/test.msk create mode 100644 nibabel/tests/data/test.vmp create mode 100644 nibabel/tests/data/test.vmr create mode 100644 nibabel/tests/data/test.vtc create mode 100644 nibabel/tests/data/test2.vmp create mode 100644 nibabel/tests/data/test3.vmp diff --git a/nibabel/__init__.py b/nibabel/__init__.py index cf4173fc27..8e5f14646d 100644 --- a/nibabel/__init__.py +++ b/nibabel/__init__.py @@ -51,6 +51,8 @@ from .nifti2 import Nifti2Header, Nifti2Image, Nifti2Pair from .minc1 import Minc1Image from .minc2 import Minc2Image +from .brainvoyager import (BvMskHeader, BvMskImage, BvVmpHeader, BvVmpImage, + BvVtcHeader, BvVtcImage, BvVmrHeader, BvVmrImage) # Deprecated backwards compatiblity for MINC1 from .deprecated import ModuleProxy as _ModuleProxy minc = _ModuleProxy('nibabel.minc') diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index aa0df4eebc..1ae244d5e7 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -164,3 +164,8 @@ def is_proxy(obj): return obj.is_proxy except AttributeError: return False + + +class CArrayProxy(ArrayProxy): + # Assume C array memory layout + order = 'C' diff --git a/nibabel/brainvoyager/__init__.py b/nibabel/brainvoyager/__init__.py new file mode 100644 index 0000000000..5507978c52 --- /dev/null +++ b/nibabel/brainvoyager/__init__.py @@ -0,0 +1,18 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +''' Support for BrainVoyager file formats ''' +from .bv_msk import BvMskHeader, BvMskImage +from .bv_vmp import BvVmpHeader, BvVmpImage +from .bv_vtc import BvVtcHeader, BvVtcImage +from .bv_vmr import BvVmrHeader, BvVmrImage + +__all__ = ('BvMskHeader', 'BvMskImage', + 'BvVmpHeader', 'BvVmpImage', + 'BvVtcHeader', 'BvVtcImage', + 'BvVmrHeader', 'BvVmrImage') diff --git a/nibabel/brainvoyager/bv.py b/nibabel/brainvoyager/bv.py new file mode 100644 index 0000000000..b0a250347a --- /dev/null +++ b/nibabel/brainvoyager/bv.py @@ -0,0 +1,952 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Reading / writing functions for Brainvoyager (BV) file formats. + +please look at the support site of BrainInnovation for further informations +about the file formats: http://support.brainvoyager.com/ + +This file implements basic functionality for BV file formats. Look into bv_*.py +files for implementations of the different file formats. + +Author: Thomas Emmerling +""" +from __future__ import division +import numpy as np + +from ..volumeutils import array_to_file, array_from_file, make_dt_codes +from ..spatialimages import Header, HeaderDataError, SpatialImage +from ..fileholders import copy_file_map +from ..arrayproxy import CArrayProxy +from .. import imageglobals as imageglobals +from ..batteryrunners import BatteryRunner, Report +from struct import pack, unpack, calcsize +from ..externals import OrderedDict + +_dtdefs = ( # code, conversion function, equivalent dtype, aliases + (1, 'int16', np.uint16), + (2, 'float32', np.float32), + (3, 'uint8', np.uint8)) + +# Make full code alias bank, including dtype column +data_type_codes = make_dt_codes(_dtdefs) + +# Set example hdr_dict_proto for BV file formats +BV_HDR_DICT_PROTO = ( + ('resolution', 'h', 3), + ('x_start', 'h', 57), + ('x_end', 'h', 231), + ('y_start', 'h', 52), + ('y_end', 'h', 172), + ('z_start', 'h', 59), + ('z_end', 'h', 197), +) + + +def readCString(f, nStrings=1, bufsize=1000, startPos=None, strip=True, + rewind=False): + """Read a zero-terminated string from a file object. + + Read and return a zero-terminated string from a file object. + + Parameters + ---------- + f : fileobj + File object to use + nStrings: int, optional + Number of strings to search (and return). Default is 1. + bufsize: int, optional + Define the buffer size that should be searched for the string. + Default is 1000 bytes. + startPos: int, optional + Define the start file position from which to search. If None then start + where the file object currently points to. Default is None. + strip : bool, optional + Whether to strip the trailing zero from the returned string. + Default is True. + rewind: bool, optional + Whether the fileobj f should be returned to the initial position after + reading. Default is False. + + Returns + ------- + str_list : generator of string(s) + """ + currentPos = f.tell() + if strip: + suffix = b'' + else: + suffix = b'\x00' + if startPos is not None: + f.seek(startPos) + data = f.read(bufsize) + lines = data.split(b'\x00') + str_list = [] + if rewind: + f.seek(currentPos) + else: + offset = 0 + for s in range(nStrings): + offset += len(lines[s]) + 1 + f.seek(currentPos + offset) + for s in range(nStrings): + str_list.append(lines[s] + suffix) + return str_list + + +def parse_BV_header(hdr_dict_proto, fileobj, parent_hdr_dict=None): + """Parse the header of a BV file format. + + This function can be (and is) called recursively to iterate through nested + fields (e.g. the ``prts`` field of the VTC header). + + Parameters + ---------- + hdr_dict_proto: tuple + tuple of format described in Notes below. + fileobj : fileobj + File object to use. Make sure that the current position is at the + beginning of the header (e.g. at 0). + parent_hdr_dict: OrderedDict + When parse_BV_header() is called recursively the already filled + (parent) hdr_dict is passed to give access to n_fields_name fields + outside the current scope (see below). + + Returns + ------- + hdr_dict : OrderedDict + An OrderedDict containing all header fields parsed from the file. + + Notes + ----- + The description of `hdr_dict_proto` below is notated according to + https://docs.python.org/3/reference/introduction.html#notation + + hdr_dict_proto ::= ((element_proto))* + element_proto ::= '(' name ',' pack_format ',' default ')' | + '(' name ',' pack_format ',' '(' default ',' + c_fields_name ',' c_fields_value ')' ')' | + '(' name ',' hdr_dict_proto ',' n_fields_name ')' + pack_format ::= 'b' | 'h' | 'f' | 'z' + name ::= str + n_fields_name ::= str + c_fields_name ::= str + c_fields_value ::= int | float | bytes + default ::= int | float | bytes + + The pack_format codes have meaning:: + + b := signed char (1 byte) + B := unsigned char (1 byte) + h := signed short integer (2 bytes) + i := signed integer (4 bytes) + I := unsigned integer (4 bytes) + f := float (4 bytes) + z := zero-terminated string (variable bytes) + + The n_fields_name is used to indicate the name of a header field that + contains a number for nested header fields loops (e.g. 'NrOfSubMaps' in the + VMP file header). + + The c_fields_name and c_fields_value parameters are used for header fields + that are only written depending on the value of another header field (e.g. + 'NrOfLags' in the VMP file header). + """ + hdr_dict = OrderedDict() + for name, format, def_or_name in hdr_dict_proto: + # handle zero-terminated strings + if format == 'z': + value = readCString(fileobj)[0] + # handle array fields + elif isinstance(format, tuple): + value = [] + # check the length of the array to expect + if def_or_name in hdr_dict: + n_values = hdr_dict[def_or_name] + else: + n_values = parent_hdr_dict[def_or_name] + for i in range(n_values): + value.append(parse_BV_header(format, fileobj, hdr_dict)) + # handle conditional fields + elif isinstance(def_or_name, tuple): + if hdr_dict[def_or_name[1]] == def_or_name[2]: + bytes = fileobj.read(calcsize(format)) + value = unpack('<' + format, bytes)[0] + else: # assign the default value + value = def_or_name[0] + else: # unpack bytes of type format + bytes = fileobj.read(calcsize(format)) + value = unpack('<' + format, bytes)[0] + hdr_dict[name] = value + return hdr_dict + + +def pack_BV_header(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): + """Pack the header of a BV file format into a byte string. + + This function can be (and is) called recursively to iterate through nested + fields (e.g. the ``prts`` field of the VTC header). + + Parameters + ---------- + hdr_dict_proto: tuple + tuple of format described in Notes of :func:`parse_BV_header` + hdr_dict: OrderedDict + hdr_dict that contains the fields and values to for the respective + BV file format. + parent_hdr_dict: OrderedDict + When parse_BV_header() is called recursively the already filled + (parent) hdr_dict is passed to give access to n_fields_name fields + outside the current scope (see below). + + Returns + ------- + binaryblock : bytes + Binary representation of header ready for writing to file. + """ + binary_parts = [] + for name, format, def_or_name in hdr_dict_proto: + value = hdr_dict[name] + # handle zero-terminated strings + if format == 'z': + part = value + b'\x00' + # handle array fields + elif isinstance(format, tuple): + # check the length of the array to expect + if def_or_name in hdr_dict: + n_values = hdr_dict[def_or_name] + else: + n_values = parent_hdr_dict[def_or_name] + sub_parts = [] + for i in range(n_values): + sub_parts.append(pack_BV_header(format, value[i], hdr_dict)) + part = b''.join(sub_parts) + # handle conditional fields + elif isinstance(def_or_name, tuple): + if hdr_dict[def_or_name[1]] == def_or_name[2]: + part = pack('<' + format, value) + else: + continue + else: + part = pack('<' + format, value) + binary_parts.append(part) + return b''.join(binary_parts) + + +def calc_BV_header_size(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): + """Calculate the binary size of a hdr_dict for a BV file format header. + + This function can be (and is) called recursively to iterate through nested + fields (e.g. the prts field of the VTC header). + + Parameters + ---------- + hdr_dict_proto: tuple + tuple of format described in Notes of :func:`parse_BV_header` + hdr_dict: OrderedDict + hdr_dict that contains the fields and values to for the respective + BV file format. + parent_hdr_dict: OrderedDict + When parse_BV_header() is called recursively the already filled + (parent) hdr_dict is passed to give access to n_fields_name fields + outside the current scope (see below). + + Returns + ------- + hdr_size : int + Size of header when packed into bytes ready for writing to file. + """ + hdr_size = 0 + for name, format, def_or_name in hdr_dict_proto: + value = hdr_dict[name] + # handle zero-terminated strings + if format == 'z': + hdr_size += len(value) + 1 + # handle array fields + elif isinstance(format, tuple): + # check the length of the array to expect + if def_or_name in hdr_dict: + n_values = hdr_dict[def_or_name] + else: + n_values = parent_hdr_dict[def_or_name] + for i in range(n_values): + # recursively iterate through the fields of all items + # in the array + hdr_size += calc_BV_header_size(format, value[i], hdr_dict) + # handle conditional fields + elif isinstance(def_or_name, tuple): + if hdr_dict[def_or_name[1]] == def_or_name[2]: + hdr_size += calcsize(format) + else: + continue + else: + hdr_size += calcsize(format) + return hdr_size + + +def update_BV_header(hdr_dict_proto, hdr_dict_old, hdr_dict_new, + parent_old=None, parent_new=None): + """Update a hdr_dict after changed nested-loops-number or conditional fields. + + This function can be (and is) called recursively to iterate through nested + fields (e.g. the prts field of the VTC header). + + Parameters + ---------- + hdr_dict_proto: tuple + tuple of format described in Notes of :func:`parse_BV_header` + hdr_dict_old: OrderedDict + hdr_dict before any changes. + hdr_dict_new: OrderedDict + hdr_dict with changed fields in n_fields_name or c_fields_name fields. + parent_old: OrderedDict + When update_BV_header() is called recursively the not yet updated + (parent) hdr_dict is passed to give access to n_fields_name fields + outside the current scope (see below). + parent_new: OrderedDict + When update_BV_header() is called recursively the not yet updated + (parent) hdr_dict is passed to give access to n_fields_name fields + outside the current scope (see below). + + Returns + ------- + hdr_dict_new : OrderedDict + An updated version hdr_dict correcting effects of changed nested and + conditional fields. + """ + for name, format, def_or_name in hdr_dict_proto: + # handle nested loop fields + if isinstance(format, tuple): + # calculate the change of array length and the new array length + if def_or_name in hdr_dict_old: + delta_values = hdr_dict_new[def_or_name] - \ + hdr_dict_old[def_or_name] + n_values = hdr_dict_new[def_or_name] + else: + delta_values = parent_new[def_or_name] - \ + parent_old[def_or_name] + n_values = parent_new[def_or_name] + if delta_values > 0: # add nested loops + for i in range(delta_values): + hdr_dict_new[name].append(_proto2default(format, hdr_dict_new)) + elif delta_values < 0: # remove nested loops + for i in range(abs(delta_values)): + hdr_dict_new[name].pop() + # loop over nested fields + for i in range(n_values): + update_BV_header(format, hdr_dict_old[name][i], + hdr_dict_new[name][i], hdr_dict_old, + hdr_dict_new) + return hdr_dict_new + + +def _proto2default(proto, parent_default_hdr=None): + """Helper for creating a BV header OrderedDict with default parameters. + + Create an OrderedDict that contains keys with the header fields, and + default values. + + See :func:`parse_BV_header` for description of `proto` format. + """ + default_hdr = OrderedDict() + for name, format, def_or_name in proto: + if isinstance(format, tuple): + value = [] + # check the length of the array to expect + if def_or_name in default_hdr: + n_values = default_hdr[def_or_name] + else: + n_values = parent_default_hdr[def_or_name] + for i in range(n_values): + value.append(_proto2default(format, default_hdr)) + default_hdr[name] = value + # handle conditional fields + elif isinstance(def_or_name, tuple): + default_hdr[name] = def_or_name[0] + else: + default_hdr[name] = def_or_name + return default_hdr + + +def combine_st(st_array, inv=False): + """Combine spatial transformation matrices. + + This recursive function returns the dot product of all spatial + transformation matrices given in st_array for applying them in one go. + The order of multiplication follow the order in the given array. + + Parameters + ---------- + st_array: array + array filled with transformation matrices of shape (4, 4) + + inv: boolean + Set to true to invert the transformation matrices before + multiplication. + + Returns + ------- + combined_st : array of shape (4, 4) + """ + if len(st_array) == 1: + if inv: + return np.linalg.inv(st_array[0]) + else: + return st_array[0] + if inv: + return np.dot(np.linalg.inv(st_array[0, :, :]), + combine_st(st_array[1:, :, :], inv=inv)) + else: + return np.dot(st_array[0, :, :], + combine_st(st_array[1:, :, :], inv=inv)) + + +def parse_st(st_dict): + """Parse spatial transformation stored in a BV header OrderedDict. + + This function parses a given OrderedDict from a BV header field and returns + a spatial transformation matrix as a numpy array. + + Parameters + ---------- + st_dict: OrderedDict + OrderedDict filled with transformation matrices of shape (4, 4) + + Returns + ------- + st_array : array of shape (4, 4) + """ + if st_dict['nr_of_trans_val'] != 16: + raise BvError('spatial transformation has to be of shape (4, 4)') + st_array = [] + for v in range(st_dict['nr_of_trans_val']): + st_array.append(st_dict['trans_val'][v]['value']) + return np.array(st_array).reshape((4, 4)) + + +class BvError(Exception): + """Exception for BV format related problems. + + To be raised whenever there is a problem with a BV fileformat. + """ + + pass + + +class BvFileHeader(Header): + """Class to hold information from a BV file header.""" + + # Copies of module-level definitions + _data_type_codes = data_type_codes + _field_recoders = {'datatype': data_type_codes} + + # format defaults + # BV files are radiological (left-is-right) by default + # (VTC files have a flag for that, however) + default_x_flip = True + default_endianness = '<' # BV files are always little-endian + allowed_dtypes = [1, 2, 3] + default_dtype = 2 + allowed_dimensions = [3] + data_layout = 'C' + hdr_dict_proto = BV_HDR_DICT_PROTO + + def __init__(self, + hdr_dict=None, + endianness=default_endianness, + check=True, + offset=None): + """Initialize header from binary data block. + + Parameters + ---------- + binaryblock : {None, string} optional + binary block to set into header. By default, None, in + which case we insert the default empty header block + endianness : {None, '<','>', other endian code} string, optional + endianness of the binaryblock. If None, guess endianness + from the data. + check : bool, optional + Whether to check content of header in initialization. + Default is True. + offset : int, optional + offset of the actual data into to binary file (in bytes) + """ + if endianness != self.default_endianness: + raise BvError('BV files are always little-endian') + self.endianness = self.default_endianness + if hdr_dict is None: + hdr_dict = _proto2default(self.hdr_dict_proto) + self._hdr_dict = hdr_dict + if offset is None: + self.set_data_offset(calc_BV_header_size( + self.hdr_dict_proto, self._hdr_dict)) + if 'framing_cube' in self._hdr_dict: + self._framing_cube = self._hdr_dict['framing_cube'] + else: + self._framing_cube = self._guess_framing_cube() + if check: + self.check_fix() + return + + @classmethod + def from_fileobj(klass, fileobj, endianness=default_endianness, + check=True): + """Return read structure with given or guessed endiancode. + + Parameters + ---------- + fileobj : file-like object + Needs to implement ``read`` method + endianness : None or endian code, optional + Code specifying endianness of read data + + Returns + ------- + header : BvFileHeader object + BvFileHeader object initialized from data in fileobj + """ + hdr_dict = parse_BV_header(klass.hdr_dict_proto, fileobj) + offset = fileobj.tell() + return klass(hdr_dict, endianness, check, offset) + + @classmethod + def from_header(klass, header=None, check=False): + """Class method to create header from another header. + + Parameters + ---------- + header : ``Header`` instance or mapping + a header of this class, or another class of header for + conversion to this type + check : {True, False} + whether to check header for integrity + + Returns + ------- + hdr : header instance + fresh header instance of our own class + """ + # own type, return copy + if type(header) == klass: + obj = header.copy() + if check: + obj.check_fix() + return obj + # not own type, make fresh header instance + obj = klass(check=check) + if header is None: + return obj + try: # check if there is a specific conversion routine + mapping = header.as_bv_map() + except AttributeError: + # most basic conversion + obj.set_data_dtype(header.get_data_dtype()) + obj.set_data_shape(header.get_data_shape()) + obj.set_zooms(header.get_zooms()) + return obj + # header is convertible from a field mapping + for key, value in mapping.items(): + try: + obj[key] = value + except (ValueError, KeyError): + # the presence of the mapping certifies the fields as + # being of the same meaning as for BV types + pass + # set any fields etc that are specific to this format (overriden by + # sub-classes) + obj._set_format_specifics() + # Check for unsupported datatypes + orig_code = header.get_data_dtype() + try: + obj.set_data_dtype(orig_code) + except HeaderDataError: + raise HeaderDataError('Input header %s has datatype %s but ' + 'output header %s does not support it' + % (header.__class__, + header.get_value_label('datatype'), + klass)) + if check: + obj.check_fix() + return obj + + def copy(self): + """Copy object to independent representation. + + The copy should not be affected by any changes to the original + object. + """ + return self.__class__(self._hdr_dict) + + def _set_format_specifics(self): + """Utility routine to set format specific header stuff.""" + pass + + def data_from_fileobj(self, fileobj): + """Read data array from `fileobj`. + + Parameters + ---------- + fileobj : file-like + Must be open, and implement ``read`` and ``seek`` methods + + Returns + ------- + arr : ndarray + data array + """ + dtype = self.get_data_dtype() + shape = self.get_data_shape() + offset = self.get_data_offset() + return array_from_file(shape, dtype, fileobj, offset, + order=self.data_layout) + + def get_data_dtype(self): + """Get numpy dtype for data. + + For examples see ``set_data_dtype`` + """ + if 'datatype' in self._hdr_dict: + code = self._hdr_dict['datatype'] + else: + code = self.default_dtype + dtype = self._data_type_codes.dtype[code] + return dtype.newbyteorder(self.endianness) + + def set_data_dtype(self, datatype): + """Set numpy dtype for data from code or dtype or type.""" + try: + code = self._data_type_codes[datatype] + except KeyError: + raise HeaderDataError( + 'data dtype "%s" not recognized' % datatype) + if code not in self.allowed_dtypes: + raise HeaderDataError( + 'data dtype "%s" not supported' % datatype) + dtype = self._data_type_codes.dtype[code] + if 'datatype' in self._hdr_dict.keys(): + self._hdr_dict['datatype'] = code + return + if dtype.newbyteorder(self.endianness) != self.get_data_dtype(): + raise HeaderDataError( + 'File format does not support setting of header!') + + def get_xflip(self): + """Get xflip for data.""" + return self.default_x_flip + + def set_xflip(self, xflip): + """Set xflip for data.""" + if xflip is True: + return + else: + raise BvError('cannot change Left-right convention!') + + def get_data_shape(self): + """Get shape of data.""" + raise NotImplementedError + + def set_data_shape(self, shape): + """Set shape of data.""" + raise NotImplementedError + + def get_base_affine(self): + """Get affine from basic (shared) header fields. + + Note that we get the translations from the center of the + (guessed) framing cube of the referenced VMR (anatomical) file. + + Internal storage of the image is ZYXT, where (in patient coordiante/ + real world orientations): + Z := axis increasing from right to left (R to L) + Y := axis increasing from superior to inferior (S to I) + X := axis increasing from anterior to posterior (A to P) + T := volumes (if present in file format) + """ + zooms = self.get_zooms() + if not self.get_xflip(): + # make the BV internal Z axis neurological (left-is-left); + # not default in BV files! + zooms[0] *= -1 + + # compute the rotation + rot = np.zeros((3, 3)) + # make the flipped BV Z axis the new R axis + rot[:, 0] = [-zooms[0], 0, 0] + # make the flipped BV X axis the new A axis + rot[:, 1] = [0, 0, -zooms[2]] + # make the flipped BV Y axis the new S axis + rot[:, 2] = [0, -zooms[1], 0] + + # compute the translation + fcc = np.array(self.get_framing_cube()) / 2 # center of framing cube + bbc = np.array(self.get_bbox_center()) # center of bounding box + tra = np.dot((bbc - fcc), rot) + + # assemble + M = np.eye(4, 4) + M[0:3, 0:3] = rot + M[0:3, 3] = tra.T + + return M + + get_best_affine = get_base_affine + + get_default_affine = get_base_affine + + get_affine = get_base_affine + + def _guess_framing_cube(self): + """Guess the dimensions of the framing cube. + + Guess the dimensions of the framing cube that constitutes the + coordinate system boundaries for the bounding box. + + For most BV file formats this need to be guessed from + x_end, y_end, and z_end in the header. + """ + # then start guessing... + hdr = self._hdr_dict + # get the ends of the bounding box (highest values in each dimension) + x = hdr['x_end'] + y = hdr['y_end'] + z = hdr['z_end'] + + # compare with possible framing cubes + for fc in [256, 384, 512, 768, 1024]: + if any([d > fc for d in (x, y, z)]): + continue + else: + return fc, fc, fc + + def get_framing_cube(self): + """Get the dimensions of the framing cube. + + Get the dimensions of the framing cube that constitutes the + coordinate system boundaries for the bounding box. + For most BV file formats this need to be guessed from + x_end, y_end, and z_end in the header. + """ + return self._framing_cube + + def set_framing_cube(self, fc): + """Set the dimensions of the framing cube. + + Set the dimensions of the framing cube that constitutes the + coordinate system boundaries for the bounding box + For most BV file formats this need to be guessed from + x_end, y_end, and z_end in the header. + Use this if you know about the framing cube for the BV file. + """ + self._framing_cube = fc + + def get_bbox_center(self): + """Get the center coordinate of the bounding box. + + Get the center coordinate of the bounding box with respect to the + framing cube. + """ + hdr = self._hdr_dict + x = hdr['x_start'] + \ + ((hdr['x_end'] - hdr['x_start']) / 2) + y = hdr['y_start'] + \ + ((hdr['y_end'] - hdr['y_start']) / 2) + z = hdr['z_start'] + \ + ((hdr['z_end'] - hdr['z_start']) / 2) + return z, y, x + + def get_zooms(self): + shape = self.get_data_shape() + return tuple(float(self._hdr_dict['resolution']) + for d in shape[0:3]) + + def set_zooms(self, zooms): + if type(zooms) == int: + self._hdr_dict['resolution'] = zooms + else: + if any([zooms[i] != zooms[i + 1] for i in range(len(zooms) - 1)]): + raise BvError('Zooms for all dimensions must be equal!') + else: + self._hdr_dict['resolution'] = int(zooms[0]) + + def as_analyze_map(self): + raise NotImplementedError + + def set_data_offset(self, offset): + """Set offset into data file to read data.""" + self._data_offset = offset + + def get_data_offset(self): + """Return offset into data file to read data.""" + self.set_data_offset(calc_BV_header_size( + self.hdr_dict_proto, self._hdr_dict)) + return self._data_offset + + def get_slope_inter(self): + """BV formats do not do scaling.""" + return None, None + + def write_to(self, fileobj): + """Write header to fileobj. + + Write starts at fileobj current file position. + + Parameters + ---------- + fileobj : file-like object + Should implement ``write`` method + + Returns + ------- + None + """ + binaryblock = pack_BV_header(self.hdr_dict_proto, self._hdr_dict) + fileobj.write(binaryblock) + + def check_fix(self, logger=None, error_level=None): + """Check BV header with checks.""" + if logger is None: + logger = imageglobals.logger + if error_level is None: + error_level = imageglobals.error_level + battrun = BatteryRunner(self.__class__._get_checks()) + self, reports = battrun.check_fix(self) + for report in reports: + report.log_raise(logger, error_level) + + @classmethod + def _get_checks(klass): + """Return sequence of check functions for this class""" + return (klass._chk_fileversion,) + + ''' Check functions in format expected by BatteryRunner class ''' + + @classmethod + def _chk_fileversion(klass, hdr, fix=False): + rep = Report(HeaderDataError) + if 'version' in hdr._hdr_dict: + version = hdr._hdr_dict['version'] + if version in klass.supported_fileversions: + return hdr, rep + else: + rep.problem_level = 40 + rep.problem_msg = 'fileversion %d is not supported' % version + if fix: + rep.fix_msg = 'not attempting fix' + return hdr, rep + return hdr, rep + + +class BvFileImage(SpatialImage): + """Class to hold information from a BV image file.""" + + # Set the class of the corresponding header + header_class = BvFileHeader + + # Set the label ('image') and the extension ('.bv') for a (dummy) BV file + files_types = (('image', '.bv'),) + + # BV files are not compressed... + _compressed_exts = () + + # use the row-major CArrayProxy + ImageArrayProxy = CArrayProxy + + def update_header(self): + """Harmonize header with image data and affine. + + >>> data = np.zeros((2,3,4)) + >>> affine = np.diag([1.0,2.0,3.0,1.0]) + >>> img = SpatialImage(data, affine) + >>> hdr = img.get_header() + >>> img.shape == (2, 3, 4) + True + >>> img.update_header() + >>> hdr.get_data_shape() == (2, 3, 4) + True + >>> hdr.get_zooms() + (1.0, 2.0, 3.0) + """ + hdr = self._header + shape = self._dataobj.shape + # We need to update the header if the data shape has changed. It's a + # bit difficult to change the data shape using the standard API, but + # maybe it happened + if hdr.get_data_shape() != shape: + hdr.set_data_shape(shape) + + @classmethod + def from_file_map(klass, file_map): + """Load image from `file_map`. + + Parameters + ---------- + file_map : None or mapping, optional + files mapping. If None (default) use object's ``file_map`` + attribute instead + """ + bvf = file_map['image'].get_prepare_fileobj('rb') + header = klass.header_class.from_fileobj(bvf) + affine = header.get_affine() + hdr_copy = header.copy() + # use row-major memory presentation! + data = klass.ImageArrayProxy(bvf, hdr_copy) + img = klass(data, affine, header, file_map=file_map) + img._load_cache = {'header': hdr_copy, + 'affine': None, + 'file_map': copy_file_map(file_map)} + return img + + def _write_header(self, header_file, header): + """Utility routine to write BV header. + + Parameters + ---------- + header_file : file-like + file-like object implementing ``write``, open for writing + header : header object + """ + header.write_to(header_file) + + def _write_data(self, bvfile, data, header): + """Utility routine to write BV image. + + Parameters + ---------- + bvfile : file-like + file-like object implementing ``seek`` or ``tell``, and + ``write`` + data : array-like + array to write + header : analyze-type header object + header + """ + shape = header.get_data_shape() + if data.shape != shape: + raise HeaderDataError('Data should be shape (%s)' % + ', '.join(str(s) for s in shape)) + offset = header.get_data_offset() + out_dtype = header.get_data_dtype() + array_to_file(data, bvfile, out_dtype, offset, order='C') + + def to_file_map(self, file_map=None): + """Write image to `file_map` or contained ``self.file_map``. + + Parameters + ---------- + file_map : None or mapping, optional + files mapping. If None (default) use object's ``file_map`` + attribute instead + """ + if file_map is None: + file_map = self.file_map + data = self.get_data() + with file_map['image'].get_prepare_fileobj('wb') as bvf: + self._write_header(bvf, self.header) + self._write_data(bvf, data, self.header) + self.file_map = file_map diff --git a/nibabel/brainvoyager/bv_msk.py b/nibabel/brainvoyager/bv_msk.py new file mode 100644 index 0000000000..c623c15fbe --- /dev/null +++ b/nibabel/brainvoyager/bv_msk.py @@ -0,0 +1,105 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Reading / writing functions for Brainvoyager (BV) MSK files. + +for documentation on the file format see: +http://www.brainvoyager.com/ubb/Forum8/HTML/000087.html + +Author: Thomas Emmerling +""" +from __future__ import division +from .bv import BvFileHeader, BvFileImage +from ..spatialimages import HeaderDataError + +MSK_HDR_DICT_PROTO = ( + ('resolution', 'h', 3), + ('x_start', 'h', 57), + ('x_end', 'h', 231), + ('y_start', 'h', 52), + ('y_end', 'h', 172), + ('z_start', 'h', 59), + ('z_end', 'h', 197), +) + + +class BvMskHeader(BvFileHeader): + """Class for BrainVoyager MSK header.""" + + # format defaults + allowed_dtypes = [3] + default_dtype = 3 + hdr_dict_proto = MSK_HDR_DICT_PROTO + + def get_data_shape(self): + """Get shape of data.""" + hdr = self._hdr_dict + # calculate dimensions + z = (hdr['z_end'] - + hdr['z_start']) / hdr['resolution'] + y = (hdr['y_end'] - + hdr['y_start']) / hdr['resolution'] + x = (hdr['x_end'] - + hdr['x_start']) / hdr['resolution'] + + return tuple(int(d) for d in [z, y, x]) + + def set_data_shape(self, shape=None, zyx=None): + """Set shape of data. + + To conform with nibabel standards this implements shape. + However, to fill the VtcHeader with sensible information use + the zyxt parameter instead. + + Parameters + ---------- + shape : sequence + sequence of integers specifying data array shape + zyx: 3x2 nested list of integers, optional + [[z_start,z_end],[y_start,y_end],[x_start,x_end]] + array storing borders of data + """ + if (shape is None) and (zyx is None): + raise HeaderDataError('Shape or zyx needs to be specified!') + if shape is not None: + # Use zyx and t parameters instead of shape. + # Dimensions will start from standard coordinates. + if len(shape) != 3: + raise HeaderDataError('Shape for MSK files must be\ + 3 dimensional (ZYX)!') + self._hdr_dict['x_end'] = self._hdr_dict['x_start'] + \ + (shape[2] * self._hdr_dict['resolution']) + self._hdr_dict['y_end'] = self._hdr_dict['y_start'] + \ + (shape[1] * self._hdr_dict['resolution']) + self._hdr_dict['z_end'] = self._hdr_dict['z_start'] + \ + (shape[0] * self._hdr_dict['resolution']) + return + self._hdr_dict['z_start'] = zyx[0][0] + self._hdr_dict['z_end'] = zyx[0][1] + self._hdr_dict['y_start'] = zyx[1][0] + self._hdr_dict['y_end'] = zyx[1][1] + self._hdr_dict['x_start'] = zyx[2][0] + self._hdr_dict['x_end'] = zyx[2][1] + + +class BvMskImage(BvFileImage): + """Class for BrainVoyager MSK masks. + + MSK files are technically binary images + """ + + # Set the class of the corresponding header + header_class = BvMskHeader + + # Set the label ('image') and the extension ('.msk') for a MSK file + files_types = (('image', '.msk'),) + valid_exts = ('.msk',) + _compressed_suffixes = () + +load = BvMskImage.load +save = BvMskImage.instance_to_filename diff --git a/nibabel/brainvoyager/bv_vmp.py b/nibabel/brainvoyager/bv_vmp.py new file mode 100644 index 0000000000..b5e13c14e4 --- /dev/null +++ b/nibabel/brainvoyager/bv_vmp.py @@ -0,0 +1,195 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Reading / writing functions for Brainvoyager (BV) VMP files. + +for documentation on the file format see: +http://support.brainvoyager.com/installation-introduction/23-file-formats/377-users-guide-23-the-format-of-nr-vmp-files.html + +Author: Thomas Emmerling +""" +from __future__ import division +from .bv import (BvFileHeader, BvFileImage, update_BV_header) +from ..spatialimages import HeaderDataError + +VMP_HDR_DICT_PROTO = ( + ('magic_number', 'I', 2712847316), + ('version', 'h', 6), + ('document_type', 'h', 1), + ('nr_of_submaps', 'i', 1), + ('nr_of_timepoints', 'i', 0), + ('nr_of_component_params', 'i', 0), + ('show_params_range_from', 'i', 0), + ('show_params_range_to', 'i', 0), + ('use_for_fingerprint_params_range_from', 'i', 0), + ('use_for_fingerprint_params_range_to', 'i', 0), + ('x_start', 'i', 57), + ('x_end', 'i', 231), + ('y_start', 'i', 52), + ('y_end', 'i', 172), + ('z_start', 'i', 59), + ('z_end', 'i', 197), + ('resolution', 'i', 3), + ('dim_x', 'i', 256), + ('dim_y', 'i', 256), + ('dim_z', 'i', 256), + ('vtc_filename', 'z', b''), + ('prt_filename', 'z', b''), + ('voi_filename', 'z', b''), + ('maps', ( + ('type_of_map', 'i', 1), + ('map_threshold', 'f', 1.6500), + ('upper_threshold', 'f', 8.0), + ('map_name', 'z', b'New Map'), + ('pos_min_r', 'B', 255), + ('pos_min_g', 'B', 0), + ('pos_min_b', 'B', 0), + ('pos_max_r', 'B', 255), + ('pos_max_g', 'B', 255), + ('pos_max_b', 'B', 0), + ('neg_min_r', 'B', 255), + ('neg_min_g', 'B', 0), + ('neg_min_b', 'B', 255), + ('neg_max_r', 'B', 0), + ('neg_max_g', 'B', 0), + ('neg_max_b', 'B', 255), + ('use_vmp_color', 'B', 0), + ('lut_filename', 'z', b''), + ('transparent_color_factor', 'f', 1.0), + ('nr_of_lags', 'i', (0, 'type_of_map', 3)), + ('display_min_lag', 'i', (0, 'type_of_map', 3)), + ('display_max_lag', 'i', (0, 'type_of_map', 3)), + ('show_correlation_or_lag', 'i', (0, 'type_of_map', 3)), + ('cluster_size_threshold', 'i', 50), + ('enable_cluster_size_threshold', 'B', 0), + ('show_values_above_upper_threshold', 'i', 1), + ('df1', 'i', 249), + ('df2', 'i', 0), + ('show_pos_neg_values', 'B', 3), + ('nr_of_used_voxels', 'i', 45555), + ('size_of_fdr_table', 'i', 0), + ('fdr_table_info', ( + ('q', 'f', 0.0), + ('crit_standard', 'f', 0.0), + ('crit_conservative', 'f', 0.0), + ), 'size_of_fdr_table'), + ('use_fdr_table_index', 'i', 0), + ), 'nr_of_submaps'), + ('component_time_points', ( + ('timepoints', (('timepoint', 'f', 0.0),), 'nr_of_timepoints'), + ), 'nr_of_submaps'), + ('component_params', ( + ('param_name', 'z', b''), + ('param_values', (('value', 'f', 0.0),), 'nr_of_submaps') + ), 'nr_of_component_params') +) + + +class BvVmpHeader(BvFileHeader): + ''' Class for BrainVoyager NR-VMP header + ''' + + # format defaults + allowed_dtypes = [2] + default_dtype = 2 + hdr_dict_proto = VMP_HDR_DICT_PROTO + supported_fileversions = [6] + + def get_data_shape(self): + ''' Get shape of data + ''' + hdr = self._hdr_dict + # calculate dimensions + z = (hdr['z_end'] - hdr['z_start']) / hdr['resolution'] + y = (hdr['y_end'] - hdr['y_start']) / hdr['resolution'] + x = (hdr['x_end'] - hdr['x_start']) / hdr['resolution'] + n = hdr['nr_of_submaps'] + return tuple(int(d) for d in [n, z, y, x]) + + def set_data_shape(self, shape=None, zyx=None, n=None): + ''' Set shape of data + To conform with nibabel standards this implements shape. + However, to fill the BvVmpHeader with sensible information use the + zyx and the n parameter instead. + + Parameters + ---------- + shape: sequence + sequence of integers specifying data array shape + zyx: 3x2 nested list of integers, optional + [[z_start,z_end],[y_start,y_end],[x_start,x_end]] + array storing borders of data + n: int, optional + number of submaps + + ''' + hdr_dict_old = self._hdr_dict.copy() + if (shape is None) and (zyx is None) and (n is None): + raise HeaderDataError('Shape, zyx, or n needs to be specified!') + + if ((n is not None) and (n < 1)) or \ + ((shape is not None) and (shape[0] < 1)): + raise HeaderDataError('NR-VMP files need at least one sub-map!') + + if shape is not None: + # Use zyx and t parameters instead of shape. + # Dimensions will start from default coordinates. + if len(shape) != 4: + raise HeaderDataError( + 'Shape for VMP files must be 4 dimensional (NZYX)!') + self._hdr_dict['x_end'] = self._hdr_dict['x_start'] + \ + (shape[3] * self._hdr_dict['resolution']) + self._hdr_dict['y_end'] = self._hdr_dict['y_start'] + \ + (shape[2] * self._hdr_dict['resolution']) + self._hdr_dict['z_end'] = self._hdr_dict['z_start'] + \ + (shape[1] * self._hdr_dict['resolution']) + self._hdr_dict['nr_of_submaps'] = int(shape[0]) + self._hdr_dict = update_BV_header(self.hdr_dict_proto, + hdr_dict_old, self._hdr_dict) + return + if zyx is not None: + self._hdr_dict['z_start'] = zyx[0][0] + self._hdr_dict['z_end'] = zyx[0][1] + self._hdr_dict['y_start'] = zyx[1][0] + self._hdr_dict['y_end'] = zyx[1][1] + self._hdr_dict['x_start'] = zyx[2][0] + self._hdr_dict['x_end'] = zyx[2][1] + if n is not None: + self._hdr_dict['nr_of_submaps'] = int(n) + self._hdr_dict = update_BV_header(self.hdr_dict_proto, + hdr_dict_old, self._hdr_dict) + + def get_framing_cube(self): + ''' Get the dimensions of the framing cube that constitutes + the coordinate system boundaries for the bounding box. + ''' + hdr = self._hdr_dict + return hdr['dim_z'], hdr['dim_y'], hdr['dim_x'] + + def set_framing_cube(self, fc): + ''' Set the dimensions of the framing cube that constitutes + the coordinate system boundaries for the bounding box. + + For VMP files this puts the values also into the header. + ''' + self._hdr_dict['dim_z'] = fc[0] + self._hdr_dict['dim_y'] = fc[1] + self._hdr_dict['dim_x'] = fc[2] + self._framing_cube = fc + + +class BvVmpImage(BvFileImage): + # Set the class of the corresponding header + header_class = BvVmpHeader + + # Set the label ('image') and the extension ('.vmp') for a VMP file + files_types = (('image', '.vmp'),) + valid_exts = ('.vmp',) + +load = BvVmpImage.load +save = BvVmpImage.instance_to_filename diff --git a/nibabel/brainvoyager/bv_vmr.py b/nibabel/brainvoyager/bv_vmr.py new file mode 100644 index 0000000000..6ee244c01e --- /dev/null +++ b/nibabel/brainvoyager/bv_vmr.py @@ -0,0 +1,255 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Reading / writing functions for Brainvoyager (BV) VMR files. + +for documentation on the file format see: +http://support.brainvoyager.com/automation-aamp-development/23-file-formats/385-developer-guide-26-the-format-of-vmr-files.html + +Author: Sabrina Fontanella and Thomas Emmerling +""" +from __future__ import division +from .bv import (BvError, BvFileHeader, BvFileImage, parse_BV_header, + pack_BV_header, calc_BV_header_size, combine_st, parse_st) +from ..spatialimages import HeaderDataError +from ..batteryrunners import Report +import numpy as np + + +VMR_PRHDR_DICT_PROTO = ( + ('version', 'h', 4), + ('dim_x', 'h', 256), + ('dim_y', 'h', 256), + ('dim_z', 'h', 256), +) + +VMR_PSHDR_DICT_PROTO = ( + ('offset_x', 'h', 0), + ('offset_y', 'h', 0), + ('offset_z', 'h', 0), + ('framing_cube', 'h', 256), + ('pos_infos_verified', 'i', 0), + ('coordinate_system_entry', 'i', 1), + ('slice_first_center_x', 'f', 127.5), + ('slice_first_center_y', 'f', 0.0), + ('slice_first_center_z', 'f', 0.0), + ('slice_last_center_x', 'f', -127.5), + ('slice_last_center_y', 'f', 0.0), + ('slice_last_center_z', 'f', 0.0), + ('row_dir_x', 'f', 0.0), + ('row_dir_y', 'f', 1.0), + ('row_dir_z', 'f', 0.0), + ('col_dir_x', 'f', 0.0), + ('col_dir_y', 'f', 0.0), + ('col_dir_z', 'f', -1.0), + ('nr_rows', 'i', 256), + ('nr_cols', 'i', 256), + ('fov_row_dir', 'f', 256.0), + ('fov_col_dir', 'f', 256.0), + ('slice_thickness', 'f', 1.0), + ('gap_thickness', 'f', 0.0), + ('nr_of_past_spatial_trans', 'i', 0), + ('past_spatial_trans', ( + ('name', 'z', b''), + ('type', 'i', b''), + ('source_file', 'z', b''), + ('nr_of_trans_val', 'i', b''), + ('trans_val', (('value', 'f', 0.0),), 'nr_of_trans_val') + ), 'nr_of_past_spatial_trans'), + ('lr_convention', 'B', 1), + ('reference_space', 'B', 0), + ('vox_res_x', 'f', 1.0), + ('vox_res_y', 'f', 1.0), + ('vox_res_z', 'f', 1.0), + ('flag_vox_resolution', 'B', 0), + ('flag_tal_space', 'B', 0), + ('min_intensity', 'i', 0), + ('mean_intensity', 'i', 127), + ('max_intensity', 'i', 255), +) + + +def computeOffsetPostHDR(hdr_dict, fileobj): + currentSeek = fileobj.tell() + return currentSeek + (hdr_dict['dim_x'] * hdr_dict['dim_y'] * + hdr_dict['dim_z']) + + +def concatePrePos(preDict, posDict): + temp = preDict.copy() + temp.update(posDict) + return temp + + +class BvVmrHeader(BvFileHeader): + """Class for BrainVoyager VMR header.""" + + # format defaults + default_endianness = '<' + allowed_dtypes = [3] + default_dtype = 3 + hdr_dict_proto = VMR_PRHDR_DICT_PROTO + VMR_PSHDR_DICT_PROTO + supported_fileversions = [4] + + def get_data_shape(self): + hdr = self._hdr_dict + # calculate dimensions + z = hdr['dim_z'] + y = hdr['dim_y'] + x = hdr['dim_x'] + return tuple(int(d) for d in [z, y, x]) + + def set_data_shape(self, shape): + """Set shape of data. + + Parameters + ---------- + shape : sequence + sequence of integers specifying data array shape + """ + if len(shape) != 3: + raise HeaderDataError( + 'Shape for VMR files must be 3 dimensional (ZYX)!') + self._hdr_dict['dim_x'] = shape[2] + self._hdr_dict['dim_y'] = shape[1] + self._hdr_dict['dim_z'] = shape[0] + + def set_data_offset(self, offset): + """Set offset into data file to read data. + + The offset is always 8 for VMR files. + """ + self._data_offset = 8 + + def get_data_offset(self): + """Return offset into data file to read data. + + The offset is always 8 for VMR files. + """ + return 8 + + def set_xflip(self, xflip): + if xflip is True: + self._hdr_dict['lr_convention'] = 1 + elif xflip is False: + self._hdr_dict['lr_convention'] = 2 + else: + self._hdr_dict['lr_convention'] = 0 + + def get_xflip(self): + xflip = int(self._hdr_dict['lr_convention']) + if xflip == 1: + return True + elif xflip == 2: + return False + else: + raise BvError('Left-right convention is unknown!') + + def get_base_affine(self): + """Get affine from VMR header fields. + + Internal storage of the image is ZYXT, where (in patient coordiante/ + real world orientations): + Z := axis increasing from right to left (R to L) + Y := axis increasing from superior to inferior (S to I) + X := axis increasing from anterior to posterior (A to P) + T := volumes (if present in file format) + """ + zooms = self.get_zooms() + if not self.get_xflip(): + # make the BV internal Z axis neurological (left-is-left); + # not default in BV files! + zooms[0] *= -1 + + # compute the rotation + rot = np.zeros((3, 3)) + # make the flipped BV Z axis the new R axis + rot[:, 0] = [-zooms[0], 0, 0] + # make the flipped BV X axis the new A axis + rot[:, 1] = [0, 0, -zooms[2]] + # make the flipped BV Y axis the new S axis + rot[:, 2] = [0, -zooms[1], 0] + + # compute the translation + fcc = np.array(self.get_framing_cube()) / 2 # center of framing cube + bbc = np.array(self.get_bbox_center()) # center of bounding box + tra = np.dot((bbc - fcc), rot) + + # assemble + M = np.eye(4, 4) + M[0:3, 0:3] = rot + M[0:3, 3] = tra.T + + # look for additional transformations in past_spatial_trans and combine + # with M + if self._hdr_dict['past_spatial_trans']: + STarray = np.zeros((len(self._hdr_dict['past_spatial_trans']), + 4, 4)) + for st in range(len(self._hdr_dict['past_spatial_trans'])): + STarray[st, :, :] = \ + parse_st(self._hdr_dict['past_spatial_trans'][st]) + combinedST = combine_st(STarray, inv=True) + M = np.dot(M, combinedST) + return M + + get_best_affine = get_base_affine + + get_default_affine = get_base_affine + + get_affine = get_base_affine + + @classmethod + def from_fileobj(klass, fileobj, endianness=default_endianness, + check=True): + hdr_dictPre = parse_BV_header(VMR_PRHDR_DICT_PROTO, fileobj) + # calculate new seek for the post data header + newSeek = computeOffsetPostHDR(hdr_dictPre, fileobj) + fileobj.seek(newSeek) + hdr_dictPos = parse_BV_header(VMR_PSHDR_DICT_PROTO, fileobj) + hdr_dict = concatePrePos(hdr_dictPre, hdr_dictPos) + # The offset is always 8 for VMR files. + offset = 8 + return klass(hdr_dict, endianness, check, offset) + + def get_bbox_center(self): + """Get the center coordinate of the bounding box. + Not required for VMR files + """ + return np.array([self.get_framing_cube() / 2 for d in range(3)]) + + def get_zooms(self): + return (self._hdr_dict['vox_res_z'], self._hdr_dict['vox_res_y'], + self._hdr_dict['vox_res_x']) + + def set_zooms(self, zooms): + self._hdr_dict['vox_res_z'] = float(zooms[0]) + self._hdr_dict['vox_res_y'] = float(zooms[1]) + self._hdr_dict['vox_res_x'] = float(zooms[2]) + + def write_to(self, fileobj): + binaryblock = pack_BV_header(self.hdr_dict_proto, self._hdr_dict) + # calculate size of preDataHeader + sizePrH = calc_BV_header_size(VMR_PRHDR_DICT_PROTO, self._hdr_dict) + # write the preHeader + fileobj.write(binaryblock[0:sizePrH]) + fileobj.seek(computeOffsetPostHDR(self._hdr_dict, fileobj)) + fileobj.write(binaryblock[sizePrH:]) + + +class BvVmrImage(BvFileImage): + """Class for BrainVoyager VMR images.""" + + # Set the class of the corresponding header + header_class = BvVmrHeader + + # Set the label ('image') and the extension ('.vtc') for a VMR file + files_types = (('image', '.vmr'),) + valid_exts = ('.vmr',) + +load = BvVmrImage.load +save = BvVmrImage.instance_to_filename diff --git a/nibabel/brainvoyager/bv_vtc.py b/nibabel/brainvoyager/bv_vtc.py new file mode 100644 index 0000000000..a1d76c6a15 --- /dev/null +++ b/nibabel/brainvoyager/bv_vtc.py @@ -0,0 +1,155 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Reading / writing functions for Brainvoyager (BV) VTC files. + +for documentation on the file format see: +http://support.brainvoyager.com/installation-introduction/23-file-formats/379-users-guide-23-the-format-of-vtc-files.html + +Author: Thomas Emmerling +""" +from __future__ import division +from .bv import BvError, BvFileHeader, BvFileImage +from ..spatialimages import HeaderDataError +from ..batteryrunners import Report + +VTC_HDR_DICT_PROTO = ( + ('version', 'h', 3), + ('fmr', 'z', b''), + ('nr_prts', 'h', 0), + ('prts', (('filename', 'z', b''),), 'nr_prts'), + ('current_prt', 'h', 0), + ('datatype', 'h', 2), + ('volumes', 'h', 0), + ('resolution', 'h', 3), + ('x_start', 'h', 57), + ('x_end', 'h', 231), + ('y_start', 'h', 52), + ('y_end', 'h', 172), + ('z_start', 'h', 59), + ('z_end', 'h', 197), + ('lr_convention', 'b', 1), + ('ref_space', 'b', 3), + ('tr', 'f', 2000.0), +) + + +class BvVtcHeader(BvFileHeader): + """Header for Brainvoyager (BV) VTC files. + + For documentation on the file format see: + http://support.brainvoyager.com/installation-introduction/23-file-formats/379-users-guide-23-the-format-of-vtc-files.html + """ + + """ + Header for Brainvoyager (BV) VTC files. + + For documentation on the file format see: + http://support.brainvoyager.com/installation-introduction/23-file-formats/379-users-guide-23-the-format-of-vtc-files.html + """ + + # format defaults + allowed_dtypes = [1, 2] + default_dtype = 2 + hdr_dict_proto = VTC_HDR_DICT_PROTO + supported_fileversions = [3] + + def get_data_shape(self): + """Get shape of data.""" + hdr = self._hdr_dict + # calculate dimensions + z = (hdr['z_end'] - + hdr['z_start']) / hdr['resolution'] + y = (hdr['y_end'] - + hdr['y_start']) / hdr['resolution'] + x = (hdr['x_end'] - + hdr['x_start']) / hdr['resolution'] + t = hdr['volumes'] + return tuple(int(d) for d in [z, y, x, t]) + + def set_data_shape(self, shape=None, zyx=None, t=None): + """Set shape of data. + + To conform with nibabel standards this implements shape. + However, to fill the BvVtcHeader with sensible information + use the zyx and the t parameter instead. + + Parameters + ---------- + shape : sequence + sequence of integers specifying data array shape + zyx: 3x2 nested list of integers, optional + [[z_start,z_end],[y_start,y_end],[x_start,x_end]] + array storing borders of data + t: int + number of volumes + """ + if (shape is None) and (zyx is None) and (t is None): + raise HeaderDataError('Shape, zyx, or t needs to be specified!') + if ((t is not None) and (t < 0)) or \ + ((shape is not None) and (shape[3] < 0)): + raise HeaderDataError('VTC files need at least one volume!') + if shape is not None: + # Use zyx and t parameters instead of shape. + # Dimensions will start from standard coordinates. + if len(shape) != 4: + raise HeaderDataError( + 'Shape for VTC files must be 4 dimensional (ZYXT)!') + self._hdr_dict['x_end'] = \ + self._hdr_dict['x_start'] + \ + (shape[2] * self._hdr_dict['resolution']) + self._hdr_dict['y_end'] = \ + self._hdr_dict['y_start'] + \ + (shape[1] * self._hdr_dict['resolution']) + self._hdr_dict['z_end'] = \ + self._hdr_dict['z_start'] + \ + (shape[0] * self._hdr_dict['resolution']) + self._hdr_dict['volumes'] = shape[3] + return + if zyx is not None: + self._hdr_dict['z_start'] = zyx[0][0] + self._hdr_dict['z_end'] = zyx[0][1] + self._hdr_dict['y_start'] = zyx[1][0] + self._hdr_dict['y_end'] = zyx[1][1] + self._hdr_dict['x_start'] = zyx[2][0] + self._hdr_dict['x_end'] = zyx[2][1] + if t is not None: + self._hdr_dict['volumes'] = t + + def get_xflip(self): + """Get xflip for data.""" + xflip = int(self._hdr_dict['lr_convention']) + if xflip == 1: + return True + elif xflip == 2: + return False + else: + raise BvError('Left-right convention is unknown!') + + def set_xflip(self, xflip): + """Set xflip for data.""" + if xflip is True: + self._hdr_dict['lr_convention'] = 1 + elif xflip is False: + self._hdr_dict['lr_convention'] = 2 + else: + self._hdr_dict['lr_convention'] = 0 + + +class BvVtcImage(BvFileImage): + """Class for BrainVoyager VTC images.""" + + # Set the class of the corresponding header + header_class = BvVtcHeader + + # Set the label ('image') and the extension ('.vtc') for a VTC file + files_types = (('image', '.vtc'),) + valid_exts = ('.vtc',) + +load = BvVtcImage.load +save = BvVtcImage.instance_to_filename diff --git a/nibabel/brainvoyager/tests/__init__.py b/nibabel/brainvoyager/tests/__init__.py new file mode 100644 index 0000000000..e4659cfab1 --- /dev/null +++ b/nibabel/brainvoyager/tests/__init__.py @@ -0,0 +1,23 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +''' Tests for BrainVoyager file formats ''' +from .test_bv_vtc import BVVTC_EXAMPLE_IMAGES, BVVTC_EXAMPLE_HDRS +from .test_bv_msk import BVMSK_EXAMPLE_IMAGES, BVMSK_EXAMPLE_HDRS +from .test_bv_vmp import BVVMP_EXAMPLE_IMAGES, BVVMP_EXAMPLE_HDRS +from .test_bv_vmr import BVVMR_EXAMPLE_IMAGES, BVVMR_EXAMPLE_HDRS + +__all__ = ('BVVTC_EXAMPLE_IMAGES', 'BVMSK_EXAMPLE_IMAGES', + 'BVVMP_EXAMPLE_IMAGES', 'BVVMR_EXAMPLE_IMAGES') + +# assemble example images and corresponding example headers for testing +BV_EXAMPLE_IMAGES = (BVVTC_EXAMPLE_IMAGES, BVMSK_EXAMPLE_IMAGES, + BVVMP_EXAMPLE_IMAGES, BVVMR_EXAMPLE_IMAGES) + +BV_EXAMPLE_HDRS = (BVVTC_EXAMPLE_HDRS, BVMSK_EXAMPLE_HDRS, + BVVMP_EXAMPLE_HDRS, BVVMR_EXAMPLE_HDRS) diff --git a/nibabel/brainvoyager/tests/test_bv.py b/nibabel/brainvoyager/tests/test_bv.py new file mode 100644 index 0000000000..bd651f86ac --- /dev/null +++ b/nibabel/brainvoyager/tests/test_bv.py @@ -0,0 +1,284 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Test main BV module.""" + +import os +import numpy as np +from ...loadsave import load +from ...tmpdirs import InTemporaryDirectory +from ..bv import (readCString, parse_BV_header, pack_BV_header, BvFileHeader, + calc_BV_header_size, _proto2default, update_BV_header, + parse_st, combine_st, BvError) +from ..bv_vtc import VTC_HDR_DICT_PROTO +from ..bv_vmr import BvVmrImage +from ...testing import (assert_equal, assert_array_equal, data_path, + assert_true, assert_false, assert_raises) +from . import BV_EXAMPLE_IMAGES, BV_EXAMPLE_HDRS +from ...externals import OrderedDict + + +vtc_file = os.path.join(data_path, 'test.vtc') +vmp_file = os.path.join(data_path, 'test.vmp') +vmr_file = os.path.join(data_path, 'test.vmr') + +TEST_PROTO = ( + ('some_signed_char', 'b', 1), + ('some_unsigned_char', 'B', 255), + ('some_signed_short_integer', 'h', 6), + ('some_signed_integer', 'i', 1), + ('another_signed_integer', 'i', 3), + ('some_counter_integer', 'i', 4), + ('another_counter_integer', 'i', 0), + ('some_unsigned_long_integer', 'I', 2712847316), + ('some_float', 'f', 1.0), + ('some_zero_terminated_string', 'z', b'HelloWorld!'), + ('some_conditional_integer', 'i', (0, 'some_signed_integer', 1)), + ('another_conditional_integer', 'i', (23, 'another_signed_integer', 1)), + ('some_nested_field', ( + ('a_number', 'i', 1), + ('a_float', 'f', 1.6500), + ('a_string', 'z', b'test.txt'), + ('nested_counter_integer', 'i', 2), + ('fdr_table_info', ( + ('another_float', 'f', 0.0), + ('another_string', 'z', b'sample'), + ), 'nested_counter_integer'), + ), 'some_counter_integer'), + ('another_nested_field', ( + ('b_float', 'f', 1.234), + ), 'another_counter_integer'), +) + +TEST_HDR = OrderedDict([ + ('some_signed_char', 1), + ('some_unsigned_char', 255), + ('some_signed_short_integer', 6), + ('some_signed_integer', 1), + ('another_signed_integer', 3), + ('some_counter_integer', 4), + ('another_counter_integer', 0), + ('some_unsigned_long_integer', 2712847316), + ('some_float', 1.0), + ('some_zero_terminated_string', 'HelloWorld!'), + ('some_conditional_integer', 0), + ('another_conditional_integer', 23), + ('some_nested_field', + [OrderedDict([('a_number', 1), + ('a_float', 1.65), + ('a_string', 'test.txt'), + ('nested_counter_integer', 2), + ('fdr_table_info', + [OrderedDict([('another_float', 0.0), + ('another_string', 'sample')]), + OrderedDict([('another_float', 0.0), + ('another_string', 'sample')])])]), + OrderedDict([('a_number', 1), + ('a_float', 1.65), + ('a_string', 'test.txt'), + ('nested_counter_integer', 2), + ('fdr_table_info', + [OrderedDict([('another_float', 0.0), + ('another_string', 'sample')]), + OrderedDict([('another_float', 0.0), + ('another_string', 'sample')])])]), + OrderedDict([('a_number', 1), + ('a_float', 1.65), + ('a_string', 'test.txt'), + ('nested_counter_integer', 2), + ('fdr_table_info', + [OrderedDict([('another_float', 0.0), + ('another_string', 'sample')]), + OrderedDict([('another_float', 0.0), + ('another_string', 'sample')])])]), + OrderedDict([('a_number', 1), + ('a_float', 1.65), + ('a_string', 'test.txt'), + ('nested_counter_integer', 2), + ('fdr_table_info', + [OrderedDict([('another_float', 0.0), + ('another_string', 'sample')]), + OrderedDict([('another_float', 0.0), + ('another_string', + 'sample')])])])]), + ('another_nested_field', [])]) + +TEST_HDR_PACKED = \ + b''.join([b'\x01\xff\x06\x00\x01\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00', + b'\x00\x00\x00\x00\x00\xd4\xc3\xb2\xa1\x00\x00\x80?HelloWorld!', + b'\x00\x00\x00\x00\x00\x01\x00\x00\x0033\xd3?test.txt\x00\x02', + b'\x00\x00\x00\x00\x00\x00\x00sample\x00\x00\x00\x00\x00sample', + b'\x00\x01\x00\x00\x0033\xd3?test.txt\x00\x02\x00\x00\x00\x00', + b'\x00\x00\x00sample\x00\x00\x00\x00\x00sample\x00\x01\x00\x00', + b'\x0033\xd3?test.txt\x00\x02\x00\x00\x00\x00\x00\x00\x00sample', + b'\x00\x00\x00\x00\x00sample\x00\x01\x00\x00\x0033\xd3?test.txt', + b'\x00\x02\x00\x00\x00\x00\x00\x00\x00sample\x00\x00\x00\x00', + b'\x00sample\x00']) + + +def test_readCString(): + # sample binary block + binary = b'test.fmr\x00test.prt\x00' + with InTemporaryDirectory(): + # create a tempfile + path = 'test.header' + fwrite = open(path, 'wb') + + # write the binary block to it + fwrite.write(binary) + fwrite.close() + + # open it again + fread = open(path, 'rb') + + # test readout of one string + assert_equal([s for s in readCString(fread)], [b'test.fmr']) + + # test new file position + assert_equal(fread.tell(), 9) + + # manually rewind + fread.seek(0) + + # test readout of two strings + assert_equal([s for s in readCString(fread, 2, rewind=True)], + [b'test.fmr', b'test.prt']) + + # test automatic rewind + assert_equal(fread.tell(), 0) + + # test readout of two strings with trailing zeros + assert_equal([s for s in readCString(fread, 2, strip=False)], + [b'test.fmr\x00', b'test.prt\x00']) + + # test new file position + assert_equal(fread.tell(), 18) + + # test readout of one string from given position + fread.seek(0) + assert_equal([s for s in readCString(fread, startPos=9)], + [b'test.prt']) + fread.close() + + +def test_combine_st(): + vmr = BvVmrImage.from_filename(vmr_file) + STarray = [] + for st in range(vmr.header._hdr_dict['nr_of_past_spatial_trans']): + STarray.append(parse_st( + vmr.header._hdr_dict['past_spatial_trans'][st])) + STarray = np.array(STarray) + combinedST = combine_st(STarray, inv=True) + correctCombinedST = [[1., 0., 0., 0.], + [0., 1., 0., -1.], + [0., 0., 1., 1.], + [0., 0., 0., 1.]] + assert_array_equal(combinedST, correctCombinedST) + combinedST = combine_st(STarray, inv=False) + correctCombinedST = [[1., 0., 0., 0.], + [0., 1., 0., 1.], + [0., 0., 1., -1.], + [0., 0., 0., 1.]] + assert_array_equal(combinedST, correctCombinedST) + + +def test_parse_st(): + vmr = BvVmrImage.from_filename(vmr_file) + ST = parse_st(vmr.header._hdr_dict['past_spatial_trans'][0]) + correctST = [[1., 0., 0., -1.], + [0., 1., 0., 0.], + [0., 0., 1., -1.], + [0., 0., 0., 1.]] + assert_array_equal(ST, correctST) + + # parse_st will only handle 4x4 matrices + vmr.header._hdr_dict['past_spatial_trans'][0]['nr_of_trans_val'] = 10 + assert_raises(BvError, parse_st, + vmr.header._hdr_dict['past_spatial_trans'][0]) + + +def compare_header_values(header, expected_header): + '''recursively compare every value in header with expected_header''' + + for key in header: + if (type(header[key]) is list): + for i in range(len(expected_header[key])): + compare_header_values(header[key][i], expected_header[key][i]) + assert_equal(header[key], expected_header[key]) + + +def test_BvFileHeader_parse_BV_header(): + bv = _proto2default(TEST_PROTO) + compare_header_values(bv, TEST_HDR) + + +def test_BvFileHeader_pack_BV_header(): + bv = _proto2default(TEST_PROTO) + packed_bv = pack_BV_header(TEST_PROTO, bv) + assert_equal(packed_bv, TEST_HDR_PACKED) + + # open vtc test file + fileobj = open(vtc_file, 'rb') + hdr_dict = parse_BV_header(VTC_HDR_DICT_PROTO, fileobj) + binaryblock = pack_BV_header(VTC_HDR_DICT_PROTO, hdr_dict) + assert_equal(binaryblock, b''.join([ + b'\x03\x00test.fmr\x00\x01\x00test.prt\x00\x00\x00\x02\x00\x05\x00', + b'\x03\x00x\x00\x96\x00x\x00\x96\x00x\x00\x96\x00\x01\x01\x00\x00\xfaD' + ])) + + +def test_BvFileHeader_calc_BV_header_size(): + bv = _proto2default(TEST_PROTO) + assert_equal(calc_BV_header_size(TEST_PROTO, bv), 216) + + # change a header field + bv['some_zero_terminated_string'] = 'AnotherString' + assert_equal(calc_BV_header_size(TEST_PROTO, bv), 218) + + # open vtc test file + fileobj = open(vtc_file, 'rb') + hdr_dict = parse_BV_header(VTC_HDR_DICT_PROTO, fileobj) + hdrSize = calc_BV_header_size(VTC_HDR_DICT_PROTO, hdr_dict) + assert_equal(hdrSize, 48) + + +def test_BvFileHeader_update_BV_header(): + # increase a nested field counter + bv = _proto2default(TEST_PROTO) + bv_new = bv.copy() + bv_new['some_counter_integer'] = 5 + bv_updated = update_BV_header(TEST_PROTO, bv, bv_new) + assert_equal(len(bv_updated['some_nested_field']), 5) + + # decrease a nested field counter + bv = _proto2default(TEST_PROTO) + bv_new = bv.copy() + bv_new['some_counter_integer'] = 3 + bv_updated = update_BV_header(TEST_PROTO, bv, bv_new) + assert_equal(len(bv_updated['some_nested_field']), 3) + + +def test_BvFileHeader_xflip(): + bv = BvFileHeader() + assert_true(bv.get_xflip()) + + # should only return + bv.set_xflip(True) + + assert_raises(BvError, bv.set_xflip, False) + + +def test_BvFileHeader_endianness(): + assert_raises(BvError, BvFileHeader, endianness='>') + + +def test_parse_all_BV_headers(): + for images, headers in zip(BV_EXAMPLE_IMAGES, BV_EXAMPLE_HDRS): + for i in range(len(images)): + image = load(images[i]['fname']) + compare_header_values(image.header._hdr_dict, headers[i]) diff --git a/nibabel/brainvoyager/tests/test_bv_msk.py b/nibabel/brainvoyager/tests/test_bv_msk.py new file mode 100644 index 0000000000..c17bc4f6b9 --- /dev/null +++ b/nibabel/brainvoyager/tests/test_bv_msk.py @@ -0,0 +1,61 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Test BV module for MSK files.""" + +from os.path import join as pjoin +import numpy as np +from ..bv_msk import BvMskImage, BvMskHeader +from ...testing import (assert_equal, assert_raises, data_path) +from ...spatialimages import HeaderDataError +from ...externals import OrderedDict + +# Example images in format expected for ``test_image_api``, adding ``zooms`` +# item. +BVMSK_EXAMPLE_IMAGES = [ + dict( + fname=pjoin(data_path, 'test.msk'), + shape=(10, 10, 10), + dtype=np.uint8, + affine=np.array([[-3., 0, 0, -21.], + [0, 0, -3., -21.], + [0, -3., 0, -21.], + [0, 0, 0, 1.]]), + zooms=(3., 3., 3.), + fileformat=BvMskImage, + # These values are from NeuroElf + data_summary=dict( + min=0, + max=1, + mean=0.499), + is_proxy=True) +] + +BVMSK_EXAMPLE_HDRS = [ + OrderedDict([('resolution', 3), + ('x_start', 120), + ('x_end', 150), + ('y_start', 120), + ('y_end', 150), + ('z_start', 120), + ('z_end', 150)]) +] + + +def test_BvMskHeader_set_data_shape(): + msk = BvMskHeader() + assert_equal(msk.get_data_shape(), (46, 40, 58)) + msk.set_data_shape((45, 39, 57)) + assert_equal(msk.get_data_shape(), (45, 39, 57)) + + # Use zyx parameter instead of shape + msk.set_data_shape(None, [[57, 240], [52, 178], [59, 191]]) + assert_equal(msk.get_data_shape(), (61, 42, 44)) + + # raise error when neither shape nor xyz is specified + assert_raises(HeaderDataError, msk.set_data_shape, None, None) diff --git a/nibabel/brainvoyager/tests/test_bv_vmp.py b/nibabel/brainvoyager/tests/test_bv_vmp.py new file mode 100644 index 0000000000..c68ec1c701 --- /dev/null +++ b/nibabel/brainvoyager/tests/test_bv_vmp.py @@ -0,0 +1,357 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Test BV module for VMP files.""" + +from os.path import join as pjoin +import numpy as np +from ..bv_vmp import BvVmpImage, BvVmpHeader +from ...testing import (assert_equal, assert_raises, data_path) +from ...spatialimages import HeaderDataError +from ...externals import OrderedDict + +# Example images in format expected for ``test_image_api``, adding ``zooms`` +# item. +BVVMP_EXAMPLE_IMAGES = [ + dict( + fname=pjoin(data_path, 'test.vmp'), + shape=(1, 10, 10, 10), + dtype=np.float32, + affine=np.array([[-3., 0, 0, -21.], + [0, 0, -3., -21.], + [0, -3., 0, -21.], + [0, 0, 0, 1.]]), + zooms=(3., 3., 3.), + fileformat=BvVmpImage, + # These values are from NeuroElf + data_summary=dict( + min=0.0033484352752566338, + max=7.996956825256348, + mean=3.9617851), + is_proxy=True), + dict( + fname=pjoin(data_path, 'test2.vmp'), + shape=(2, 10, 10, 10), + dtype=np.float32, + affine=np.array([[-3., 0, 0, -21.], + [0, 0, -3., -21.], + [0, -3., 0, -21.], + [0, 0, 0, 1.]]), + zooms=(3., 3., 3.), + fileformat=BvVmpImage, + # These values are from NeuroElf + data_summary=dict( + min=0.0033484352752566338, + max=7.996956825256348, + mean=3.9617851), + is_proxy=True), + dict( + fname=pjoin(data_path, 'test3.vmp'), + shape=(1, 10, 10, 10), + dtype=np.float32, + affine=np.array([[-3., 0, 0, -21.], + [0, 0, -3., -21.], + [0, -3., 0, -21.], + [0, 0, 0, 1.]]), + zooms=(3., 3., 3.), + fileformat=BvVmpImage, + # These values are from NeuroElf + data_summary=dict( + min=0.0, + max=7.163260459899902, + mean=2.1438238620758057), + is_proxy=True) +] + +BVVMP_EXAMPLE_HDRS = [ + OrderedDict([('magic_number', 2712847316), + ('version', 6), + ('document_type', 1), + ('nr_of_submaps', 1), + ('nr_of_timepoints', 0), + ('nr_of_component_params', 0), + ('show_params_range_from', 0), + ('show_params_range_to', 0), + ('use_for_fingerprint_params_range_from', 0), + ('use_for_fingerprint_params_range_to', 0), + ('x_start', 120), + ('x_end', 150), + ('y_start', 120), + ('y_end', 150), + ('z_start', 120), + ('z_end', 150), + ('resolution', 3), + ('dim_x', 256), + ('dim_y', 256), + ('dim_z', 256), + ('vtc_filename', 'test.vtc'), + ('prt_filename', ''), + ('voi_filename', ''), + ('maps', + [OrderedDict([('type_of_map', 1), + ('map_threshold', 1.649999976158142), + ('upper_threshold', 8.0), + ('map_name', 'Testmap'), + ('pos_min_r', 255), + ('pos_min_g', 0), + ('pos_min_b', 0), + ('pos_max_r', 255), + ('pos_max_g', 255), + ('pos_max_b', 0), + ('neg_min_r', 255), + ('neg_min_g', 0), + ('neg_min_b', 255), + ('neg_max_r', 0), + ('neg_max_g', 0), + ('neg_max_b', 255), + ('use_vmp_color', 0), + ('lut_filename', ''), + ('transparent_color_factor', 1.0), + ('nr_of_lags', 0), + ('display_min_lag', 0), + ('display_max_lag', 0), + ('show_correlation_or_lag', 0), + ('cluster_size_threshold', 50), + ('enable_cluster_size_threshold', 0), + ('show_values_above_upper_threshold', 1), + ('df1', 249), + ('df2', 1), + ('show_pos_neg_values', 3), + ('nr_of_used_voxels', 45555), + ('size_of_fdr_table', 0), + ('fdr_table_info', []), + ('use_fdr_table_index', 0)])]), + ('component_time_points', [OrderedDict([('timepoints', [])])]), + ('component_params', [])]), + OrderedDict([('magic_number', 2712847316), + ('version', 6), + ('document_type', 1), + ('nr_of_submaps', 2), + ('nr_of_timepoints', 0), + ('nr_of_component_params', 0), + ('show_params_range_from', 0), + ('show_params_range_to', 0), + ('use_for_fingerprint_params_range_from', 0), + ('use_for_fingerprint_params_range_to', 0), + ('x_start', 120), + ('x_end', 150), + ('y_start', 120), + ('y_end', 150), + ('z_start', 120), + ('z_end', 150), + ('resolution', 3), + ('dim_x', 256), + ('dim_y', 256), + ('dim_z', 256), + ('vtc_filename', 'test.vtc'), + ('prt_filename', ''), + ('voi_filename', ''), + ('maps', + [OrderedDict([('type_of_map', 1), + ('map_threshold', 1.649999976158142), + ('upper_threshold', 8.0), + ('map_name', 'Testmap'), + ('pos_min_r', 255), + ('pos_min_g', 0), + ('pos_min_b', 0), + ('pos_max_r', 255), + ('pos_max_g', 255), + ('pos_max_b', 0), + ('neg_min_r', 255), + ('neg_min_g', 0), + ('neg_min_b', 255), + ('neg_max_r', 0), + ('neg_max_g', 0), + ('neg_max_b', 255), + ('use_vmp_color', 0), + ('lut_filename', ''), + ('transparent_color_factor', 1.0), + ('nr_of_lags', 0), + ('display_min_lag', 0), + ('display_max_lag', 0), + ('show_correlation_or_lag', 0), + ('cluster_size_threshold', 50), + ('enable_cluster_size_threshold', 0), + ('show_values_above_upper_threshold', 1), + ('df1', 249), + ('df2', 1), + ('show_pos_neg_values', 3), + ('nr_of_used_voxels', 45555), + ('size_of_fdr_table', 0), + ('fdr_table_info', []), + ('use_fdr_table_index', 0)]), + OrderedDict([('type_of_map', 1), + ('map_threshold', 1.649999976158142), + ('upper_threshold', 8.0), + ('map_name', 'Testmap'), + ('pos_min_r', 255), + ('pos_min_g', 0), + ('pos_min_b', 0), + ('pos_max_r', 255), + ('pos_max_g', 255), + ('pos_max_b', 0), + ('neg_min_r', 255), + ('neg_min_g', 0), + ('neg_min_b', 255), + ('neg_max_r', 0), + ('neg_max_g', 0), + ('neg_max_b', 255), + ('use_vmp_color', 0), + ('lut_filename', ''), + ('transparent_color_factor', 1.0), + ('nr_of_lags', 0), + ('display_min_lag', 0), + ('display_max_lag', 0), + ('show_correlation_or_lag', 0), + ('cluster_size_threshold', 50), + ('enable_cluster_size_threshold', 0), + ('show_values_above_upper_threshold', 1), + ('df1', 249), + ('df2', 1), + ('show_pos_neg_values', 3), + ('nr_of_used_voxels', 45555), + ('size_of_fdr_table', 0), + ('fdr_table_info', []), + ('use_fdr_table_index', 0)])]), + ('component_time_points', + [OrderedDict([('timepoints', [])]), + OrderedDict([('timepoints', [])])]), + ('component_params', [])]), + OrderedDict([('magic_number', 2712847316), + ('version', 6), + ('document_type', 1), + ('nr_of_submaps', 1), + ('nr_of_timepoints', 0), + ('nr_of_component_params', 0), + ('show_params_range_from', 0), + ('show_params_range_to', 0), + ('use_for_fingerprint_params_range_from', 0), + ('use_for_fingerprint_params_range_to', 0), + ('x_start', 102), + ('x_end', 108), + ('y_start', 54), + ('y_end', 62), + ('z_start', 62), + ('z_end', 72), + ('resolution', 2), + ('dim_x', 256), + ('dim_y', 256), + ('dim_z', 256), + ('vtc_filename', '/path/to/test.vtc'), + ('prt_filename', ''), + ('voi_filename', ''), + ('maps', + [OrderedDict([('type_of_map', 3), + ('map_threshold', 0.16120874881744385), + ('upper_threshold', 0.800000011920929), + ('map_name', ''), + ('pos_min_r', 0), + ('pos_min_g', 0), + ('pos_min_b', 100), + ('pos_max_r', 0), + ('pos_max_g', 0), + ('pos_max_b', 255), + ('neg_min_r', 100), + ('neg_min_g', 100), + ('neg_min_b', 50), + ('neg_max_r', 200), + ('neg_max_g', 200), + ('neg_max_b', 100), + ('use_vmp_color', 0), + ('lut_filename', ''), + ('transparent_color_factor', 1.0), + ('nr_of_lags', 8), + ('display_min_lag', 0), + ('display_max_lag', 7), + ('show_correlation_or_lag', 0), + ('cluster_size_threshold', 4), + ('enable_cluster_size_threshold', 0), + ('show_values_above_upper_threshold', 1), + ('df1', 254), + ('df2', 0), + ('show_pos_neg_values', 3), + ('nr_of_used_voxels', 78498), + ('size_of_fdr_table', 8), + ('fdr_table_info', + [OrderedDict([('q', 0.10000000149011612), + ('crit_standard', + 0.13649293780326843), + ('crit_conservative', + 0.20921018719673157)]), + OrderedDict([('q', 0.05000000074505806), + ('crit_standard', + 0.16120874881744385), + ('crit_conservative', + 0.2241515964269638)]), + OrderedDict([('q', 0.03999999910593033), + ('crit_standard', + 0.16819879412651062), + ('crit_conservative', + 0.2286316156387329)]), + OrderedDict([('q', 0.029999999329447746), + ('crit_standard', + 0.1767669916152954), + ('crit_conservative', + 0.2341766357421875)]), + OrderedDict([('q', 0.019999999552965164), + ('crit_standard', + 0.1880645751953125), + ('crit_conservative', + 0.2415686398744583)]), + OrderedDict([('q', 0.009999999776482582), + ('crit_standard', + 0.20535583794116974), + ('crit_conservative', + 0.2533867359161377)]), + OrderedDict([('q', 0.004999999888241291), + ('crit_standard', + 0.2205917239189148), + ('crit_conservative', + 0.2645622491836548)]), + OrderedDict([('q', 0.0010000000474974513), + ('crit_standard', + 0.25055694580078125), + ('crit_conservative', + 0.2873672842979431)])]), + ('use_fdr_table_index', 1)])]), + ('component_time_points', [OrderedDict([('timepoints', [])])]), + ('component_params', [])]) +] + + +def test_BvVmpHeader_set_data_shape(): + vmp = BvVmpHeader() + assert_equal(vmp.get_data_shape(), (1, 46, 40, 58)) + vmp.set_data_shape((1, 45, 39, 57)) + assert_equal(vmp.get_data_shape(), (1, 45, 39, 57)) + + # Use zyx parameter instead of shape + vmp.set_data_shape(None, [[57, 240], [52, 178], [59, 191]]) + assert_equal(vmp.get_data_shape(), (1, 61, 42, 44)) + + # Change number of submaps + vmp.set_data_shape(None, None, 5) # via n parameter + assert_equal(vmp.get_data_shape(), (5, 61, 42, 44)) + vmp.set_data_shape((3, 61, 42, 44)) # via shape parameter + assert_equal(vmp.get_data_shape(), (3, 61, 42, 44)) + + # raise error when neither shape nor zyx nor n is specified + assert_raises(HeaderDataError, vmp.set_data_shape, None, None, None) + + # raise error when n is negative + assert_raises(HeaderDataError, vmp.set_data_shape, (-1, 45, 39, 57)) + assert_raises(HeaderDataError, vmp.set_data_shape, None, None, -1) + + +def test_BvVmpHeader_set_framing_cube(): + vmp = BvVmpHeader() + assert_equal(vmp.get_framing_cube(), (256, 256, 256)) + vmp.set_framing_cube((512, 512, 512)) + assert_equal(vmp.get_framing_cube(), (512, 512, 512)) + vmp.set_framing_cube((512, 513, 514)) + assert_equal(vmp.get_framing_cube(), (512, 513, 514)) diff --git a/nibabel/brainvoyager/tests/test_bv_vmr.py b/nibabel/brainvoyager/tests/test_bv_vmr.py new file mode 100644 index 0000000000..79e05c2bca --- /dev/null +++ b/nibabel/brainvoyager/tests/test_bv_vmr.py @@ -0,0 +1,142 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Test BV module for VMR files.""" + +from os.path import join as pjoin +import numpy as np +from ..bv import BvError +from ..bv_vmr import BvVmrImage, BvVmrHeader +from ...testing import (assert_equal, assert_true, assert_false, assert_raises, + data_path) +from ...externals import OrderedDict + +vmr_file = pjoin(data_path, 'test.vmr') + +# Example images in format expected for ``test_image_api``, adding ``zooms`` +# item. +BVVMR_EXAMPLE_IMAGES = [ + dict( + fname=pjoin(data_path, 'test.vmr'), + shape=(5, 4, 3), + dtype=np.uint8, + affine=np.array([[-1., 0., 0., 0.], + [0., 0., -1., -1.], + [0., -1., 0., 1.], + [0., 0., 0., 1.]]), + zooms=(3., 3., 3.), + fileformat=BvVmrImage, + # These values are from NeuroElf + data_summary=dict( + min=7, + max=218, + mean=120.3), + is_proxy=True) +] + +BVVMR_EXAMPLE_HDRS = [ + OrderedDict([('version', 4), + ('dim_x', 3), + ('dim_y', 4), + ('dim_z', 5), + ('offset_x', 0), + ('offset_y', 0), + ('offset_z', 0), + ('framing_cube', 256), + ('pos_infos_verified', 0), + ('coordinate_system_entry', 1), + ('slice_first_center_x', 127.5), + ('slice_first_center_y', 0.0), + ('slice_first_center_z', 0.0), + ('slice_last_center_x', -127.5), + ('slice_last_center_y', 0.0), + ('slice_last_center_z', 0.0), + ('row_dir_x', 0.0), + ('row_dir_y', 1.0), + ('row_dir_z', 0.0), + ('col_dir_x', 0.0), + ('col_dir_y', 0.0), + ('col_dir_z', -1.0), + ('nr_rows', 256), + ('nr_cols', 256), + ('fov_row_dir', 256.0), + ('fov_col_dir', 256.0), + ('slice_thickness', 1.0), + ('gap_thickness', 0.0), + ('nr_of_past_spatial_trans', 2), + ('past_spatial_trans', + [OrderedDict([('name', 'NoName'), + ('type', 2), + ('source_file', '/home/test.vmr'), + ('nr_of_trans_val', 16), + ('trans_val', + [OrderedDict([('value', 1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', -1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 1.0)]), + OrderedDict([('value', -1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 1.0)])])]), + OrderedDict([('name', 'NoName'), + ('type', 2), + ('source_file', '/home/test_TRF.vmr'), + ('nr_of_trans_val', 16), + ('trans_val', + [OrderedDict([('value', 1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 1.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 0.0)]), + OrderedDict([('value', 1.0)])])])]), + ('lr_convention', 1), + ('reference_space', 0), + ('vox_res_x', 1.0), + ('vox_res_y', 1.0), + ('vox_res_z', 1.0), + ('flag_vox_resolution', 0), + ('flag_tal_space', 0), + ('min_intensity', 0), + ('mean_intensity', 127), + ('max_intensity', 255)]) +] + + +def test_BvVmrHeader_xflip(): + vmr = BvVmrHeader() + assert_true(vmr.get_xflip()) + vmr.set_xflip(False) + assert_false(vmr.get_xflip()) + vmr.set_xflip(0) + assert_raises(BvError, vmr.get_xflip) + + +def test_BvVmrHeader_set_zooms(): + vmr = BvVmrHeader() + assert_equal(vmr.get_zooms(), (1.0, 1.0, 1.0)) + vmr.set_zooms((1.1, 2.2, 3.3)) + assert_equal(vmr.get_zooms(), (1.1, 2.2, 3.3)) + assert_equal(vmr._hdr_dict['vox_res_z'], 1.1) diff --git a/nibabel/brainvoyager/tests/test_bv_vtc.py b/nibabel/brainvoyager/tests/test_bv_vtc.py new file mode 100644 index 0000000000..e7b9c87fb0 --- /dev/null +++ b/nibabel/brainvoyager/tests/test_bv_vtc.py @@ -0,0 +1,121 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +"""Test BV module for VTC files.""" + +from os.path import join as pjoin +import numpy as np +from ..bv import BvError +from ..bv_vtc import BvVtcImage, BvVtcHeader +from ...testing import (data_path, assert_equal, assert_raises, assert_true, + assert_false) +from numpy.testing import (assert_array_equal) +from ...spatialimages import HeaderDataError +from ...externals import OrderedDict + +# Example images in format expected for ``test_image_api``, adding ``zooms`` +# item. +BVVTC_EXAMPLE_IMAGES = [ + dict( + fname=pjoin(data_path, 'test.vtc'), + shape=(10, 10, 10, 5), + dtype=np.float32, + affine=np.array([[-3., 0, 0, -21.], + [0, 0, -3., -21.], + [0, -3., 0, -21.], + [0, 0, 0, 1.]]), + zooms=(3., 3., 3.), + fileformat=BvVtcImage, + # These values are from NeuroElf + data_summary=dict( + min=0.0096689118, + max=199.93549, + mean=100.19728), + is_proxy=True) +] + +BVVTC_EXAMPLE_HDRS = [ + OrderedDict([('version', 3), + ('fmr', 'test.fmr'), + ('nr_prts', 1), + ('prts', [OrderedDict([('filename', 'test.prt')])]), + ('current_prt', 0), + ('datatype', 2), + ('volumes', 5), + ('resolution', 3), + ('x_start', 120), + ('x_end', 150), + ('y_start', 120), + ('y_end', 150), + ('z_start', 120), + ('z_end', 150), + ('lr_convention', 1), + ('ref_space', 1), + ('tr', 2000.0)]) +] + + +def test_get_base_affine(): + hdr = BvVtcHeader() + hdr.set_data_shape((3, 5, 7, 9)) + hdr.set_zooms((3, 3, 3, 3)) + assert_array_equal(hdr.get_base_affine(), + np.asarray([[-3., 0., 0., 193.5], + [0., 0., -3., 181.5], + [0., -3., 0., 205.5], + [0., 0., 0., 1.]])) + + +def test_BvVtcHeader_set_data_shape(): + vtc = BvVtcHeader() + assert_equal(vtc.get_data_shape(), (46, 40, 58, 0)) + vtc.set_data_shape((45, 39, 57, 0)) + assert_equal(vtc.get_data_shape(), (45, 39, 57, 0)) + + # Use zyx parameter instead of shape + vtc.set_data_shape(None, [[57, 240], [52, 178], [59, 191]]) + assert_equal(vtc.get_data_shape(), (61, 42, 44, 0)) + + # Change number of submaps + vtc.set_data_shape(None, None, 5) # via t parameter + assert_equal(vtc.get_data_shape(), (61, 42, 44, 5)) + vtc.set_data_shape((61, 42, 44, 3)) # via shape parameter + assert_equal(vtc.get_data_shape(), (61, 42, 44, 3)) + + # raise error when neither shape nor zyx nor t is specified + assert_raises(HeaderDataError, vtc.set_data_shape, None, None, None) + + # raise error when n is negative + assert_raises(HeaderDataError, vtc.set_data_shape, (45, 39, 57, -1)) + assert_raises(HeaderDataError, vtc.set_data_shape, None, None, -1) + + +def test_BvVtcHeader_set_framing_cube(): + vtc = BvVtcHeader() + assert_equal(vtc.get_framing_cube(), (256, 256, 256)) + vtc.set_framing_cube((512, 512, 512)) + assert_equal(vtc.get_framing_cube(), (512, 512, 512)) + vtc.set_framing_cube((512, 513, 514)) + assert_equal(vtc.get_framing_cube(), (512, 513, 514)) + + +def test_BvVtcHeader_xflip(): + vtc = BvVtcHeader() + assert_true(vtc.get_xflip()) + vtc.set_xflip(False) + assert_false(vtc.get_xflip()) + vtc.set_xflip(True) + assert_true(vtc.get_xflip()) + vtc.set_xflip(0) + assert_raises(BvError, vtc.get_xflip) + + +def test_BvVtcHeader_fileversion_error(): + vtc = BvVtcHeader() + vtc._hdr_dict['version'] = 4 + assert_raises(HeaderDataError, BvVtcHeader.from_header, vtc) diff --git a/nibabel/freesurfer/mghformat.py b/nibabel/freesurfer/mghformat.py index efe51c7d5a..421f78e202 100644 --- a/nibabel/freesurfer/mghformat.py +++ b/nibabel/freesurfer/mghformat.py @@ -281,7 +281,7 @@ def set_data_dtype(self, datatype): try: code = self._data_type_codes[datatype] except KeyError: - raise MGHError('datatype dtype "%s" not recognized' % datatype) + raise HeaderDataError('datatype dtype "%s" not recognized' % datatype) self._header_data['type'] = code def get_zooms(self): @@ -332,6 +332,8 @@ def set_data_shape(self, shape): dims = self._header_data['dims'] # If len(dims) is 3, add a dimension. MGH header always # needs 4 dimensions. + if len(shape) == 2: + raise HeaderDataError('shape cannot have 2 dimensions') if len(shape) == 3: shape = list(shape) shape.append(1) diff --git a/nibabel/freesurfer/tests/test_mghformat.py b/nibabel/freesurfer/tests/test_mghformat.py index 9683148e5f..fe45154af1 100644 --- a/nibabel/freesurfer/tests/test_mghformat.py +++ b/nibabel/freesurfer/tests/test_mghformat.py @@ -17,6 +17,7 @@ from .. import load, save from ...openers import ImageOpener from ..mghformat import MGHHeader, MGHError, MGHImage +from ...spatialimages import HeaderDataError from ...tmpdirs import InTemporaryDirectory from ...fileholders import FileHolder @@ -138,7 +139,7 @@ def bad_dtype_mgh(): def test_bad_dtype_mgh(): # Now test the above function - assert_raises(MGHError, bad_dtype_mgh) + assert_raises(HeaderDataError, bad_dtype_mgh) def test_filename_exts(): diff --git a/nibabel/imageclasses.py b/nibabel/imageclasses.py index 265e77f78a..ac530f9fd7 100644 --- a/nibabel/imageclasses.py +++ b/nibabel/imageclasses.py @@ -1,11 +1,11 @@ # emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the NiBabel package for the # copyright and license terms. # -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## ''' Define supported image classes and names ''' from .analyze import AnalyzeImage @@ -20,6 +20,7 @@ from .spm2analyze import Spm2AnalyzeImage from .volumeutils import Recoder from .deprecated import deprecate_with_version +from .brainvoyager import BvMskImage, BvVtcImage, BvVmpImage, BvVmrImage from .optpkg import optional_package _, have_scipy, _ = optional_package('scipy') @@ -29,7 +30,8 @@ all_image_classes = [Nifti1Pair, Nifti1Image, Nifti2Pair, Nifti2Image, Spm2AnalyzeImage, Spm99AnalyzeImage, AnalyzeImage, Minc1Image, Minc2Image, MGHImage, - PARRECImage, GiftiImage] + PARRECImage, GiftiImage, + BvMskImage, BvVtcImage, BvVmpImage, BvVmrImage] # DEPRECATED: mapping of names to classes and class functionality @@ -81,6 +83,26 @@ def __getitem__(self, *args, **kwargs): 'has_affine': True, 'makeable': True, 'rw': True}, + vtc={'class': BvVtcImage, + 'ext': '.vtc', + 'has_affine': True, + 'makeable': True, + 'rw': True}, + msk={'class': BvMskImage, + 'ext': '.msk', + 'has_affine': True, + 'makeable': True, + 'rw': True}, + vmp={'class': BvVmpImage, + 'ext': '.vmp', + 'has_affine': True, + 'makeable': True, + 'rw': True}, + vmr={'class': BvVmrImage, + 'ext': '.vmr', + 'has_affine': True, + 'makeable': True, + 'rw': True}, par={'class': PARRECImage, 'ext': '.par', 'has_affine': True, @@ -103,6 +125,10 @@ def __getitem__(self, *args, **kwargs): ('mgh', '.mgh'), ('mgz', '.mgz'), ('par', '.par'), + ('vtc', '.vtc'), + ('msk', '.msk'), + ('vmp', '.vmp'), + ('vmr', '.vmr'), )) # Image classes known to require spatial axes to be first in index ordering. diff --git a/nibabel/spatialimages.py b/nibabel/spatialimages.py index d910f7bb22..f8e00ebbee 100644 --- a/nibabel/spatialimages.py +++ b/nibabel/spatialimages.py @@ -297,13 +297,44 @@ def supported_np_types(obj): except HeaderDataError: continue # Did set work? - if np.dtype(obj.get_data_dtype()) == np.dtype(np_type): + if np.dtype(obj.get_data_dtype()) == np.dtype(np_type) or \ + np.dtype(obj.get_data_dtype()) == np.dtype(np_type).newbyteorder('S'): supported.append(np_type) # Reset original header dtype obj.set_data_dtype(dt) return set(supported) +def supported_dimensions(obj): + """ Data dimensions that instance `obj` supports + + Parameters + ---------- + obj : object + Object implementing `get_data_shape` and `set_data_shape`. The object + should raise ``HeaderDataError`` for setting unsupported dimensions. + The object will likely be a header or a :class:`SpatialImage` + + Returns + ------- + dimensions : set + set of data dimensions that `obj` supports + """ + shape = obj.get_data_shape() + dimensions = [] + for dim in [1, 2, 3, 4]: + try: + obj.set_data_shape(np.ones(dim)) + except HeaderDataError: + continue + # Did set work? + if len(obj.get_data_shape()) == dim: + dimensions.append(dim) + # Reset original header dtype + obj.set_data_shape(shape) + return set(dimensions) + + class Header(SpatialHeader): '''Alias for SpatialHeader; kept for backwards compatibility.''' diff --git a/nibabel/tests/data/test.msk b/nibabel/tests/data/test.msk new file mode 100644 index 0000000000000000000000000000000000000000..038f6eb45ffb33f32384d7ab8a63f62093e0758c GIT binary patch literal 1014 zcmYk1ff0i+3%MvAh;VzS#J%0Ly*waYU2_5) zT$}bqLTy4x*sYAw2{W)7mxF{EV8yp!XBHt1{A}}07{luRrv$kCY7_zNlkjv1c$Z%@ zBDET>ll>V(3~ zx6~2H{5ogrBARjtKuj#naYR_}n;`ee!(*uj?4;t$!bq6$9aWD7-BqH{=4sVTAdc4m DEy@KU literal 0 HcmV?d00001 diff --git a/nibabel/tests/data/test.vmp b/nibabel/tests/data/test.vmp new file mode 100644 index 0000000000000000000000000000000000000000..4ee875c732f6bdde8147b6561a9580e889dd892f GIT binary patch literal 4164 zcmZ`+X*kzg*OsYClgbfMN)gFaH1Jz@Qi>#WXjEiOry}a4!7PL#gg=oX%1{&~4d$^7 zk%J;JV} z|GNC^_+Pa%yJ)xRlHKY5B8rOD#2)TE#D@PezQ8fV!7($#?*FwMDyC-U#`b6J81@P$ zQegN0v&;B@E!R-$ztH~*KhGM7RX!%+L)Hiy?Z(0UV&>=J#fVR;#GIHmxayR`u!>NM`)ZOA&4M3ZtiXQI>EiOy{H{H%J4{W8YD1?n2$E*5I1-z$t|Y zh^(k#D$L}Vavf6=lk~&4_Ao4YQH+i=!?^pqkoNVT!w%|#``96f1_>fMp9@K&H4yla zicC9ARg0A|NQ|w7%Kk?Pye5r{w|?OYpBD}+@`v5Nzes1V3k0Go;r`_TzA3p=sEa2y zI1J%X)+*Y?I*CD{Vr=t|rw=Dmaly|B^6gdV`r(Vdyb|3)4F z{bL+Svc{8`tI2`HVg|>o?D6!AFs4>zpkBZR-shvRPU91`IPJwZnI}{i5s10Z7ogl( zg+y$5@KCQ7^K-LtA!a)mmrz7I$YPJfNw{v|#k=@!J-LVA@$_B+!V?8m6`FfLTF2A!!aNdS=-04c##@l znucXT7WixNX?$djQ+#eBvBqNIpID)~iZ4wy;iW#r@2|rt8##2${Y|=cy3ph;U}|3v zg1+BlI?_B*#qWV6CwWrW{y}<&)lp@41QqiO!FRj?jw=VTUEG!~Zp)^Ut0zhNsU_Tb zE9m&5C6L{)34uGq5iOj6k+R>g?mvv94WD4oolAu}d$F_QHqH(WLtHeSIVHtSsh*u^ zk59$Bv<9?f79dUS7aF^F;Zd+5&U^A8EaL_;tHV_%N1D;pB?h176vF}jq8&?z4a z+4hBy(-wh2mI>ye4Z#8H@iULm=3WR!I|0(a)Kw!6E7G6uW=Lj`2h+Odq;=>zKB?Q_ z5Awn$={_9MpJuWiZ6u2WAF$)J3nl|JAhG5mjlYS9d>{{IskmdI<1OaFg%Ok{|Ach4 zJ9bA!f%ES=EEQ>nO=~0b>emw5Em%x-3EpU`zlDLNya+R|rTdp(W1>d~;-ju`?M%mX zIL@;b{q@V>bVHqf3&lZ->jt9E%z<7+ z7PNSVV6bfzx0csn>4jX9t}X_bu{BhTohU^@9cu;VQHMYm_B99YK)~|A73gxAA(oLELaW1B6Jrg zH4M%oCO8bmyt-JFcnW4QYe*yWAY8Uu&<(X2>`i};7YEMaNd6_#Q^-cK&?t;s8nJon zHw;#%lBe!c%<8MhlbW+g-aUh}J&PcBS{W;B-e6DPEtqHqU^YAAjju?N@6J+^3u=dw zbrN1Uu+Vk8nvv#Y$Gw0MQV)*MgZM%eUTuTp$$G}e(GSZf!booS6yBwK;PhcHl$vJ1 zHKYn61z&MZG7>sP$*SvEt+deXALhg}SG3(}!yc1ByeW!;SI%A99rq69X^UWN*@W4L zKj0pp1G;vwxoX%0K}Ah;S-XTZ`xY~Awd9~x`w5*%wuaFMDuEWS!4LKwvf+Mz(wuNz%R>m<%$Oz-s_aG=enV=PRZ{T{ejn3K}+j0BL zJC(rSy>JPPX0on(fu)pBa|7ck=0+M$$R}b^c`=T}xufV+8ImXOBD=>G^1>RZ@d(6j z<7-$Za2uS(4RGg5g4dhW@sLnV!zil=8^1MUgJ%!fx4j2nbvBF~CJ^j)n#|l@U@Gx34j_X@L$;tQ zPyCK1Z3WHOyr8Qu>yMFlF;(w`T|)>M5t#_(xcmEQ!Y8MVNf5hq#q`~O3D{47WWX_%ZGn<7$C&Y5tm29aUrx8$`0-5mA!&>r#P@alSMO!Bhg&- z89!#XVWK{YPUeN+-1ARpJXHY|`S+0g+Kl;L?7Zq&3t#mpocUEvKQaLen{)`=wPvh` z7h{8NBuw4)N&TcMhBTd^l)esGOgkQ@Kf`3}CGyIe#{1X-wELGJdSfKs8xO(KQy&w zn_mo-nGp;Kb`ay*g4EN4P)c}5Mnm^;wdxC6SBm1}%BQr)I1`@pgTWDQg_L8KFxR%F zWU+u9=dONd=cs7152?fD#dg^IQ4BXToQY373AXCpustM;>6uF67rqOFkKQO%+6gU3 zIeMLJ0A1-={N!?`-iZ`ktF6KEmIg3Qp)d^p0M+PB#5z5Li*i45&2nhdrSCXq(F=92 zOmKP;JBK}k@tZ8tPUy#efi5y04aNA{ql{il5jF^|K*pIZ7;y+A-Y`?x9<0aD!45RD z{~fIx=VPAZbJV;_hU4-g9J#59&#$!^hnQaw5w*pba|`AzZ^D4v7wn&<27^mLN?-(D zmp#IUI00Pl(5LOJjf}5E2xT6U!othm%<{l2G`5)_g`1tzx2uxg(Hhtu{eis5k0|(b zft2^9(CDc${3)r1Wf$9!_caO%n3}?AFBSlOaG9fx>RlSXJ|7YM!sfLdHc`QAWNyTd8_-H%}LXqd7m41im zsPUweFV|64S1}Y#UQ%V}df4v$11DJeq_=GVC;zS{(d^?eNGWIDa3rHU+Jxd2jnJR^ z2?m?@P(np9;s>}huL0`fGL9_1Z6#^f4qAjv|a*} zR~Do9#WVOD_M^_N27iuNVme+2=BuV~t8gK*0#-mp+z7YjjPdfcJxYXf>2bmha0E1? zpd^P9qV-hWZ!}W+s5d4r+{C@vIk4f3!|WF+sN0zVzZdqXHCuvP0=A?i<^_etKCokb zgq27tly;{>p)-pPv&3QdIF1Z^-avnuKW;|6g{Z+&jQN8WO$0*W>L%(mErgjw26$db zGtz~%q-K?j3=T_ld0c~9z-Ac5h2wp<6|8;>VAZ8sjL%Vk(upyMFF61i@<7f*7bFD* z;9^D_o0AFV6{G0;wi&_4UQ>4b9auZ1B9+G-a~0E(RQDaCH=0q#Jr}>D+2_=iL#2Gf zq*Ln!?mo>n)`!{%H$?5(e$gIDuXcRSO@h(+1U4t>j8Ib`=Hnpq{nLLqk+ z_TMaEWGhNxY@&&*=3yqd^FA6R9?|{?NeC*|50LH4yV^ z5)r$z7RM~3A=Xhx!6Kd5a$W~-ueZWO=`L=z%wdKMy>UjP0hYb3RLCC#MY9Z~H%Z|< zS0(ddlQkZMG~(XTmY@2*9R}_n5!MoLzq`z@y4c zxF66$(Q*x37Gp`!$Z_ zur-^t@=_))SYR>K5T=$G%8n48J~V(Mq)=cekHq!yRxmE{&O2!M@2HmzYl#AIYKhzP_)m^?xZ ltLgC}L2kHgfVv%K1B?clh|EUV0P+?{28Ka0^+5a|hyfk5LA?L~ literal 0 HcmV?d00001 diff --git a/nibabel/tests/data/test.vtc b/nibabel/tests/data/test.vtc new file mode 100644 index 0000000000000000000000000000000000000000..8b226acbd1a3df52a0225afd67cf991511171af6 GIT binary patch literal 20048 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 Dg7c8x literal 0 HcmV?d00001 diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py index abc7856623..7dcc0ce39e 100644 --- a/nibabel/tests/test_arrayproxy.py +++ b/nibabel/tests/test_arrayproxy.py @@ -17,7 +17,7 @@ import numpy as np -from ..arrayproxy import ArrayProxy, is_proxy +from ..arrayproxy import ArrayProxy, CArrayProxy, is_proxy from ..nifti1 import Nifti1Header from numpy.testing import assert_array_equal, assert_array_almost_equal @@ -50,11 +50,6 @@ def copy(self): return FunkyHeader(self.shape) -class CArrayProxy(ArrayProxy): - # C array memory layout - order = 'C' - - def test_init(): bio = BytesIO() shape = [2, 3, 4] diff --git a/nibabel/tests/test_files_interface.py b/nibabel/tests/test_files_interface.py index 788be6a31a..7e9b7833b5 100644 --- a/nibabel/tests/test_files_interface.py +++ b/nibabel/tests/test_files_interface.py @@ -15,7 +15,8 @@ from .. import Nifti1Image, Nifti1Pair, MGHImage, all_image_classes from ..externals.six import BytesIO from ..fileholders import FileHolderError -from ..spatialimages import SpatialImage +from ..spatialimages import (SpatialImage, supported_np_types, + supported_dimensions) from nose.tools import (assert_true, assert_false, assert_equal, assert_raises) @@ -23,9 +24,6 @@ def test_files_spatialimages(): - # test files creation in image classes - arr = np.zeros((2, 3, 4)) - aff = np.eye(4) klasses = [klass for klass in all_image_classes if klass.rw and issubclass(klass, SpatialImage)] for klass in klasses: @@ -37,12 +35,21 @@ def test_files_spatialimages(): # If we can't create new images in memory without loading, bail here if not klass.makeable: continue - # MGHImage accepts only a few datatypes - # so we force a type change to float32 - if klass == MGHImage: - img = klass(arr.astype(np.float32), aff) - else: - img = klass(arr, aff) + # test files creation in image classes + arr = np.zeros((2, 3, 4)) + aff = np.eye(4) + # some Image types accept only a few datatypes and shapes + # so we check and force a type change to a compatible dtype + try: + supported_dims = supported_dimensions(klass.header_class()) + if len(arr.shape) not in supported_dims: + arr = np.ones(tuple([d+2 for d in range(supported_dims.pop())])) + except: + pass + supported_dtypes = supported_np_types(klass.header_class()) + if arr.dtype not in supported_dtypes: + arr = arr.astype(supported_dtypes.pop()) + img = klass(arr, aff) for key, value in img.file_map.items(): assert_equal(value.filename, None) assert_equal(value.fileobj, None) @@ -86,14 +93,25 @@ def test_files_interface(): def test_round_trip_spatialimages(): # write an image to files - data = np.arange(24, dtype='i4').reshape((2, 3, 4)) - aff = np.eye(4) klasses = [klass for klass in all_image_classes if klass.rw and issubclass(klass, SpatialImage)] for klass in klasses: file_map = klass.make_file_map() for key in file_map: file_map[key].fileobj = BytesIO() + data = np.arange(24, dtype='i4').reshape((2, 3, 4)) + aff = np.eye(4) + # some Image types accept only a few datatypes and shapes + # so we check and force a type change to a compatible dtype + try: + supported_dims = supported_dimensions(klass.header_class()) + if len(data.shape) not in supported_dims: + data = np.ones(tuple([d+2 for d in range(supported_dims.pop())])) + except: + pass + supported_dtypes = supported_np_types(klass.header_class()) + if data.dtype not in supported_dtypes: + data = data.astype(supported_dtypes.pop()) img = klass(data, aff) img.file_map = file_map img.to_file_map() diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index e86b8c8ea7..4efb876f88 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -32,11 +32,14 @@ _, have_scipy, _ = optional_package('scipy') _, have_h5py, _ = optional_package('h5py') -from .. import (AnalyzeImage, Spm99AnalyzeImage, Spm2AnalyzeImage, - Nifti1Pair, Nifti1Image, Nifti2Pair, Nifti2Image, - MGHImage, Minc1Image, Minc2Image) -from ..spatialimages import SpatialImage -from .. import minc1, minc2, parrec +from nibabel import (AnalyzeImage, Spm99AnalyzeImage, Spm2AnalyzeImage, + Nifti1Pair, Nifti1Image, Nifti2Pair, Nifti2Image, + MGHImage, Minc1Image, Minc2Image, BvVtcImage, BvMskImage, + BvVmpImage, BvVmrImage) +from nibabel.spatialimages import (SpatialImage, supported_np_types, + supported_dimensions) +from nibabel.ecat import EcatImage +from nibabel import minc1, minc2, parrec from nose import SkipTest from nose.tools import (assert_true, assert_false, assert_raises, @@ -51,6 +54,10 @@ from .test_minc1 import EXAMPLE_IMAGES as MINC1_EXAMPLE_IMAGES from .test_minc2 import EXAMPLE_IMAGES as MINC2_EXAMPLE_IMAGES from .test_parrec import EXAMPLE_IMAGES as PARREC_EXAMPLE_IMAGES +from nibabel.brainvoyager.tests import BVVTC_EXAMPLE_IMAGES +from nibabel.brainvoyager.tests import BVMSK_EXAMPLE_IMAGES +from nibabel.brainvoyager.tests import BVVMP_EXAMPLE_IMAGES +from nibabel.brainvoyager.tests import BVVMR_EXAMPLE_IMAGES class GenericImageAPI(ValidateAPI): @@ -140,6 +147,9 @@ def validate_header_deprecated(self, imaker, params): img = imaker() with clear_and_catch_warnings() as w: warnings.simplefilter('always', DeprecationWarning) + # Ignore numpy.rint warning in python3/windows + warnings.simplefilter('ignore', RuntimeWarning, 726) + img = imaker() hdr = img.get_header() assert_equal(len(w), 1) assert_true(hdr is img.header) @@ -175,12 +185,21 @@ def validate_dtype(self, imaker, params): rt_img = bytesio_round_trip(img) assert_equal(rt_img.get_data_dtype().type, params['dtype']) # Setting to a different dtype - img.set_data_dtype(np.float32) # assumed supported for all formats - assert_equal(img.get_data_dtype().type, np.float32) + new_dtype = np.float32 + # some Image types accept only a few datatypes and shapes + # so we check and force a type change to a compatible dtype + try: + supported_dtypes = supported_np_types(img.header_class()) + if new_dtype not in supported_dtypes: + new_dtype = supported_dtypes.pop() + except: + pass + img.set_data_dtype(new_dtype) + assert_equal(img.get_data_dtype().type, new_dtype) # dtype survives round trip if self.can_save: rt_img = bytesio_round_trip(img) - assert_equal(rt_img.get_data_dtype().type, np.float32) + assert_equal(rt_img.get_data_dtype().type, new_dtype) def validate_data(self, imaker, params): # Check get data returns array, and caches @@ -267,7 +286,17 @@ def validate_filenames(self, imaker, params): if not self.can_save: raise SkipTest img = imaker() - img.set_data_dtype(np.float32) # to avoid rounding in load / save + # Setting to a different dtype to avoid rounding in load / save + new_dtype = np.float32 + # some Image types accept only a few datatypes and shapes + # so we check and force a type change to a compatible dtype + try: + supported_dtypes = supported_np_types(img.header_class()) + if new_dtype not in supported_dtypes: + new_dtype = supported_dtypes.pop() + except: + pass + img.set_data_dtype(new_dtype) # The bytesio_round_trip helper tests bytesio load / save via file_map rt_img = bytesio_round_trip(img) assert_array_equal(img.shape, rt_img.shape) @@ -323,6 +352,8 @@ class MakeImageAPI(LoadImageAPI): header_maker = None # Example shapes for created images example_shapes = ((2,), (2, 3), (2, 3, 4), (2, 3, 4, 5)) + # Example dtypes for created images + example_dtypes = (np.uint8, np.int16, np.float32) def img_from_arr_aff(self, arr, aff, header=None): return self.image_maker(arr, aff, header) @@ -334,10 +365,25 @@ def obj_params(self): # Create a new images aff = np.diag([1, 2, 3, 1]) + # Try to retrieve allowed dims + try: + supported_dims = supported_dimensions(self.header_maker()) + self.example_shapes = (shape for shape in self.example_shapes + if len(shape) in supported_dims) + except: + pass + # Try to retrieve allowed dtypes + try: + supported_dtypes = supported_np_types(self.header_maker()) + self.example_dtypes = (dtype for dtype in self.example_dtypes + if dtype in supported_dtypes) + except: + pass + def make_imaker(arr, aff, header=None): return lambda: self.image_maker(arr, aff, header) for shape in self.example_shapes: - for dtype in (np.uint8, np.int16, np.float32): + for dtype in self.example_dtypes: arr = np.arange(np.prod(shape), dtype=np.float32).reshape(shape) hdr = self.header_maker() hdr.set_data_dtype(dtype) @@ -453,3 +499,39 @@ class TestMGHAPI(ImageHeaderAPI): has_scaling = True can_save = True standard_extension = '.mgh' + + +class TestBvVtcAPI(ImageHeaderAPI): + klass = image_maker = BvVtcImage + loader = BvVtcImage.load + example_images = BVVTC_EXAMPLE_IMAGES + has_scaling = False + can_save = True + standard_extension = '.vtc' + + +class TestBvMskAPI(ImageHeaderAPI): + klass = image_maker = BvMskImage + loader = BvMskImage.load + example_images = BVMSK_EXAMPLE_IMAGES + has_scaling = False + can_save = True + standard_extension = '.msk' + + +class TestBvVmpAPI(ImageHeaderAPI): + klass = image_maker = BvVmpImage + loader = BvVmpImage.load + example_images = BVVMP_EXAMPLE_IMAGES + has_scaling = False + can_save = True + standard_extension = '.vmp' + + +class TestBvVmrAPI(TestBvMskAPI): + klass = image_maker = BvVmrImage + loader = BvVmrImage.load + example_images = BVVMR_EXAMPLE_IMAGES + has_scaling = False + can_save = True + standard_extension = '.vmr' diff --git a/nibabel/tests/test_image_load_save.py b/nibabel/tests/test_image_load_save.py index d43d1ee581..f71f7156b3 100644 --- a/nibabel/tests/test_image_load_save.py +++ b/nibabel/tests/test_image_load_save.py @@ -23,11 +23,13 @@ from .. import loadsave as nils from .. import (Nifti1Image, Nifti1Header, Nifti1Pair, Nifti2Image, Nifti2Pair, Minc1Image, Minc2Image, Spm2AnalyzeImage, Spm99AnalyzeImage, - AnalyzeImage, MGHImage, all_image_classes) + AnalyzeImage, MGHImage, BvVtcImage, BvMskImage, BvVmpImage, + BvVmrImage, all_image_classes) from ..tmpdirs import InTemporaryDirectory from ..volumeutils import native_code, swapped_code from ..optpkg import optional_package -from ..spatialimages import SpatialImage +from ..spatialimages import (SpatialImage, supported_np_types, + supported_dimensions) from numpy.testing import assert_array_equal, assert_array_almost_equal from nose.tools import assert_true, assert_equal @@ -56,11 +58,17 @@ def test_conversion_spatialimages(): for r_class in klasses: if not r_class.makeable: continue + if npt not in supported_np_types(r_class.header_class()) or \ + len(shape) not in supported_dimensions(r_class.header_class()): + continue img = r_class(data, affine) img.set_data_dtype(npt) for w_class in klasses: if not w_class.makeable: continue + if npt not in supported_np_types(w_class.header_class()) or \ + len(shape) not in supported_dimensions(w_class.header_class()): + continue img2 = w_class.from_image(img) assert_array_equal(img2.get_data(), data) assert_array_equal(img2.affine, affine) @@ -322,3 +330,15 @@ def test_guessed_image_type(): assert_equal(nils.guessed_image_type( pjoin(DATA_PATH, 'analyze.hdr')), Spm2AnalyzeImage) + assert_equal(nils.guessed_image_type( + pjoin(DATA_PATH, 'test.vtc')), + BvVtcImage) + assert_equal(nils.guessed_image_type( + pjoin(DATA_PATH, 'test.msk')), + BvMskImage) + assert_equal(nils.guessed_image_type( + pjoin(DATA_PATH, 'test.vmp')), + BvVmpImage) + assert_equal(nils.guessed_image_type( + pjoin(DATA_PATH, 'test.vmr')), + BvVmrImage) diff --git a/nibabel/tests/test_image_types.py b/nibabel/tests/test_image_types.py index e72ad6bbbc..f33a483c35 100644 --- a/nibabel/tests/test_image_types.py +++ b/nibabel/tests/test_image_types.py @@ -19,7 +19,8 @@ AnalyzeImage, AnalyzeHeader, Minc1Image, Minc2Image, Spm2AnalyzeImage, Spm99AnalyzeImage, - MGHImage, all_image_classes) + MGHImage, BvVtcImage, BvMskImage, + BvVmpImage, BvVmrImage, all_image_classes) from nose.tools import assert_true @@ -116,7 +117,11 @@ def check_img(img_path, img_klass, sniff_mode, sniff, expect_success, ('tiny.mnc', Minc1Image), ('small.mnc', Minc2Image), ('test.mgz', MGHImage), - ('analyze.hdr', Spm2AnalyzeImage)]: + ('analyze.hdr', Spm2AnalyzeImage), + ('test.vtc', BvVtcImage), + ('test.msk', BvMskImage), + ('test.vmp', BvVmpImage), + ('test.vmr', BvVmrImage)]: # print('Testing: %s %s' % (img_filename, image_klass.__name__)) test_image_class(pjoin(DATA_PATH, img_filename), image_klass) diff --git a/nibabel/tests/test_loadsave.py b/nibabel/tests/test_loadsave.py index a5f36100d9..e5d7af3ed1 100644 --- a/nibabel/tests/test_loadsave.py +++ b/nibabel/tests/test_loadsave.py @@ -33,7 +33,11 @@ def test_read_img_data(): 'minc1_1_scale.mnc', 'minc1_4d.mnc', 'test.mgz', - 'tiny.mnc' + 'tiny.mnc', + 'test.vtc', + 'test.msk', + 'test.vmp', + 'test.vmr' ): fpath = pjoin(data_path, fname) img = load(fpath) diff --git a/setup.py b/setup.py index 5e9bf51c29..7ee5cba916 100755 --- a/setup.py +++ b/setup.py @@ -92,6 +92,8 @@ def main(**extra_args): 'nibabel.benchmarks', 'nibabel.streamlines', 'nibabel.streamlines.tests', + 'nibabel.brainvoyager', + 'nibabel.brainvoyager.tests', # install nisext as its own package 'nisext', 'nisext.tests'], From a87853d53a5412067dc372e314a0fedb5507b984 Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Wed, 17 Aug 2016 18:09:21 +0200 Subject: [PATCH 2/9] TEST: Fix failing test_image_api tests for new BV test files. --- nibabel/brainvoyager/bv_vtc.py | 2 +- nibabel/brainvoyager/tests/test_bv_vmp.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nibabel/brainvoyager/bv_vtc.py b/nibabel/brainvoyager/bv_vtc.py index a1d76c6a15..da93a67d1d 100644 --- a/nibabel/brainvoyager/bv_vtc.py +++ b/nibabel/brainvoyager/bv_vtc.py @@ -92,7 +92,7 @@ def set_data_shape(self, shape=None, zyx=None, t=None): if (shape is None) and (zyx is None) and (t is None): raise HeaderDataError('Shape, zyx, or t needs to be specified!') if ((t is not None) and (t < 0)) or \ - ((shape is not None) and (shape[3] < 0)): + ((shape is not None) and (len(shape) == 4) and (shape[3] < 0)): raise HeaderDataError('VTC files need at least one volume!') if shape is not None: # Use zyx and t parameters instead of shape. diff --git a/nibabel/brainvoyager/tests/test_bv_vmp.py b/nibabel/brainvoyager/tests/test_bv_vmp.py index c68ec1c701..d1675ad44f 100644 --- a/nibabel/brainvoyager/tests/test_bv_vmp.py +++ b/nibabel/brainvoyager/tests/test_bv_vmp.py @@ -52,13 +52,13 @@ is_proxy=True), dict( fname=pjoin(data_path, 'test3.vmp'), - shape=(1, 10, 10, 10), + shape=(1, 5, 4, 3), dtype=np.float32, - affine=np.array([[-3., 0, 0, -21.], - [0, 0, -3., -21.], - [0, -3., 0, -21.], + affine=np.array([[-2., 0, 0, 122.], + [0, 0, -2., 46.], + [0, -2., 0, 140.], [0, 0, 0, 1.]]), - zooms=(3., 3., 3.), + zooms=(2., 2., 2.), fileformat=BvVmpImage, # These values are from NeuroElf data_summary=dict( From 9a13fd2cae4b7b44a7f1320e003c281dcb41d75a Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Wed, 17 Aug 2016 18:31:03 +0200 Subject: [PATCH 3/9] TEST+PL: Fix python3 string issues and linting. --- nibabel/brainvoyager/bv_vmr.py | 1 - nibabel/brainvoyager/bv_vtc.py | 1 - nibabel/brainvoyager/tests/test_bv.py | 26 ++++++++--------- nibabel/brainvoyager/tests/test_bv_vmp.py | 34 +++++++++++------------ nibabel/brainvoyager/tests/test_bv_vmr.py | 8 +++--- nibabel/brainvoyager/tests/test_bv_vtc.py | 4 +-- 6 files changed, 36 insertions(+), 38 deletions(-) diff --git a/nibabel/brainvoyager/bv_vmr.py b/nibabel/brainvoyager/bv_vmr.py index 6ee244c01e..012469765e 100644 --- a/nibabel/brainvoyager/bv_vmr.py +++ b/nibabel/brainvoyager/bv_vmr.py @@ -17,7 +17,6 @@ from .bv import (BvError, BvFileHeader, BvFileImage, parse_BV_header, pack_BV_header, calc_BV_header_size, combine_st, parse_st) from ..spatialimages import HeaderDataError -from ..batteryrunners import Report import numpy as np diff --git a/nibabel/brainvoyager/bv_vtc.py b/nibabel/brainvoyager/bv_vtc.py index da93a67d1d..d6d0e006d7 100644 --- a/nibabel/brainvoyager/bv_vtc.py +++ b/nibabel/brainvoyager/bv_vtc.py @@ -16,7 +16,6 @@ from __future__ import division from .bv import BvError, BvFileHeader, BvFileImage from ..spatialimages import HeaderDataError -from ..batteryrunners import Report VTC_HDR_DICT_PROTO = ( ('version', 'h', 3), diff --git a/nibabel/brainvoyager/tests/test_bv.py b/nibabel/brainvoyager/tests/test_bv.py index bd651f86ac..d762c76511 100644 --- a/nibabel/brainvoyager/tests/test_bv.py +++ b/nibabel/brainvoyager/tests/test_bv.py @@ -65,47 +65,47 @@ ('another_counter_integer', 0), ('some_unsigned_long_integer', 2712847316), ('some_float', 1.0), - ('some_zero_terminated_string', 'HelloWorld!'), + ('some_zero_terminated_string', b'HelloWorld!'), ('some_conditional_integer', 0), ('another_conditional_integer', 23), ('some_nested_field', [OrderedDict([('a_number', 1), ('a_float', 1.65), - ('a_string', 'test.txt'), + ('a_string', b'test.txt'), ('nested_counter_integer', 2), ('fdr_table_info', [OrderedDict([('another_float', 0.0), - ('another_string', 'sample')]), + ('another_string', b'sample')]), OrderedDict([('another_float', 0.0), - ('another_string', 'sample')])])]), + ('another_string', b'sample')])])]), OrderedDict([('a_number', 1), ('a_float', 1.65), - ('a_string', 'test.txt'), + ('a_string', b'test.txt'), ('nested_counter_integer', 2), ('fdr_table_info', [OrderedDict([('another_float', 0.0), - ('another_string', 'sample')]), + ('another_string', b'sample')]), OrderedDict([('another_float', 0.0), - ('another_string', 'sample')])])]), + ('another_string', b'sample')])])]), OrderedDict([('a_number', 1), ('a_float', 1.65), - ('a_string', 'test.txt'), + ('a_string', b'test.txt'), ('nested_counter_integer', 2), ('fdr_table_info', [OrderedDict([('another_float', 0.0), - ('another_string', 'sample')]), + ('another_string', b'sample')]), OrderedDict([('another_float', 0.0), - ('another_string', 'sample')])])]), + ('another_string', b'sample')])])]), OrderedDict([('a_number', 1), ('a_float', 1.65), - ('a_string', 'test.txt'), + ('a_string', b'test.txt'), ('nested_counter_integer', 2), ('fdr_table_info', [OrderedDict([('another_float', 0.0), - ('another_string', 'sample')]), + ('another_string', b'sample')]), OrderedDict([('another_float', 0.0), ('another_string', - 'sample')])])])]), + b'sample')])])])]), ('another_nested_field', [])]) TEST_HDR_PACKED = \ diff --git a/nibabel/brainvoyager/tests/test_bv_vmp.py b/nibabel/brainvoyager/tests/test_bv_vmp.py index d1675ad44f..0e0555aef3 100644 --- a/nibabel/brainvoyager/tests/test_bv_vmp.py +++ b/nibabel/brainvoyager/tests/test_bv_vmp.py @@ -89,14 +89,14 @@ ('dim_x', 256), ('dim_y', 256), ('dim_z', 256), - ('vtc_filename', 'test.vtc'), - ('prt_filename', ''), - ('voi_filename', ''), + ('vtc_filename', b'test.vtc'), + ('prt_filename', b''), + ('voi_filename', b''), ('maps', [OrderedDict([('type_of_map', 1), ('map_threshold', 1.649999976158142), ('upper_threshold', 8.0), - ('map_name', 'Testmap'), + ('map_name', b'Testmap'), ('pos_min_r', 255), ('pos_min_g', 0), ('pos_min_b', 0), @@ -110,7 +110,7 @@ ('neg_max_g', 0), ('neg_max_b', 255), ('use_vmp_color', 0), - ('lut_filename', ''), + ('lut_filename', b''), ('transparent_color_factor', 1.0), ('nr_of_lags', 0), ('display_min_lag', 0), @@ -148,14 +148,14 @@ ('dim_x', 256), ('dim_y', 256), ('dim_z', 256), - ('vtc_filename', 'test.vtc'), - ('prt_filename', ''), - ('voi_filename', ''), + ('vtc_filename', b'test.vtc'), + ('prt_filename', b''), + ('voi_filename', b''), ('maps', [OrderedDict([('type_of_map', 1), ('map_threshold', 1.649999976158142), ('upper_threshold', 8.0), - ('map_name', 'Testmap'), + ('map_name', b'Testmap'), ('pos_min_r', 255), ('pos_min_g', 0), ('pos_min_b', 0), @@ -169,7 +169,7 @@ ('neg_max_g', 0), ('neg_max_b', 255), ('use_vmp_color', 0), - ('lut_filename', ''), + ('lut_filename', b''), ('transparent_color_factor', 1.0), ('nr_of_lags', 0), ('display_min_lag', 0), @@ -188,7 +188,7 @@ OrderedDict([('type_of_map', 1), ('map_threshold', 1.649999976158142), ('upper_threshold', 8.0), - ('map_name', 'Testmap'), + ('map_name', b'Testmap'), ('pos_min_r', 255), ('pos_min_g', 0), ('pos_min_b', 0), @@ -202,7 +202,7 @@ ('neg_max_g', 0), ('neg_max_b', 255), ('use_vmp_color', 0), - ('lut_filename', ''), + ('lut_filename', b''), ('transparent_color_factor', 1.0), ('nr_of_lags', 0), ('display_min_lag', 0), @@ -242,14 +242,14 @@ ('dim_x', 256), ('dim_y', 256), ('dim_z', 256), - ('vtc_filename', '/path/to/test.vtc'), - ('prt_filename', ''), - ('voi_filename', ''), + ('vtc_filename', b'/path/to/test.vtc'), + ('prt_filename', b''), + ('voi_filename', b''), ('maps', [OrderedDict([('type_of_map', 3), ('map_threshold', 0.16120874881744385), ('upper_threshold', 0.800000011920929), - ('map_name', ''), + ('map_name', b''), ('pos_min_r', 0), ('pos_min_g', 0), ('pos_min_b', 100), @@ -263,7 +263,7 @@ ('neg_max_g', 200), ('neg_max_b', 100), ('use_vmp_color', 0), - ('lut_filename', ''), + ('lut_filename', b''), ('transparent_color_factor', 1.0), ('nr_of_lags', 8), ('display_min_lag', 0), diff --git a/nibabel/brainvoyager/tests/test_bv_vmr.py b/nibabel/brainvoyager/tests/test_bv_vmr.py index 79e05c2bca..865043a024 100644 --- a/nibabel/brainvoyager/tests/test_bv_vmr.py +++ b/nibabel/brainvoyager/tests/test_bv_vmr.py @@ -70,9 +70,9 @@ ('gap_thickness', 0.0), ('nr_of_past_spatial_trans', 2), ('past_spatial_trans', - [OrderedDict([('name', 'NoName'), + [OrderedDict([('name', b'NoName'), ('type', 2), - ('source_file', '/home/test.vmr'), + ('source_file', b'/home/test.vmr'), ('nr_of_trans_val', 16), ('trans_val', [OrderedDict([('value', 1.0)]), @@ -91,9 +91,9 @@ OrderedDict([('value', 0.0)]), OrderedDict([('value', 0.0)]), OrderedDict([('value', 1.0)])])]), - OrderedDict([('name', 'NoName'), + OrderedDict([('name', b'NoName'), ('type', 2), - ('source_file', '/home/test_TRF.vmr'), + ('source_file', b'/home/test_TRF.vmr'), ('nr_of_trans_val', 16), ('trans_val', [OrderedDict([('value', 1.0)]), diff --git a/nibabel/brainvoyager/tests/test_bv_vtc.py b/nibabel/brainvoyager/tests/test_bv_vtc.py index e7b9c87fb0..22368898a0 100644 --- a/nibabel/brainvoyager/tests/test_bv_vtc.py +++ b/nibabel/brainvoyager/tests/test_bv_vtc.py @@ -41,9 +41,9 @@ BVVTC_EXAMPLE_HDRS = [ OrderedDict([('version', 3), - ('fmr', 'test.fmr'), + ('fmr', b'test.fmr'), ('nr_prts', 1), - ('prts', [OrderedDict([('filename', 'test.prt')])]), + ('prts', [OrderedDict([('filename', b'test.prt')])]), ('current_prt', 0), ('datatype', 2), ('volumes', 5), From d2929fc6e40b9b0debdda8195d3bb7901ca8de67 Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Thu, 18 Aug 2016 15:13:52 +0200 Subject: [PATCH 4/9] TEST: Fix rint error message filtering in test_image_api. --- nibabel/tests/test_image_api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index 4efb876f88..3ebd3b7a78 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -148,7 +148,8 @@ def validate_header_deprecated(self, imaker, params): with clear_and_catch_warnings() as w: warnings.simplefilter('always', DeprecationWarning) # Ignore numpy.rint warning in python3/windows - warnings.simplefilter('ignore', RuntimeWarning, 726) + warnings.filterwarnings('ignore', + 'invalid value encountered in rint') img = imaker() hdr = img.get_header() assert_equal(len(w), 1) @@ -170,6 +171,9 @@ def validate_shape_deprecated(self, imaker, params): # Check deprecated get_shape API with clear_and_catch_warnings() as w: warnings.simplefilter('always', DeprecationWarning) + # Ignore numpy.rint warning in python3/windows + warnings.filterwarnings('ignore', + 'invalid value encountered in rint') img = imaker() assert_equal(img.get_shape(), params['shape']) assert_equal(len(w), 1) From 9da49dc59594095913858a857de1c2bc09a8e262 Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Thu, 18 Aug 2016 17:21:13 +0200 Subject: [PATCH 5/9] TEST: Add more BV tests. --- nibabel/brainvoyager/bv.py | 2 +- nibabel/brainvoyager/bv_vmr.py | 2 +- nibabel/brainvoyager/tests/test_bv.py | 35 +++++++++++++++++++++-- nibabel/brainvoyager/tests/test_bv_vmr.py | 15 +++++++++- nibabel/brainvoyager/tests/test_bv_vtc.py | 25 ++++++++++++++++ 5 files changed, 74 insertions(+), 5 deletions(-) diff --git a/nibabel/brainvoyager/bv.py b/nibabel/brainvoyager/bv.py index b0a250347a..b4ab59c54f 100644 --- a/nibabel/brainvoyager/bv.py +++ b/nibabel/brainvoyager/bv.py @@ -672,7 +672,7 @@ def get_base_affine(self): if not self.get_xflip(): # make the BV internal Z axis neurological (left-is-left); # not default in BV files! - zooms[0] *= -1 + zooms = (-zooms[0], zooms[1], zooms[2]) # compute the rotation rot = np.zeros((3, 3)) diff --git a/nibabel/brainvoyager/bv_vmr.py b/nibabel/brainvoyager/bv_vmr.py index 012469765e..30919d005b 100644 --- a/nibabel/brainvoyager/bv_vmr.py +++ b/nibabel/brainvoyager/bv_vmr.py @@ -163,7 +163,7 @@ def get_base_affine(self): if not self.get_xflip(): # make the BV internal Z axis neurological (left-is-left); # not default in BV files! - zooms[0] *= -1 + zooms = (-zooms[0], zooms[1], zooms[2]) # compute the rotation rot = np.zeros((3, 3)) diff --git a/nibabel/brainvoyager/tests/test_bv.py b/nibabel/brainvoyager/tests/test_bv.py index d762c76511..de0c67959b 100644 --- a/nibabel/brainvoyager/tests/test_bv.py +++ b/nibabel/brainvoyager/tests/test_bv.py @@ -15,10 +15,10 @@ from ..bv import (readCString, parse_BV_header, pack_BV_header, BvFileHeader, calc_BV_header_size, _proto2default, update_BV_header, parse_st, combine_st, BvError) -from ..bv_vtc import VTC_HDR_DICT_PROTO +from ..bv_vtc import VTC_HDR_DICT_PROTO, BvVtcHeader from ..bv_vmr import BvVmrImage from ...testing import (assert_equal, assert_array_equal, data_path, - assert_true, assert_false, assert_raises) + assert_true, assert_raises) from . import BV_EXAMPLE_IMAGES, BV_EXAMPLE_HDRS from ...externals import OrderedDict @@ -270,6 +270,7 @@ def test_BvFileHeader_xflip(): # should only return bv.set_xflip(True) + # cannot flip most BV images assert_raises(BvError, bv.set_xflip, False) @@ -277,6 +278,36 @@ def test_BvFileHeader_endianness(): assert_raises(BvError, BvFileHeader, endianness='>') +def test_BvFileHeader_not_implemented(): + bv = BvFileHeader() + assert_raises(NotImplementedError, bv.get_data_shape) + assert_raises(NotImplementedError, bv.set_data_shape, (1, 2, 3)) + + +def test_BvVtcHeader_from_header(): + vtc = load(vtc_file) + vtc_data = vtc.get_data() + + # try the same load through the header + fread = open(vtc_file, 'rb') + header = BvVtcHeader.from_fileobj(fread) + image = header.data_from_fileobj(fread) + assert_array_equal(vtc_data, image) + fread.close() + + +def test_BvVtcHeader_data_from_fileobj(): + vtc = load(vtc_file) + vtc_data = vtc.get_data() + + # try the same load through the header + fread = open(vtc_file, 'rb') + header = BvVtcHeader.from_fileobj(fread) + image = header.data_from_fileobj(fread) + assert_array_equal(vtc_data, image) + fread.close() + + def test_parse_all_BV_headers(): for images, headers in zip(BV_EXAMPLE_IMAGES, BV_EXAMPLE_HDRS): for i in range(len(images)): diff --git a/nibabel/brainvoyager/tests/test_bv_vmr.py b/nibabel/brainvoyager/tests/test_bv_vmr.py index 865043a024..e6abbddf07 100644 --- a/nibabel/brainvoyager/tests/test_bv_vmr.py +++ b/nibabel/brainvoyager/tests/test_bv_vmr.py @@ -13,7 +13,7 @@ from ..bv import BvError from ..bv_vmr import BvVmrImage, BvVmrHeader from ...testing import (assert_equal, assert_true, assert_false, assert_raises, - data_path) + assert_array_equal, data_path) from ...externals import OrderedDict vmr_file = pjoin(data_path, 'test.vmr') @@ -130,9 +130,22 @@ def test_BvVmrHeader_xflip(): assert_true(vmr.get_xflip()) vmr.set_xflip(False) assert_false(vmr.get_xflip()) + assert_equal(vmr._hdr_dict['lr_convention'], 2) + vmr.set_xflip(True) + assert_true(vmr.get_xflip()) + assert_equal(vmr._hdr_dict['lr_convention'], 1) vmr.set_xflip(0) + assert_equal(vmr._hdr_dict['lr_convention'], 0) assert_raises(BvError, vmr.get_xflip) + vmr = BvVmrImage.from_filename(vmr_file) + vmr.header.set_xflip(False) + expected_affine = [[1., 0., 0., 0.], + [0., 0., -1., -1.], + [0., -1., 0., 1.], + [0., 0., 0., 1.]] + assert_array_equal(vmr.header.get_affine(), expected_affine) + def test_BvVmrHeader_set_zooms(): vmr = BvVmrHeader() diff --git a/nibabel/brainvoyager/tests/test_bv_vtc.py b/nibabel/brainvoyager/tests/test_bv_vtc.py index 22368898a0..e10c548b4b 100644 --- a/nibabel/brainvoyager/tests/test_bv_vtc.py +++ b/nibabel/brainvoyager/tests/test_bv_vtc.py @@ -115,6 +115,31 @@ def test_BvVtcHeader_xflip(): assert_raises(BvError, vtc.get_xflip) +def test_BvVtcHeader_guess_framing_cube(): + vtc = BvVtcHeader() + assert_equal(vtc._guess_framing_cube(), (256, 256, 256)) + vtc._hdr_dict['x_end'] = 400 + vtc._hdr_dict['y_end'] = 400 + vtc._hdr_dict['z_end'] = 400 + assert_equal(vtc._guess_framing_cube(), (512, 512, 512)) + + +def test_BvVtcHeader_zooms(): + vtc = BvVtcHeader() + assert_equal(vtc.get_zooms(), (3.0, 3.0, 3.0)) + + # set all zooms to one value (default for VTC files) + vtc.set_zooms(2) + assert_equal(vtc.get_zooms(), (2.0, 2.0, 2.0)) + vtc.set_zooms((1.0, 1.0, 1.0)) + assert_equal(vtc.get_zooms(), (1.0, 1.0, 1.0)) + vtc.set_zooms((4, 4, 4)) + assert_equal(vtc.get_zooms(), (4.0, 4.0, 4.0)) + + # set zooms to different values for the three dimensions (not possible) + assert_raises(BvError, vtc.set_zooms, (1.0, 2.0, 3.0)) + + def test_BvVtcHeader_fileversion_error(): vtc = BvVtcHeader() vtc._hdr_dict['version'] = 4 From aaa9a95d2d046e63e4059f1e1ba0896801801ff8 Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Thu, 18 Aug 2016 17:22:05 +0200 Subject: [PATCH 6/9] TEST: Remove try block around dtype and dimension checks. --- nibabel/tests/test_files_interface.py | 18 ++++++------------ nibabel/tests/test_image_api.py | 27 +++++++++------------------ 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/nibabel/tests/test_files_interface.py b/nibabel/tests/test_files_interface.py index 7e9b7833b5..59853e8092 100644 --- a/nibabel/tests/test_files_interface.py +++ b/nibabel/tests/test_files_interface.py @@ -40,12 +40,9 @@ def test_files_spatialimages(): aff = np.eye(4) # some Image types accept only a few datatypes and shapes # so we check and force a type change to a compatible dtype - try: - supported_dims = supported_dimensions(klass.header_class()) - if len(arr.shape) not in supported_dims: - arr = np.ones(tuple([d+2 for d in range(supported_dims.pop())])) - except: - pass + supported_dims = supported_dimensions(klass.header_class()) + if len(arr.shape) not in supported_dims: + arr = np.ones(tuple([d+2 for d in range(supported_dims.pop())])) supported_dtypes = supported_np_types(klass.header_class()) if arr.dtype not in supported_dtypes: arr = arr.astype(supported_dtypes.pop()) @@ -103,12 +100,9 @@ def test_round_trip_spatialimages(): aff = np.eye(4) # some Image types accept only a few datatypes and shapes # so we check and force a type change to a compatible dtype - try: - supported_dims = supported_dimensions(klass.header_class()) - if len(data.shape) not in supported_dims: - data = np.ones(tuple([d+2 for d in range(supported_dims.pop())])) - except: - pass + supported_dims = supported_dimensions(klass.header_class()) + if len(data.shape) not in supported_dims: + data = np.ones(tuple([d+2 for d in range(supported_dims.pop())])) supported_dtypes = supported_np_types(klass.header_class()) if data.dtype not in supported_dtypes: data = data.astype(supported_dtypes.pop()) diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index 3ebd3b7a78..d63620a0f4 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -294,12 +294,9 @@ def validate_filenames(self, imaker, params): new_dtype = np.float32 # some Image types accept only a few datatypes and shapes # so we check and force a type change to a compatible dtype - try: - supported_dtypes = supported_np_types(img.header_class()) - if new_dtype not in supported_dtypes: - new_dtype = supported_dtypes.pop() - except: - pass + supported_dtypes = supported_np_types(img.header_class()) + if new_dtype not in supported_dtypes: + new_dtype = supported_dtypes.pop() img.set_data_dtype(new_dtype) # The bytesio_round_trip helper tests bytesio load / save via file_map rt_img = bytesio_round_trip(img) @@ -370,19 +367,13 @@ def obj_params(self): aff = np.diag([1, 2, 3, 1]) # Try to retrieve allowed dims - try: - supported_dims = supported_dimensions(self.header_maker()) - self.example_shapes = (shape for shape in self.example_shapes - if len(shape) in supported_dims) - except: - pass + supported_dims = supported_dimensions(self.header_maker()) + self.example_shapes = (shape for shape in self.example_shapes + if len(shape) in supported_dims) # Try to retrieve allowed dtypes - try: - supported_dtypes = supported_np_types(self.header_maker()) - self.example_dtypes = (dtype for dtype in self.example_dtypes - if dtype in supported_dtypes) - except: - pass + supported_dtypes = supported_np_types(self.header_maker()) + self.example_dtypes = (dtype for dtype in self.example_dtypes + if dtype in supported_dtypes) def make_imaker(arr, aff, header=None): return lambda: self.image_maker(arr, aff, header) From 880b89ad16f084858555f6fa6c7b7bb3b77b6471 Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Tue, 20 Sep 2016 20:29:19 -0400 Subject: [PATCH 7/9] PL: Correct docstrings and variable naming (PEP8). --- nibabel/brainvoyager/bv.py | 124 +++++++++++++------------- nibabel/brainvoyager/tests/test_bv.py | 12 +-- 2 files changed, 69 insertions(+), 67 deletions(-) diff --git a/nibabel/brainvoyager/bv.py b/nibabel/brainvoyager/bv.py index b4ab59c54f..3a51833c78 100644 --- a/nibabel/brainvoyager/bv.py +++ b/nibabel/brainvoyager/bv.py @@ -48,8 +48,8 @@ ) -def readCString(f, nStrings=1, bufsize=1000, startPos=None, strip=True, - rewind=False): +def read_c_string(f, n_strings=1, bufsize=1000, start_pos=None, strip=True, + rewind=False): """Read a zero-terminated string from a file object. Read and return a zero-terminated string from a file object. @@ -57,13 +57,13 @@ def readCString(f, nStrings=1, bufsize=1000, startPos=None, strip=True, Parameters ---------- f : fileobj - File object to use - nStrings: int, optional + File object to use. Object should implement tell, seek, and read. + n_strings: int, optional Number of strings to search (and return). Default is 1. bufsize: int, optional Define the buffer size that should be searched for the string. Default is 1000 bytes. - startPos: int, optional + start_pos: int, optional Define the start file position from which to search. If None then start where the file object currently points to. Default is None. strip : bool, optional @@ -77,24 +77,19 @@ def readCString(f, nStrings=1, bufsize=1000, startPos=None, strip=True, ------- str_list : generator of string(s) """ - currentPos = f.tell() - if strip: - suffix = b'' - else: - suffix = b'\x00' - if startPos is not None: - f.seek(startPos) + current_pos = f.tell() + suffix = b'' if strip else b'\x00' + if start_pos is not None: + f.seek(start_pos) data = f.read(bufsize) lines = data.split(b'\x00') str_list = [] if rewind: - f.seek(currentPos) + f.seek(current_pos) else: - offset = 0 - for s in range(nStrings): - offset += len(lines[s]) + 1 - f.seek(currentPos + offset) - for s in range(nStrings): + offsets = [len(lines[s]) + 1 for s in range(n_strings)] + f.seek(current_pos + sum(offsets)) + for s in range(n_strings): str_list.append(lines[s] + suffix) return str_list @@ -111,8 +106,10 @@ def parse_BV_header(hdr_dict_proto, fileobj, parent_hdr_dict=None): tuple of format described in Notes below. fileobj : fileobj File object to use. Make sure that the current position is at the - beginning of the header (e.g. at 0). - parent_hdr_dict: OrderedDict + beginning of the header (e.g. at 0). Object should implement tell, + seek, and read. + parent_hdr_dict: None or OrderedDict, optional + Default is None. None results in empty `OrderedDict`. When parse_BV_header() is called recursively the already filled (parent) hdr_dict is passed to give access to n_fields_name fields outside the current scope (see below). @@ -158,12 +155,12 @@ def parse_BV_header(hdr_dict_proto, fileobj, parent_hdr_dict=None): 'NrOfLags' in the VMP file header). """ hdr_dict = OrderedDict() - for name, format, def_or_name in hdr_dict_proto: + for name, pack_format, def_or_name in hdr_dict_proto: # handle zero-terminated strings - if format == 'z': - value = readCString(fileobj)[0] + if pack_format == 'z': + value = read_c_string(fileobj)[0] # handle array fields - elif isinstance(format, tuple): + elif isinstance(pack_format, tuple): value = [] # check the length of the array to expect if def_or_name in hdr_dict: @@ -171,17 +168,17 @@ def parse_BV_header(hdr_dict_proto, fileobj, parent_hdr_dict=None): else: n_values = parent_hdr_dict[def_or_name] for i in range(n_values): - value.append(parse_BV_header(format, fileobj, hdr_dict)) + value.append(parse_BV_header(pack_format, fileobj, hdr_dict)) # handle conditional fields elif isinstance(def_or_name, tuple): if hdr_dict[def_or_name[1]] == def_or_name[2]: - bytes = fileobj.read(calcsize(format)) - value = unpack('<' + format, bytes)[0] + raw_bytes = fileobj.read(calcsize(pack_format)) + value = unpack('<' + pack_format, raw_bytes)[0] else: # assign the default value value = def_or_name[0] - else: # unpack bytes of type format - bytes = fileobj.read(calcsize(format)) - value = unpack('<' + format, bytes)[0] + else: # unpack raw_bytes of type pack_format + raw_bytes = fileobj.read(calcsize(pack_format)) + value = unpack('<' + pack_format, raw_bytes)[0] hdr_dict[name] = value return hdr_dict @@ -197,12 +194,13 @@ def pack_BV_header(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): hdr_dict_proto: tuple tuple of format described in Notes of :func:`parse_BV_header` hdr_dict: OrderedDict - hdr_dict that contains the fields and values to for the respective - BV file format. - parent_hdr_dict: OrderedDict - When parse_BV_header() is called recursively the already filled - (parent) hdr_dict is passed to give access to n_fields_name fields - outside the current scope (see below). + hdr_dict that contains the fields and values to for the respective + BV file format. + parent_hdr_dict: None or OrderedDict, optional + Default is None. None results in empty `OrderedDict`. + When parse_BV_header() is called recursively the already filled + (parent) hdr_dict is passed to give access to n_fields_name fields + outside the current scope (see below). Returns ------- @@ -210,13 +208,13 @@ def pack_BV_header(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): Binary representation of header ready for writing to file. """ binary_parts = [] - for name, format, def_or_name in hdr_dict_proto: + for name, pack_format, def_or_name in hdr_dict_proto: value = hdr_dict[name] # handle zero-terminated strings - if format == 'z': + if pack_format == 'z': part = value + b'\x00' # handle array fields - elif isinstance(format, tuple): + elif isinstance(pack_format, tuple): # check the length of the array to expect if def_or_name in hdr_dict: n_values = hdr_dict[def_or_name] @@ -224,16 +222,17 @@ def pack_BV_header(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): n_values = parent_hdr_dict[def_or_name] sub_parts = [] for i in range(n_values): - sub_parts.append(pack_BV_header(format, value[i], hdr_dict)) + sub_parts.append(pack_BV_header(pack_format, value[i], + hdr_dict)) part = b''.join(sub_parts) # handle conditional fields elif isinstance(def_or_name, tuple): if hdr_dict[def_or_name[1]] == def_or_name[2]: - part = pack('<' + format, value) + part = pack('<' + pack_format, value) else: continue else: - part = pack('<' + format, value) + part = pack('<' + pack_format, value) binary_parts.append(part) return b''.join(binary_parts) @@ -249,12 +248,13 @@ def calc_BV_header_size(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): hdr_dict_proto: tuple tuple of format described in Notes of :func:`parse_BV_header` hdr_dict: OrderedDict - hdr_dict that contains the fields and values to for the respective - BV file format. - parent_hdr_dict: OrderedDict - When parse_BV_header() is called recursively the already filled - (parent) hdr_dict is passed to give access to n_fields_name fields - outside the current scope (see below). + hdr_dict that contains the fields and values to for the respective + BV file format. + parent_hdr_dict: None or OrderedDict, optional + Default is None. None results in empty `OrderedDict`. + When parse_BV_header() is called recursively the already filled + (parent) hdr_dict is passed to give access to n_fields_name fields + outside the current scope (see below). Returns ------- @@ -262,13 +262,13 @@ def calc_BV_header_size(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): Size of header when packed into bytes ready for writing to file. """ hdr_size = 0 - for name, format, def_or_name in hdr_dict_proto: + for name, pack_format, def_or_name in hdr_dict_proto: value = hdr_dict[name] # handle zero-terminated strings - if format == 'z': + if pack_format == 'z': hdr_size += len(value) + 1 # handle array fields - elif isinstance(format, tuple): + elif isinstance(pack_format, tuple): # check the length of the array to expect if def_or_name in hdr_dict: n_values = hdr_dict[def_or_name] @@ -277,15 +277,16 @@ def calc_BV_header_size(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): for i in range(n_values): # recursively iterate through the fields of all items # in the array - hdr_size += calc_BV_header_size(format, value[i], hdr_dict) + hdr_size += calc_BV_header_size(pack_format, value[i], + hdr_dict) # handle conditional fields elif isinstance(def_or_name, tuple): if hdr_dict[def_or_name[1]] == def_or_name[2]: - hdr_size += calcsize(format) + hdr_size += calcsize(pack_format) else: continue else: - hdr_size += calcsize(format) + hdr_size += calcsize(pack_format) return hdr_size @@ -319,9 +320,9 @@ def update_BV_header(hdr_dict_proto, hdr_dict_old, hdr_dict_new, An updated version hdr_dict correcting effects of changed nested and conditional fields. """ - for name, format, def_or_name in hdr_dict_proto: + for name, pack_format, def_or_name in hdr_dict_proto: # handle nested loop fields - if isinstance(format, tuple): + if isinstance(pack_format, tuple): # calculate the change of array length and the new array length if def_or_name in hdr_dict_old: delta_values = hdr_dict_new[def_or_name] - \ @@ -333,13 +334,14 @@ def update_BV_header(hdr_dict_proto, hdr_dict_old, hdr_dict_new, n_values = parent_new[def_or_name] if delta_values > 0: # add nested loops for i in range(delta_values): - hdr_dict_new[name].append(_proto2default(format, hdr_dict_new)) + hdr_dict_new[name].append(_proto2default(pack_format, + hdr_dict_new)) elif delta_values < 0: # remove nested loops for i in range(abs(delta_values)): hdr_dict_new[name].pop() # loop over nested fields for i in range(n_values): - update_BV_header(format, hdr_dict_old[name][i], + update_BV_header(pack_format, hdr_dict_old[name][i], hdr_dict_new[name][i], hdr_dict_old, hdr_dict_new) return hdr_dict_new @@ -354,8 +356,8 @@ def _proto2default(proto, parent_default_hdr=None): See :func:`parse_BV_header` for description of `proto` format. """ default_hdr = OrderedDict() - for name, format, def_or_name in proto: - if isinstance(format, tuple): + for name, pack_format, def_or_name in proto: + if isinstance(pack_format, tuple): value = [] # check the length of the array to expect if def_or_name in default_hdr: @@ -363,7 +365,7 @@ def _proto2default(proto, parent_default_hdr=None): else: n_values = parent_default_hdr[def_or_name] for i in range(n_values): - value.append(_proto2default(format, default_hdr)) + value.append(_proto2default(pack_format, default_hdr)) default_hdr[name] = value # handle conditional fields elif isinstance(def_or_name, tuple): diff --git a/nibabel/brainvoyager/tests/test_bv.py b/nibabel/brainvoyager/tests/test_bv.py index de0c67959b..37ba7a2f59 100644 --- a/nibabel/brainvoyager/tests/test_bv.py +++ b/nibabel/brainvoyager/tests/test_bv.py @@ -12,7 +12,7 @@ import numpy as np from ...loadsave import load from ...tmpdirs import InTemporaryDirectory -from ..bv import (readCString, parse_BV_header, pack_BV_header, BvFileHeader, +from ..bv import (read_c_string, parse_BV_header, pack_BV_header, BvFileHeader, calc_BV_header_size, _proto2default, update_BV_header, parse_st, combine_st, BvError) from ..bv_vtc import VTC_HDR_DICT_PROTO, BvVtcHeader @@ -121,7 +121,7 @@ b'\x00sample\x00']) -def test_readCString(): +def test_read_c_string(): # sample binary block binary = b'test.fmr\x00test.prt\x00' with InTemporaryDirectory(): @@ -137,7 +137,7 @@ def test_readCString(): fread = open(path, 'rb') # test readout of one string - assert_equal([s for s in readCString(fread)], [b'test.fmr']) + assert_equal([s for s in read_c_string(fread)], [b'test.fmr']) # test new file position assert_equal(fread.tell(), 9) @@ -146,14 +146,14 @@ def test_readCString(): fread.seek(0) # test readout of two strings - assert_equal([s for s in readCString(fread, 2, rewind=True)], + assert_equal([s for s in read_c_string(fread, 2, rewind=True)], [b'test.fmr', b'test.prt']) # test automatic rewind assert_equal(fread.tell(), 0) # test readout of two strings with trailing zeros - assert_equal([s for s in readCString(fread, 2, strip=False)], + assert_equal([s for s in read_c_string(fread, 2, strip=False)], [b'test.fmr\x00', b'test.prt\x00']) # test new file position @@ -161,7 +161,7 @@ def test_readCString(): # test readout of one string from given position fread.seek(0) - assert_equal([s for s in readCString(fread, startPos=9)], + assert_equal([s for s in read_c_string(fread, start_pos=9)], [b'test.prt']) fread.close() From fdd34b5decb505ad343764cb9128009c9e64b367 Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Tue, 20 Sep 2016 20:50:49 -0400 Subject: [PATCH 8/9] DOC+PL: docstring corrections and minor clarifications. --- nibabel/brainvoyager/bv.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/nibabel/brainvoyager/bv.py b/nibabel/brainvoyager/bv.py index 3a51833c78..8c182eb84b 100644 --- a/nibabel/brainvoyager/bv.py +++ b/nibabel/brainvoyager/bv.py @@ -230,6 +230,7 @@ def pack_BV_header(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): if hdr_dict[def_or_name[1]] == def_or_name[2]: part = pack('<' + pack_format, value) else: + # skip to next header field if condition is not met continue else: part = pack('<' + pack_format, value) @@ -309,7 +310,7 @@ def update_BV_header(hdr_dict_proto, hdr_dict_old, hdr_dict_new, When update_BV_header() is called recursively the not yet updated (parent) hdr_dict is passed to give access to n_fields_name fields outside the current scope (see below). - parent_new: OrderedDict + parent_new: None or OrderedDict, optional When update_BV_header() is called recursively the not yet updated (parent) hdr_dict is passed to give access to n_fields_name fields outside the current scope (see below). @@ -321,16 +322,18 @@ def update_BV_header(hdr_dict_proto, hdr_dict_old, hdr_dict_new, conditional fields. """ for name, pack_format, def_or_name in hdr_dict_proto: - # handle nested loop fields - if isinstance(pack_format, tuple): + # handle only nested loop fields + if not isinstance(pack_format, tuple): + continue + else: # calculate the change of array length and the new array length if def_or_name in hdr_dict_old: - delta_values = hdr_dict_new[def_or_name] - \ - hdr_dict_old[def_or_name] + delta_values = (hdr_dict_new[def_or_name] - + hdr_dict_old[def_or_name]) n_values = hdr_dict_new[def_or_name] else: - delta_values = parent_new[def_or_name] - \ - parent_old[def_or_name] + delta_values = (parent_new[def_or_name] - + parent_old[def_or_name]) n_values = parent_new[def_or_name] if delta_values > 0: # add nested loops for i in range(delta_values): @@ -384,8 +387,8 @@ def combine_st(st_array, inv=False): Parameters ---------- - st_array: array - array filled with transformation matrices of shape (4, 4) + st_array: array of shape (n, 4, 4) + array filled with n transformation matrices of shape (4, 4) inv: boolean Set to true to invert the transformation matrices before @@ -754,12 +757,12 @@ def get_bbox_center(self): framing cube. """ hdr = self._hdr_dict - x = hdr['x_start'] + \ - ((hdr['x_end'] - hdr['x_start']) / 2) - y = hdr['y_start'] + \ - ((hdr['y_end'] - hdr['y_start']) / 2) - z = hdr['z_start'] + \ - ((hdr['z_end'] - hdr['z_start']) / 2) + x = (hdr['x_start'] + + ((hdr['x_end'] - hdr['x_start']) / 2)) + y = (hdr['y_start'] + + ((hdr['y_end'] - hdr['y_start']) / 2)) + z = (hdr['z_start'] + + ((hdr['z_end'] - hdr['z_start']) / 2)) return z, y, x def get_zooms(self): From 351ca44d52880575e022ef33c491ced6424ac037 Mon Sep 17 00:00:00 2001 From: Thomas Emmerling Date: Mon, 27 Feb 2017 00:52:08 +0000 Subject: [PATCH 9/9] DOC+PL+TEST: Correct docstrings and change to property decorators. --- nibabel/brainvoyager/bv.py | 100 ++++++++++++-------- nibabel/brainvoyager/bv_vmp.py | 8 +- nibabel/brainvoyager/bv_vmr.py | 43 ++++----- nibabel/brainvoyager/tests/test_bv.py | 109 ++++++++++------------ nibabel/brainvoyager/tests/test_bv_vmp.py | 10 +- nibabel/brainvoyager/tests/test_bv_vtc.py | 10 +- nibabel/tests/test_image_api.py | 2 +- 7 files changed, 147 insertions(+), 135 deletions(-) diff --git a/nibabel/brainvoyager/bv.py b/nibabel/brainvoyager/bv.py index 8c182eb84b..cd5879ef92 100644 --- a/nibabel/brainvoyager/bv.py +++ b/nibabel/brainvoyager/bv.py @@ -273,6 +273,8 @@ def calc_BV_header_size(hdr_dict_proto, hdr_dict, parent_hdr_dict=None): # check the length of the array to expect if def_or_name in hdr_dict: n_values = hdr_dict[def_or_name] + # handle cases when n_values is resides outside of the + # current scope (e.g. nr_of_timepoints in VMP_HDR_DICT_PROTO) else: n_values = parent_hdr_dict[def_or_name] for i in range(n_values): @@ -306,7 +308,7 @@ def update_BV_header(hdr_dict_proto, hdr_dict_old, hdr_dict_new, hdr_dict before any changes. hdr_dict_new: OrderedDict hdr_dict with changed fields in n_fields_name or c_fields_name fields. - parent_old: OrderedDict + parent_old: None or OrderedDict, optional When update_BV_header() is called recursively the not yet updated (parent) hdr_dict is passed to give access to n_fields_name fields outside the current scope (see below). @@ -325,28 +327,27 @@ def update_BV_header(hdr_dict_proto, hdr_dict_old, hdr_dict_new, # handle only nested loop fields if not isinstance(pack_format, tuple): continue + # calculate the change of array length and the new array length + if def_or_name in hdr_dict_old: + delta_values = (hdr_dict_new[def_or_name] - + hdr_dict_old[def_or_name]) + n_values = hdr_dict_new[def_or_name] else: - # calculate the change of array length and the new array length - if def_or_name in hdr_dict_old: - delta_values = (hdr_dict_new[def_or_name] - - hdr_dict_old[def_or_name]) - n_values = hdr_dict_new[def_or_name] - else: - delta_values = (parent_new[def_or_name] - - parent_old[def_or_name]) - n_values = parent_new[def_or_name] - if delta_values > 0: # add nested loops - for i in range(delta_values): - hdr_dict_new[name].append(_proto2default(pack_format, - hdr_dict_new)) - elif delta_values < 0: # remove nested loops - for i in range(abs(delta_values)): - hdr_dict_new[name].pop() - # loop over nested fields - for i in range(n_values): - update_BV_header(pack_format, hdr_dict_old[name][i], - hdr_dict_new[name][i], hdr_dict_old, - hdr_dict_new) + delta_values = (parent_new[def_or_name] - + parent_old[def_or_name]) + n_values = parent_new[def_or_name] + if delta_values > 0: # add nested loops + for i in range(delta_values): + hdr_dict_new[name].append(_proto2default(pack_format, + hdr_dict_new)) + elif delta_values < 0: # remove nested loops + for i in range(abs(delta_values)): + hdr_dict_new[name].pop() + # loop over nested fields + for i in range(n_values): + update_BV_header(pack_format, hdr_dict_old[name][i], + hdr_dict_new[name][i], hdr_dict_old, + hdr_dict_new) return hdr_dict_new @@ -453,7 +454,7 @@ class BvFileHeader(Header): # format defaults # BV files are radiological (left-is-right) by default # (VTC files have a flag for that, however) - default_x_flip = True + default_xflip = True default_endianness = '<' # BV files are always little-endian allowed_dtypes = [1, 2, 3] default_dtype = 2 @@ -470,9 +471,10 @@ def __init__(self, Parameters ---------- - binaryblock : {None, string} optional - binary block to set into header. By default, None, in - which case we insert the default empty header block + hdr_dict : None or OrderedDict, optional + An OrderedDict containing all header fields parsed from the file. + By default, None, in which case we create a default hdr_dict from + the corresponding _HDR_DICT_PROTO endianness : {None, '<','>', other endian code} string, optional endianness of the binaryblock. If None, guess endianness from the data. @@ -641,11 +643,12 @@ def set_data_dtype(self, datatype): raise HeaderDataError( 'File format does not support setting of header!') - def get_xflip(self): - """Get xflip for data.""" - return self.default_x_flip + @property + def xflip(self): + return self.default_xflip - def set_xflip(self, xflip): + @xflip.setter + def xflip(self, xflip): """Set xflip for data.""" if xflip is True: return @@ -666,7 +669,7 @@ def get_base_affine(self): Note that we get the translations from the center of the (guessed) framing cube of the referenced VMR (anatomical) file. - Internal storage of the image is ZYXT, where (in patient coordiante/ + Internal storage of the image is ZYXT, where (in patient coordinates/ real world orientations): Z := axis increasing from right to left (R to L) Y := axis increasing from superior to inferior (S to I) @@ -674,7 +677,7 @@ def get_base_affine(self): T := volumes (if present in file format) """ zooms = self.get_zooms() - if not self.get_xflip(): + if not self.xflip: # make the BV internal Z axis neurological (left-is-left); # not default in BV files! zooms = (-zooms[0], zooms[1], zooms[2]) @@ -689,7 +692,7 @@ def get_base_affine(self): rot[:, 2] = [0, -zooms[1], 0] # compute the translation - fcc = np.array(self.get_framing_cube()) / 2 # center of framing cube + fcc = np.array(self.framing_cube) / 2 # center of framing cube bbc = np.array(self.get_bbox_center()) # center of bounding box tra = np.dot((bbc - fcc), rot) @@ -700,11 +703,14 @@ def get_base_affine(self): return M - get_best_affine = get_base_affine + def get_best_affine(self): + return self.get_base_affine() - get_default_affine = get_base_affine + def get_default_affine(self): + return self.get_base_affine() - get_affine = get_base_affine + def get_affine(self): + return self.get_base_affine() def _guess_framing_cube(self): """Guess the dimensions of the framing cube. @@ -729,7 +735,8 @@ def _guess_framing_cube(self): else: return fc, fc, fc - def get_framing_cube(self): + @property + def framing_cube(self): """Get the dimensions of the framing cube. Get the dimensions of the framing cube that constitutes the @@ -739,7 +746,8 @@ def get_framing_cube(self): """ return self._framing_cube - def set_framing_cube(self, fc): + @framing_cube.setter + def framing_cube(self, fc): """Set the dimensions of the framing cube. Set the dimensions of the framing cube that constitutes the @@ -771,10 +779,24 @@ def get_zooms(self): for d in shape[0:3]) def set_zooms(self, zooms): + """Set the zooms for the image. + + Voxel dimensions of functional data in BV file formats are + always in relationship to the voxel dimensions in a VMR file and + therefore need to be equal for all three spatial dimensions. + + Parameters + ---------- + zooms : int or sequence + An integer or a sequence of integers specifying the relationship + between voxel dimensions and real-world dimensions. If a single + integer is used it is applied to all spatial dimensions. If a + sequence of integers is used all dimensions have to be equal. + """ if type(zooms) == int: self._hdr_dict['resolution'] = zooms else: - if any([zooms[i] != zooms[i + 1] for i in range(len(zooms) - 1)]): + if np.any(np.diff(zooms)): raise BvError('Zooms for all dimensions must be equal!') else: self._hdr_dict['resolution'] = int(zooms[0]) diff --git a/nibabel/brainvoyager/bv_vmp.py b/nibabel/brainvoyager/bv_vmp.py index b5e13c14e4..ba5b4a9ed9 100644 --- a/nibabel/brainvoyager/bv_vmp.py +++ b/nibabel/brainvoyager/bv_vmp.py @@ -113,6 +113,7 @@ def get_data_shape(self): def set_data_shape(self, shape=None, zyx=None, n=None): ''' Set shape of data + To conform with nibabel standards this implements shape. However, to fill the BvVmpHeader with sensible information use the zyx and the n parameter instead. @@ -164,14 +165,16 @@ def set_data_shape(self, shape=None, zyx=None, n=None): self._hdr_dict = update_BV_header(self.hdr_dict_proto, hdr_dict_old, self._hdr_dict) - def get_framing_cube(self): + @property + def framing_cube(self): ''' Get the dimensions of the framing cube that constitutes the coordinate system boundaries for the bounding box. ''' hdr = self._hdr_dict return hdr['dim_z'], hdr['dim_y'], hdr['dim_x'] - def set_framing_cube(self, fc): + @framing_cube.setter + def framing_cube(self, fc): ''' Set the dimensions of the framing cube that constitutes the coordinate system boundaries for the bounding box. @@ -191,5 +194,6 @@ class BvVmpImage(BvFileImage): files_types = (('image', '.vmp'),) valid_exts = ('.vmp',) + load = BvVmpImage.load save = BvVmpImage.instance_to_filename diff --git a/nibabel/brainvoyager/bv_vmr.py b/nibabel/brainvoyager/bv_vmr.py index 30919d005b..23dba82349 100644 --- a/nibabel/brainvoyager/bv_vmr.py +++ b/nibabel/brainvoyager/bv_vmr.py @@ -73,13 +73,13 @@ ) -def computeOffsetPostHDR(hdr_dict, fileobj): - currentSeek = fileobj.tell() - return currentSeek + (hdr_dict['dim_x'] * hdr_dict['dim_y'] * - hdr_dict['dim_z']) +def compute_offset_post_hdr(hdr_dict, fileobj): + current_seek = fileobj.tell() + return current_seek + (hdr_dict['dim_x'] * hdr_dict['dim_y'] * + hdr_dict['dim_z']) -def concatePrePos(preDict, posDict): +def merge_pre_pos(preDict, posDict): temp = preDict.copy() temp.update(posDict) return temp @@ -175,7 +175,7 @@ def get_base_affine(self): rot[:, 2] = [0, -zooms[1], 0] # compute the translation - fcc = np.array(self.get_framing_cube()) / 2 # center of framing cube + fcc = np.array(self.framing_cube) / 2 # center of framing cube bbc = np.array(self.get_bbox_center()) # center of bounding box tra = np.dot((bbc - fcc), rot) @@ -187,30 +187,24 @@ def get_base_affine(self): # look for additional transformations in past_spatial_trans and combine # with M if self._hdr_dict['past_spatial_trans']: - STarray = np.zeros((len(self._hdr_dict['past_spatial_trans']), - 4, 4)) + st_array = np.zeros((len(self._hdr_dict['past_spatial_trans']), + 4, 4)) for st in range(len(self._hdr_dict['past_spatial_trans'])): - STarray[st, :, :] = \ + st_array[st, :, :] = \ parse_st(self._hdr_dict['past_spatial_trans'][st]) - combinedST = combine_st(STarray, inv=True) - M = np.dot(M, combinedST) + combined_st = combine_st(st_array, inv=True) + M = np.dot(M, combined_st) return M - get_best_affine = get_base_affine - - get_default_affine = get_base_affine - - get_affine = get_base_affine - @classmethod def from_fileobj(klass, fileobj, endianness=default_endianness, check=True): - hdr_dictPre = parse_BV_header(VMR_PRHDR_DICT_PROTO, fileobj) + hdr_dict_pre = parse_BV_header(VMR_PRHDR_DICT_PROTO, fileobj) # calculate new seek for the post data header - newSeek = computeOffsetPostHDR(hdr_dictPre, fileobj) - fileobj.seek(newSeek) - hdr_dictPos = parse_BV_header(VMR_PSHDR_DICT_PROTO, fileobj) - hdr_dict = concatePrePos(hdr_dictPre, hdr_dictPos) + new_seek = compute_offset_post_hdr(hdr_dict_pre, fileobj) + fileobj.seek(new_seek) + hdr_dict_pos = parse_BV_header(VMR_PSHDR_DICT_PROTO, fileobj) + hdr_dict = merge_pre_pos(hdr_dict_pre, hdr_dict_pos) # The offset is always 8 for VMR files. offset = 8 return klass(hdr_dict, endianness, check, offset) @@ -219,7 +213,7 @@ def get_bbox_center(self): """Get the center coordinate of the bounding box. Not required for VMR files """ - return np.array([self.get_framing_cube() / 2 for d in range(3)]) + return np.array([self.framing_cube / 2 for d in range(3)]) def get_zooms(self): return (self._hdr_dict['vox_res_z'], self._hdr_dict['vox_res_y'], @@ -236,7 +230,7 @@ def write_to(self, fileobj): sizePrH = calc_BV_header_size(VMR_PRHDR_DICT_PROTO, self._hdr_dict) # write the preHeader fileobj.write(binaryblock[0:sizePrH]) - fileobj.seek(computeOffsetPostHDR(self._hdr_dict, fileobj)) + fileobj.seek(compute_offset_post_hdr(self._hdr_dict, fileobj)) fileobj.write(binaryblock[sizePrH:]) @@ -250,5 +244,6 @@ class BvVmrImage(BvFileImage): files_types = (('image', '.vmr'),) valid_exts = ('.vmr',) + load = BvVmrImage.load save = BvVmrImage.instance_to_filename diff --git a/nibabel/brainvoyager/tests/test_bv.py b/nibabel/brainvoyager/tests/test_bv.py index 37ba7a2f59..6ec915ae73 100644 --- a/nibabel/brainvoyager/tests/test_bv.py +++ b/nibabel/brainvoyager/tests/test_bv.py @@ -125,76 +125,64 @@ def test_read_c_string(): # sample binary block binary = b'test.fmr\x00test.prt\x00' with InTemporaryDirectory(): - # create a tempfile + # create a tempfile and write the binary block to it path = 'test.header' - fwrite = open(path, 'wb') - - # write the binary block to it - fwrite.write(binary) - fwrite.close() + with open(path, 'wb') as fwrite: + fwrite.write(binary) # open it again - fread = open(path, 'rb') - - # test readout of one string - assert_equal([s for s in read_c_string(fread)], [b'test.fmr']) - - # test new file position - assert_equal(fread.tell(), 9) - - # manually rewind - fread.seek(0) - - # test readout of two strings - assert_equal([s for s in read_c_string(fread, 2, rewind=True)], - [b'test.fmr', b'test.prt']) - - # test automatic rewind - assert_equal(fread.tell(), 0) - - # test readout of two strings with trailing zeros - assert_equal([s for s in read_c_string(fread, 2, strip=False)], - [b'test.fmr\x00', b'test.prt\x00']) - - # test new file position - assert_equal(fread.tell(), 18) - - # test readout of one string from given position - fread.seek(0) - assert_equal([s for s in read_c_string(fread, start_pos=9)], - [b'test.prt']) - fread.close() + with open(path, 'rb') as fread: + # test readout of one string + assert_equal([s for s in read_c_string(fread)], [b'test.fmr']) + # test new file position + assert_equal(fread.tell(), 9) + # manually rewind + fread.seek(0) + # test readout of two strings + assert_equal([s for s in read_c_string(fread, 2, rewind=True)], + [b'test.fmr', b'test.prt']) + # test automatic rewind + assert_equal(fread.tell(), 0) + # test readout of two strings with trailing zeros + assert_equal([s for s in read_c_string(fread, 2, strip=False)], + [b'test.fmr\x00', b'test.prt\x00']) + # test new file position + assert_equal(fread.tell(), 18) + # test readout of one string from given position + fread.seek(0) + assert_equal([s for s in read_c_string(fread, start_pos=9)], + [b'test.prt']) def test_combine_st(): vmr = BvVmrImage.from_filename(vmr_file) - STarray = [] + st_array = [] for st in range(vmr.header._hdr_dict['nr_of_past_spatial_trans']): - STarray.append(parse_st( + st_array.append(parse_st( vmr.header._hdr_dict['past_spatial_trans'][st])) - STarray = np.array(STarray) - combinedST = combine_st(STarray, inv=True) - correctCombinedST = [[1., 0., 0., 0.], - [0., 1., 0., -1.], - [0., 0., 1., 1.], - [0., 0., 0., 1.]] - assert_array_equal(combinedST, correctCombinedST) - combinedST = combine_st(STarray, inv=False) - correctCombinedST = [[1., 0., 0., 0.], - [0., 1., 0., 1.], - [0., 0., 1., -1.], - [0., 0., 0., 1.]] - assert_array_equal(combinedST, correctCombinedST) + st_array = np.array(st_array) + combined_st = combine_st(st_array, inv=True) + correct_combined_st = [[1., 0., 0., 0.], + [0., 1., 0., -1.], + [0., 0., 1., 1.], + [0., 0., 0., 1.]] + assert_array_equal(combined_st, correct_combined_st) + combined_st = combine_st(st_array, inv=False) + correct_combined_st = [[1., 0., 0., 0.], + [0., 1., 0., 1.], + [0., 0., 1., -1.], + [0., 0., 0., 1.]] + assert_array_equal(combined_st, correct_combined_st) def test_parse_st(): vmr = BvVmrImage.from_filename(vmr_file) ST = parse_st(vmr.header._hdr_dict['past_spatial_trans'][0]) - correctST = [[1., 0., 0., -1.], - [0., 1., 0., 0.], - [0., 0., 1., -1.], - [0., 0., 0., 1.]] - assert_array_equal(ST, correctST) + correct_st = [[1., 0., 0., -1.], + [0., 1., 0., 0.], + [0., 0., 1., -1.], + [0., 0., 0., 1.]] + assert_array_equal(ST, correct_st) # parse_st will only handle 4x4 matrices vmr.header._hdr_dict['past_spatial_trans'][0]['nr_of_trans_val'] = 10 @@ -265,13 +253,16 @@ def test_BvFileHeader_update_BV_header(): def test_BvFileHeader_xflip(): bv = BvFileHeader() - assert_true(bv.get_xflip()) + assert_true(bv.xflip) # should only return - bv.set_xflip(True) + bv.xflip = True + + def set_xflip_false(): + bv.xflip = False # cannot flip most BV images - assert_raises(BvError, bv.set_xflip, False) + assert_raises(BvError, set_xflip_false) def test_BvFileHeader_endianness(): diff --git a/nibabel/brainvoyager/tests/test_bv_vmp.py b/nibabel/brainvoyager/tests/test_bv_vmp.py index 0e0555aef3..5a275d97ab 100644 --- a/nibabel/brainvoyager/tests/test_bv_vmp.py +++ b/nibabel/brainvoyager/tests/test_bv_vmp.py @@ -350,8 +350,8 @@ def test_BvVmpHeader_set_data_shape(): def test_BvVmpHeader_set_framing_cube(): vmp = BvVmpHeader() - assert_equal(vmp.get_framing_cube(), (256, 256, 256)) - vmp.set_framing_cube((512, 512, 512)) - assert_equal(vmp.get_framing_cube(), (512, 512, 512)) - vmp.set_framing_cube((512, 513, 514)) - assert_equal(vmp.get_framing_cube(), (512, 513, 514)) + assert_equal(vmp.framing_cube, (256, 256, 256)) + vmp.framing_cube = (512, 512, 512) + assert_equal(vmp.framing_cube, (512, 512, 512)) + vmp.framing_cube = (512, 513, 514) + assert_equal(vmp.framing_cube, (512, 513, 514)) diff --git a/nibabel/brainvoyager/tests/test_bv_vtc.py b/nibabel/brainvoyager/tests/test_bv_vtc.py index e10c548b4b..9c8693c434 100644 --- a/nibabel/brainvoyager/tests/test_bv_vtc.py +++ b/nibabel/brainvoyager/tests/test_bv_vtc.py @@ -97,11 +97,11 @@ def test_BvVtcHeader_set_data_shape(): def test_BvVtcHeader_set_framing_cube(): vtc = BvVtcHeader() - assert_equal(vtc.get_framing_cube(), (256, 256, 256)) - vtc.set_framing_cube((512, 512, 512)) - assert_equal(vtc.get_framing_cube(), (512, 512, 512)) - vtc.set_framing_cube((512, 513, 514)) - assert_equal(vtc.get_framing_cube(), (512, 513, 514)) + assert_equal(vtc.framing_cube, (256, 256, 256)) + vtc.framing_cube = (512, 512, 512) + assert_equal(vtc.framing_cube, (512, 512, 512)) + vtc.framing_cube = (512, 513, 514) + assert_equal(vtc.framing_cube, (512, 513, 514)) def test_BvVtcHeader_xflip(): diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index d63620a0f4..f968f29963 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -196,7 +196,7 @@ def validate_dtype(self, imaker, params): supported_dtypes = supported_np_types(img.header_class()) if new_dtype not in supported_dtypes: new_dtype = supported_dtypes.pop() - except: + except TypeError: pass img.set_data_dtype(new_dtype) assert_equal(img.get_data_dtype().type, new_dtype)