diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..1567824d93 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "nibabel-data/nitest-balls1"] + path = nibabel-data/nitest-balls1 + url = https://github.com/yarikoptic/nitest-balls1 diff --git a/.travis.yml b/.travis.yml index 70d286383b..0596882de0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,8 @@ before_install: # e.g. pip install -r requirements.txt # --use-mirrors install: - python setup.py install + # Point to nibabel data directory + - export NIBABEL_DATA_DIR="$PWD/nibabel-data" # command to run tests, e.g. python setup.py test script: # Change into an innocuous directory and find tests from installation diff --git a/COPYING b/COPYING index 835f251c25..87abe50f92 100644 --- a/COPYING +++ b/COPYING @@ -191,3 +191,17 @@ The files:: are from http://psydata.ovgu.de/philips_achieva_testfiles, and released under the PDDL version 1.0 available at http://opendatacommons.org/licenses/pddl/1.0/ + +The files: + + nibabel/nibabel/tests/data/DTI.PAR + nibabel/nibabel/tests/data/NA.PAR + nibabel/nibabel/tests/data/T1.PAR + nibabel/nibabel/tests/data/T2-interleaved.PAR + nibabel/nibabel/tests/data/T2.PAR + nibabel/nibabel/tests/data/T2_-interleaved.PAR + nibabel/nibabel/tests/data/T2_.PAR + nibabel/nibabel/tests/data/fieldmap.PAR + +are from https://github.com/yarikoptic/nitest-balls1, also released under the +the PDDL version 1.0 available at http://opendatacommons.org/licenses/pddl/1.0/ diff --git a/bin/parrec2nii b/bin/parrec2nii index d9e077b27a..166bcfdcb2 100755 --- a/bin/parrec2nii +++ b/bin/parrec2nii @@ -4,105 +4,155 @@ from __future__ import division, print_function, absolute_import from optparse import OptionParser, Option +import numpy as np import sys import os import gzip import nibabel import nibabel.parrec as pr +from nibabel.parrec import one_line +from nibabel.mriutils import calculate_dwell_time, MRIError import nibabel.nifti1 as nifti1 from nibabel.filename_parser import splitext_addext -from nibabel.volumeutils import seek_tell - -# global verbosity switch -verbose_switch = False +from nibabel.volumeutils import array_to_file, fname_ext_ul_case def get_opt_parser(): # use module docstring for help output p = OptionParser( - usage="%s [OPTIONS] \n\n" % sys.argv[0] + __doc__, - version="%prog " + nibabel.__version__) - + usage="%s [OPTIONS] \n\n" % sys.argv[0] + __doc__, + version="%prog " + nibabel.__version__) p.add_option( - Option("-v", "--verbose", action="store_true", - dest="verbose", default=False, - help="Make some noise.")) + Option("-v", "--verbose", action="store_true", dest="verbose", + default=False, + help="""Make some noise.""")) p.add_option( - Option("-o", "--output-dir", - action="store", type="string", dest="outdir", - default=None, - help=\ -"""Destination directory for NIfTI files. Default: current directory.""")) + Option("-o", "--output-dir", action="store", type="string", + dest="outdir", default=None, + help=one_line("""Destination directory for NIfTI files. + Default: current directory."""))) p.add_option( Option("-c", "--compressed", action="store_true", dest="compressed", default=False, help="Whether to write compressed NIfTI files or not.")) p.add_option( - Option("--origin", action="store", - dest="origin", default="scanner", - help=\ -"""Reference point of the q-form transformation of the NIfTI image. If 'scanner' -the (0,0,0) coordinates will refer to the scanner's iso center. If 'fov', this -coordinate will be the center of the recorded volume (field of view). Default: -'scanner'.""")) + Option("-p", "--permit-truncated", action="store_true", + dest="permit_truncated", default=False, + help=one_line( + """Permit conversion of truncated recordings. Support for + this is experimental, and results *must* be checked + afterward for validity."""))) + p.add_option( + Option("-b", "--bvs", action="store_true", dest="bvs", default=False, + help=one_line( + """Output bvals/bvecs files in addition to NIFTI + image."""))) + p.add_option( + Option("-d", "--dwell-time", action="store_true", default=False, + dest="dwell_time", + help=one_line( + """Calculate the scan dwell time. If supplied, the magnetic + field strength should also be supplied using + --field-strength (default 3). The field strength must be + supplied because it is not encoded in the PAR/REC + format."""))) + p.add_option( + Option("--field-strength", action="store", type="float", + dest="field_strength", + help=one_line( + """The magnetic field strength of the recording, only needed + for --dwell-time. The field strength must be supplied + because it is not encoded in the PAR/REC format."""))) p.add_option( - Option("--minmax", action="store", nargs=2, - dest="minmax", help=\ -"""Mininum and maximum settings to be stored in the NIfTI header. If any of -them is set to 'parse', the scaled data is scanned for the actual minimum and -maximum. To bypass this potentially slow and memory intensive step (the data -has to be scaled and fully loaded into memory), fixed values can be provided as -space-separated pair, e.g. '5.4 120.4'. It is possible to set a fixed minimum -as scan for the actual maximum (and vice versa). Default: 'parse parse'.""")) + Option("--origin", action="store", dest="origin", default="scanner", + help=one_line( + """Reference point of the q-form transformation of the NIfTI + image. If 'scanner' the (0,0,0) coordinates will refer to + the scanner's iso center. If 'fov', this coordinate will be + the center of the recorded volume (field of view). Default: + 'scanner'."""))) + p.add_option( + Option("--minmax", action="store", nargs=2, dest="minmax", + help=one_line( + """Mininum and maximum settings to be stored in the NIfTI + header. If any of them is set to 'parse', the scaled data is + scanned for the actual minimum and maximum. To bypass this + potentially slow and memory intensive step (the data has to + be scaled and fully loaded into memory), fixed values can be + provided as space-separated pair, e.g. '5.4 120.4'. It is + possible to set a fixed minimum as scan for the actual + maximum (and vice versa). Default: 'parse parse'."""))) p.set_defaults(minmax=('parse', 'parse')) p.add_option( - Option("--store-header", action="store_true", - dest="store_header", default=False, - help=\ -"""If set, all information from the PAR header is stored in an extension of -the NIfTI file header. Default: off""")) + Option("--store-header", action="store_true", dest="store_header", + default=False, + help=one_line( + """If set, all information from the PAR header is stored in + an extension ofthe NIfTI file header. Default: off"""))) p.add_option( Option("--scaling", action="store", dest="scaling", default='dv', - help=\ -"""Choose data scaling setting. The PAR header defines two different data -scaling settings: 'dv' (values displayed on console) and 'fp' (floating point -values). Either one can be chosen, or scaling can be disabled completely -('off'). Note that neither method will actually scale the data, but just store -the corresponding settings in the NIfTI header. Default: 'dv'""")) + help=one_line( + """Choose data scaling setting. The PAR header defines two + different data scaling settings: 'dv' (values displayed on + console) and 'fp' (floating point values). Either one can be + chosen, or scaling can be disabled completely ('off'). Note + that neither method will actually scale the data, but just + store the corresponding settings in the NIfTI header, unless + non-uniform scaling is used, in which case the data is + stored in the file in scaled form. Default: 'dv'"""))) + p.add_option( + Option("--overwrite", action="store_true", dest="overwrite", + default=False, + help=one_line("""Overwrite file if it exists. Default: + False"""))) return p def verbose(msg, indent=0): - if verbose_switch: + if verbose.switch: print("%s%s" % (' ' * indent, msg)) + def error(msg, exit_code): sys.stderr.write(msg + '\n') sys.exit(exit_code) + def proc_file(infile, opts): - # load the PAR header - pr_img = pr.load(infile) + # figure out the output filename, and see if it exists + basefilename = splitext_addext(os.path.basename(infile))[0] + if opts.outdir is not None: + # set output path + basefilename = os.path.join(opts.outdir, basefilename) + + # prep a file + if opts.compressed: + verbose('Using gzip compression') + outfilename = basefilename + '.nii.gz' + else: + outfilename = basefilename + '.nii' + if os.path.isfile(outfilename) and not opts.overwrite: + raise IOError('Output file "%s" exists, use --overwrite to ' + 'overwrite it' % outfilename) + + # load the PAR header and data + scaling = None if opts.scaling == 'off' else opts.scaling + infile = fname_ext_ul_case(infile) + pr_img = pr.load(infile, opts.permit_truncated, scaling) pr_hdr = pr_img.header - # get the raw unscaled data form the REC file raw_data = pr_img.dataobj.get_unscaled() - - # compute affine with desired origin affine = pr_hdr.get_affine(origin=opts.origin) - - # create an nifti image instance -- to get a matching header - nimg = nifti1.Nifti1Image(raw_data, affine) + nimg = nifti1.Nifti1Image(raw_data, affine, pr_hdr) nhdr = nimg.header if 'parse' in opts.minmax: # need to get the scaled data - verbose('Load (and scale) the data to determine value range') + verbose('Loading (and scaling) the data to determine value range') if opts.scaling == 'off': scaled_data = raw_data else: slope, intercept = pr_hdr.get_data_scaling(method=opts.scaling) - scaled_data = slope * raw_data - scaled_data += intercept + scaled_data = slope * raw_data + intercept if opts.minmax[0] == 'parse': nhdr.structarr['cal_min'] = scaled_data.min() else: @@ -113,87 +163,89 @@ def proc_file(infile, opts): nhdr.structarr['cal_max'] = float(opts.minmax[1]) # container for potential NIfTI1 header extensions - exts = nifti1.Nifti1Extensions() - if opts.store_header: + exts = nifti1.Nifti1Extensions() # dump the full PAR header content into an extension - fobj = open(infile, 'r') - hdr_dump = fobj.read() - dump_ext = nifti1.Nifti1Extension('comment', hdr_dump) - fobj.close() + with open(infile, 'r') as fobj: + hdr_dump = fobj.read() + dump_ext = nifti1.Nifti1Extension('comment', hdr_dump) exts.append(dump_ext) - - # put any extensions into the image - nimg.extra['extensions'] = exts - - # image description - descr = "%s;%s;%s;%s" % ( - pr_hdr.general_info['exam_name'], - pr_hdr.general_info['patient_name'], - pr_hdr.general_info['exam_date'].replace(' ',''), - pr_hdr.general_info['protocol_name']) - nhdr.structarr['descrip'] = descr[:80] - - if pr_hdr.general_info['max_dynamics'] > 1: - # fMRI - nhdr.structarr['pixdim'][4] = pr_hdr.general_info['repetition_time'] - # store units -- always mm and msec - nhdr.set_xyzt_units('mm', 'msec') - else: - # anatomical or DTI - nhdr.set_xyzt_units('mm', 'unknown') - - # get original scaling - if opts.scaling == 'off': - slope = 1.0 - intercept = 0.0 - else: - slope, intercept = pr_hdr.get_data_scaling(method=opts.scaling) - nhdr.set_slope_inter(slope, intercept) - # finalize the header: set proper data offset, pixdims, ... nimg.update_header() - # figure out the output filename - outfilename = splitext_addext(os.path.basename(infile))[0] - if not opts.outdir is None: - # set output path - outfilename = os.path.join(opts.outdir, outfilename) - # prep a file if opts.compressed: verbose('Using gzip compression') - outfilename += '.nii.gz' outfile = gzip.open(outfilename, 'wb') else: - outfilename += '.nii' outfile = open(outfilename, 'wb') + # get original scaling, and decide if we scale in-place or not + if opts.scaling == 'off': + slope = np.array([1.]) + intercept = np.array([0.]) + else: + verbose('Using data scaling "%s"' % opts.scaling) + slope, intercept = pr_hdr.get_data_scaling(method=opts.scaling) verbose('Writing %s' % outfilename) - # first write the header - nhdr.set_slope_inter(slope, intercept) + if not np.any(np.diff(slope)) and not np.any(np.diff(intercept)): + # Single scalefactor case + nhdr.set_slope_inter(slope.ravel()[0], intercept.ravel()[0]) + data_obj = raw_data + else: + # Multi scalefactor case + nhdr.set_slope_inter(1, 0) + nhdr.set_data_dtype(np.float64) + data_obj = pr_img.dataobj nhdr.write_to(outfile) - # Seek to data offset position offset = nhdr.get_data_offset() - seek_tell(outfile, offset, write0=True) - # now the data itself, but prevent any casting or scaling - nibabel.volumeutils.array_to_file( - raw_data, - outfile, - offset=offset) + array_to_file(data_obj, outfile, offset=offset) + + # write out bvals/bvecs if requested + if opts.bvs: + bvals, bvecs = pr_hdr.get_bvals_bvecs() + if (bvals, bvecs) == (None, None): + verbose('No DTI volumes detected, bvals and bvecs not written') + else: + verbose('Writing .bvals and .bvecs files') + with open(basefilename + '.bvals', 'w') as fid: + # np.savetxt could do this, but it's just a loop anyway + for val in bvals: + fid.write('%s ' % val) + fid.write('\n') + with open(basefilename + '.bvecs', 'w') as fid: + for row in bvecs.T: + for val in row: + fid.write('%s ' % val) + fid.write('\n') + + # write out dwell time if requested + if opts.dwell_time: + try: + dwell_time = calculate_dwell_time( + pr_hdr.get_water_fat_shift(), + pr_hdr.get_echo_train_length(), + opts.field_strength) + except MRIError: + verbose('No EPI factors, dwell time not written') + else: + verbose('Writing dwell time (%r sec) calculated assuming %sT ' + 'magnet' % (dwell_time, opts.field_strength)) + with open(basefilename + '.dwell_time', 'w') as fid: + fid.write('%r\n' % dwell_time) # done - outfile.close() def main(): parser = get_opt_parser() (opts, infiles) = parser.parse_args() - global verbose_switch - verbose_switch = opts.verbose + verbose.switch = opts.verbose - if not opts.origin in ['scanner', 'fov']: + if opts.origin not in ['scanner', 'fov']: error("Unrecognized value for --origin: '%s'." % opts.origin, 1) + if opts.dwell_time and opts.field_strength is None: + error('Need --field-strength for dwell time calculation', 1) # store any exceptions errs = [] @@ -206,7 +258,7 @@ def main(): if len(errs): error('Caught %i exceptions. Dump follows:\n\n %s' - % (len(errs), '\n'.join(errs)), 1) + % (len(errs), '\n'.join(errs)), 1) else: verbose('Done') diff --git a/nibabel-data/README.rst b/nibabel-data/README.rst new file mode 100644 index 0000000000..c8fa9f3a92 --- /dev/null +++ b/nibabel-data/README.rst @@ -0,0 +1,17 @@ +############ +Nibabel data +############ + +This subdirectory contains data repositories for testing. + +The data repositories should not be included in source or binary +distributions. + +A some point we might remove this directory from the source distribution and +make the data packages available with a more formal data package format. + +For the moment the tests can find this data path by: + +* Using the contents of the ``NIBABEL_DATA_DIR`` environment variable; +* Looking for this ``nibabel-data`` directory in the directory above (closer + to the root directory) the directory containing the ``nibabel`` package. diff --git a/nibabel-data/nitest-balls1 b/nibabel-data/nitest-balls1 new file mode 160000 index 0000000000..2cd07d86e2 --- /dev/null +++ b/nibabel-data/nitest-balls1 @@ -0,0 +1 @@ +Subproject commit 2cd07d86e2cc2d3c612d5d4d659daccd7a58f126 diff --git a/nibabel/__init__.py b/nibabel/__init__.py index 8178052de1..a87469c56b 100644 --- a/nibabel/__init__.py +++ b/nibabel/__init__.py @@ -63,6 +63,7 @@ apply_orientation, aff2axcodes) from .imageclasses import class_map, ext_map from . import trackvis +from . import mriutils # be friendly on systems with ancient numpy -- no tests, but at least # importable diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index ab0e99ee79..de8622d7f0 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -124,7 +124,7 @@ def __getitem__(self, slicer): self._shape, self._dtype, self._offset, - order = self.order) + order=self.order) # Upcast as necessary for big slopes, intercepts return apply_read_scaling(raw_data, self._slope, self._inter) diff --git a/nibabel/mriutils.py b/nibabel/mriutils.py new file mode 100644 index 0000000000..a56df30a0b --- /dev/null +++ b/nibabel/mriutils.py @@ -0,0 +1,51 @@ +# 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. +# +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +""" +Utilities for calculations related to MRI +""" +from __future__ import division + +__all__ = ['dwell_time'] + +GYROMAGNETIC_RATIO = 42.576 # MHz/T for hydrogen nucleus +PROTON_WATER_FAT_SHIFT = 3.4 # ppm + + +class MRIError(ValueError): + pass + + +def calculate_dwell_time(water_fat_shift, echo_train_length, field_strength): + """Calculate the dwell time + + Parameters + ---------- + water_fat_shift : float + The water fat shift of the recording, in pixels. + echo_train_length : int + The echo train length of the imaging sequence. + field_strength : float + Strength of the magnet in Tesla, e.g. 3.0 for a 3T magnet recording. + + Returns + ------- + dwell_time : float + The dwell time in seconds. + + Raises + ------ + MRIError if values are out of range + """ + if field_strength < 0: + raise MRIError("Field strength should be positive") + if echo_train_length <= 0: + raise MRIError("Echo train length should be >= 1") + return ((echo_train_length - 1) * water_fat_shift / + (GYROMAGNETIC_RATIO * PROTON_WATER_FAT_SHIFT + * field_strength * (echo_train_length + 1))) diff --git a/nibabel/parrec.py b/nibabel/parrec.py index 0a3a73f4aa..407e02e9b0 100644 --- a/nibabel/parrec.py +++ b/nibabel/parrec.py @@ -79,36 +79,37 @@ import warnings import numpy as np -import copy +from copy import deepcopy +import re -from .externals.six import binary_type +from .externals.six import string_types from .py3k import asbytes from .spatialimages import SpatialImage, Header from .eulerangles import euler2mat -from .volumeutils import Recoder, array_from_file, apply_read_scaling -from .arrayproxy import ArrayProxy -from .affines import from_matvec, dot_reduce +from .volumeutils import Recoder, array_from_file, BinOpener +from .affines import from_matvec, dot_reduce, apply_affine +from .nifti1 import unit_codes # PSL to RAS affine -PSL_TO_RAS = np.array([[0, 0, -1, 0], # L -> R - [-1, 0, 0, 0], # P -> A - [0, 1, 0, 0], # S -> S +PSL_TO_RAS = np.array([[0, 0, -1, 0], # L -> R + [-1, 0, 0, 0], # P -> A + [0, 1, 0, 0], # S -> S [0, 0, 0, 1]]) # Acquisition (tra/sag/cor) to PSL axes # These come from looking at transverse, sagittal, coronal datasets where we # can see the LR, PA, SI orientation of the slice axes from the scanned object ACQ_TO_PSL = dict( - transverse = np.array([[ 0, 1, 0, 0], # P - [ 0, 0, 1, 0], # S - [ 1, 0, 0, 0], # L - [ 0, 0, 0, 1]]), - sagittal = np.diag([1, -1, -1, 1]), - coronal = np.array([[ 0, 0, 1, 0], # P - [ 0, -1, 0, 0], # S - [ 1, 0, 0, 0], # L - [ 0, 0, 0, 1]]) + transverse=np.array([[0, 1, 0, 0], # P + [0, 0, 1, 0], # S + [1, 0, 0, 0], # L + [0, 0, 0, 1]]), + sagittal=np.diag([1, -1, -1, 1]), + coronal=np.array([[0, 0, 1, 0], # P + [0, -1, 0, 0], # S + [1, 0, 0, 0], # L + [0, 0, 0, 1]]) ) # PAR header versions we claim to understand supported_versions = ['V4.2'] @@ -134,7 +135,7 @@ 'Preparation direction': ('prep_direction',), 'Technique': ('tech',), 'Scan resolution (x, y)': ('scan_resolution', int, (2,)), - 'Scan mode': ('san_mode',), + 'Scan mode': ('scan_mode',), 'Repetition time [ms]': ('repetition_time', float), 'FOV (ap,fh,rl) [mm]': ('fov', float, (3,)), 'Water Fat shift [pixels]': ('water_fat_shift', float), @@ -189,20 +190,20 @@ ('image_flip_angle', float), ('cardiac frequency', int,), ('minimum RR-interval', int,), - ('maximum RR-interval', int,), + ('maximum RR-interval', int,), ('TURBO factor', int,), ('Inversion delay', float), - ('diffusion b value number', int,), # (imagekey!) - ('gradient orientation number', int,), # (imagekey!) - ('contrast type', 'S30'), # XXX might be too short? - ('diffusion anisotropy type', 'S30'), # XXX might be too short? + ('diffusion b value number', int,), # (imagekey!) + ('gradient orientation number', int,), # (imagekey!) + ('contrast type', 'S30'), # XXX might be too short? + ('diffusion anisotropy type', 'S30'), # XXX might be too short? ('diffusion', float, (3,)), ('label type', int,), # (imagekey!) ] image_def_dtype = np.dtype(image_def_dtd) # slice orientation codes -slice_orientation_codes = Recoder((# code, label +slice_orientation_codes = Recoder(( # code, label (1, 'transverse'), (2, 'sagittal'), (3, 'coronal')), fields=('code', 'label')) @@ -217,6 +218,189 @@ class PARRECError(Exception): pass +def _split_header(fobj): + """ Split header into `version`, `gen_dict`, `image_lines` """ + version = None + gen_dict = {} + image_lines = [] + # Small state-machine + state = 'top-header' + for line in fobj: + line = line.strip() + if line == '': + continue + if state == 'top-header': + if not line.startswith('#'): + state = 'general-info' + elif 'image export tool' in line: + version = line.split()[-1] + if state == 'general-info': + if not line.startswith('.'): + state = 'comment-block' + else: # Let match raise error for unexpected field format + key, value = GEN_RE.match(line).groups() + gen_dict[key] = value + if state == 'comment-block': + if not line.startswith('#'): + state = 'image-info' + if state == 'image-info': + if line.startswith('#'): + break + image_lines.append(line) + return version, gen_dict, image_lines + + +GEN_RE = re.compile(r".\s+(.*?)\s*:\s+(.*)") + + +def _process_gen_dict(gen_dict): + """ Process `gen_dict` key, values into `general_info` + """ + general_info = {} + for key, value in gen_dict.items(): + # get props for this hdr field + props = _hdr_key_dict[key] + # turn values into meaningful dtype + if len(props) == 2: + # only dtype spec and no shape + value = props[1](value) + elif len(props) == 3: + # array with dtype and shape + value = np.fromstring(value, props[1], sep=' ') + value.shape = props[2] + general_info[props[0]] = value + return general_info + + +def _process_image_lines(image_lines): + """ Process image information definition lines + """ + # postproc image def props + # create an array for all image defs + image_defs = np.zeros(len(image_lines), dtype=image_def_dtype) + # for every image definition + for i, line in enumerate(image_lines): + items = line.split() + item_counter = 0 + # for all image properties we know about + for props in image_def_dtd: + if len(props) == 2: + name, np_type = props + value = items[item_counter] + if not np.dtype(np_type).kind == 'S': + value = np_type(value) + item_counter += 1 + elif len(props) == 3: + name, np_type, shape = props + nelements = np.prod(shape) + value = items[item_counter:item_counter + nelements] + value = [np_type(v) for v in value] + item_counter += nelements + image_defs[name][i] = value + return image_defs + + +def vol_numbers(slice_nos): + """ Calculate volume numbers inferred from slice numbers `slice_nos` + + The volume number for each slice is the number of times this slice has + occurred previously in the `slice_nos` sequence + + Parameters + ---------- + slice_nos : sequence + Sequence of slice numbers, e.g. ``[1, 2, 3, 4, 1, 2, 3, 4]``. + + Returns + ------- + vol_nos : list + A list, the same length of `slice_nos` giving the volume number for + each corresponding slice number. + """ + counter = {} + vol_nos = [] + for s_no in slice_nos: + count = counter.setdefault(s_no, 0) + vol_nos.append(count) + counter[s_no] += 1 + return vol_nos + + +def vol_is_full(slice_nos, slice_max, slice_min=1): + """ Vector with True for slices in complete volume, False otherwise + + Parameters + ---------- + slice_nos : sequence + Sequence of slice numbers, e.g. ``[1, 2, 3, 4, 1, 2, 3, 4]``. + slice_max : int + Highest slice number for a full slice set. Slice set will be + ``range(slice_min, slice_max+1)``. + slice_min : int + Lowest slice number for full slice set. + + Returns + ------- + is_full : array + Bool vector with True for slices in full volumes, False for slices in + partial volumes. A full volume is a volume with all slices in the + ``slice set`` as defined above. + + Raises + ------ + ValueError if any `slice_nos` value is outside slice set. + """ + slice_set = set(range(slice_min, slice_max + 1)) + if not slice_set.issuperset(slice_nos): + raise ValueError( + 'Slice numbers outside inclusive range {0} to {1}'.format( + slice_min, slice_max)) + vol_nos = np.array(vol_numbers(slice_nos)) + slice_nos = np.asarray(slice_nos) + is_full = np.ones(slice_nos.shape, dtype=bool) + for vol_no in set(vol_nos): + ours = vol_nos == vol_no + if not set(slice_nos[ours]) == slice_set: + is_full[ours] = False + return is_full + + +def _truncation_checks(general_info, image_defs, permit_truncated): + """ Check for presence of truncation in PAR file parameters + + Raise error if truncation present and `permit_truncated` is False. + """ + def _err_or_warn(msg): + if not permit_truncated: + raise PARRECError(msg) + warnings.warn(msg) + + def _chk_trunc(idef_name, gdef_max_name): + id_values = image_defs[idef_name + ' number'] + n_have = len(set(id_values)) + n_expected = general_info[gdef_max_name] + if n_have != n_expected: + _err_or_warn( + "Header inconsistency: Found {0} {1} values, " + "but expected {2}".format(n_have, idef_name, n_expected)) + + _chk_trunc('slice', 'max_slices') + _chk_trunc('echo', 'max_echoes') + _chk_trunc('dynamic scan', 'max_dynamics') + _chk_trunc('diffusion b value', 'max_diffusion_values') + _chk_trunc('gradient orientation', 'max_gradient_orient') + + # Final check for partial volumes + if not np.all(vol_is_full(image_defs['slice number'], + general_info['max_slices'])): + _err_or_warn("Found one or more partial volume(s)") + + +def one_line(long_str): + """ Make maybe mutli-line `long_str` into one long line """ + return ' '.join(line.strip() for line in long_str.splitlines()) + + def parse_PAR_header(fobj): """Parse a PAR header and aggregate all information into useful containers. @@ -233,116 +417,113 @@ def parse_PAR_header(fobj): Structured array with fields giving all "Image information" in the header """ - # containers for relevant header lines - general_info = {} - image_info = [] - version = None - # single pass through the header - for line in fobj: - # no junk - line = line.strip() - if line.startswith('#'): - # try to get the header version - if line.count('image export tool'): - version = line.split()[-1] - if not version in supported_versions: - warnings.warn( - "PAR/REC version '%s' is currently not " - "supported -- making an attempt to read " - "nevertheless. Please email the NiBabel " - "mailing list, if you are interested in " - "adding support for this version." - % version) - else: - # just a comment - continue - elif line.startswith('.'): - # read 'general information' and store in a dict - first_colon = line[1:].find(':') + 1 - key = line[1:first_colon].strip() - value = line[first_colon + 1:].strip() - # get props for this hdr field - props = _hdr_key_dict[key] - # turn values into meaningful dtype - if len(props) == 2: - # only dtype spec and no shape - value = props[1](value) - elif len(props) == 3: - # array with dtype and shape - value = np.fromstring(value, props[1], sep=' ') - value.shape = props[2] - general_info[props[0]] = value - elif line: - # anything else is an image definition: store for later - # processing - image_info.append(line) + version, gen_dict, image_lines = _split_header(fobj) + if version not in supported_versions: + warnings.warn(one_line( + """ PAR/REC version '{0}' is currently not supported -- making an + attempt to read nevertheless. Please email the NiBabel mailing + list, if you are interested in adding support for this version. + """.format(version))) + general_info = _process_gen_dict(gen_dict) + image_defs = _process_image_lines(image_lines) + return general_info, image_defs - # postproc image def props - # create an array for all image defs - image_defs = np.zeros(len(image_info), dtype=image_def_dtype) - # for every image definition - for i, line in enumerate(image_info): - items = line.split() - item_counter = 0 - # for all image properties we know about - for props in image_def_dtd: - if np.issubdtype(image_defs[props[0]].dtype, binary_type): - # simple string - image_defs[props[0]][i] = asbytes(items[item_counter]) - item_counter += 1 - elif len(props) == 2: - # prop with numerical dtype - if props[1] == 'S30': - 1/0 - image_defs[props[0]][i] = props[1](items[item_counter]) - item_counter += 1 - elif len(props) == 3: - # array prop with dtype - nelements = np.prod(props[2]) - # get as many elements as necessary - itms = items[item_counter:item_counter + nelements] - # convert to array with dtype - value = np.fromstring(" ".join(itms), props[1], sep=' ') - # store - image_defs[props[0]][i] = value - item_counter += nelements +def _data_from_rec(rec_fileobj, in_shape, dtype, slice_indices, out_shape, + scaling=None): + """Get data from REC file - return general_info, image_defs + Parameters + ---------- + rec_fileobj : file-like + The file to process. + in_shape : tuple + The input shape inferred from the PAR file. + dtype : dtype + The datatype. + slice_indices : array of int + The indices used to re-index the resulting array properly. + out_shape : tuple + The output shape. + scaling : array | None + Scaling to use. + + Returns + ------- + data : array + The scaled and sorted array. + """ + rec_data = array_from_file(in_shape, dtype, rec_fileobj) + rec_data = rec_data[..., slice_indices] + rec_data = rec_data.reshape(out_shape, order='F') + if not scaling is None: + # Don't do in-place b/c this goes int16 -> float64 + rec_data = rec_data * scaling[0] + scaling[1] + return rec_data + + +class PARRECArrayProxy(object): + def __init__(self, file_like, header, scaling): + self.file_like = file_like + # Copies of values needed to read array + self._shape = header.get_data_shape() + self._dtype = header.get_data_dtype() + self._slice_indices = header.get_sorted_slice_indices() + self._slice_scaling = header.get_data_scaling(scaling) + self._rec_shape = header.get_rec_shape() + + @property + def shape(self): + return self._shape + + @property + def dtype(self): + return self._dtype + + @property + def is_proxy(self): + return True + + def get_unscaled(self): + with BinOpener(self.file_like) as fileobj: + return _data_from_rec(fileobj, self._rec_shape, self._dtype, + self._slice_indices, self._shape) + + def __array__(self): + with BinOpener(self.file_like) as fileobj: + return _data_from_rec(fileobj, self._rec_shape, self._dtype, + self._slice_indices, self._shape, + scaling=self._slice_scaling) class PARRECHeader(Header): """PAR/REC header""" - def __init__(self, info, image_defs, default_scaling='dv'): + def __init__(self, info, image_defs, permit_truncated=False): """ Parameters ---------- info : dict - "General information" from the PAR file (as returned by - `parse_PAR_header()`). + "General information" from the PAR file (as returned by + `parse_PAR_header()`). image_defs : array - Structured array with image definitions from the PAR file (as - returned by `parse_PAR_header()`). - default_scaling : {'dv', 'fp'} - Default scaling method to use for :meth:`get_slope_inter`` - see - :meth:`get_data_scaling` for detail + Structured array with image definitions from the PAR file (as + returned by `parse_PAR_header()`). + permit_truncated : bool, optional + If True, a warning is emitted instead of an error when a truncated + recording is detected. """ - self.general_info = info - self.image_defs = image_defs - self._slice_orientation = None - self.default_scaling = default_scaling + self.general_info = info.copy() + self.image_defs = image_defs.copy() + _truncation_checks(info, image_defs, permit_truncated) # charge with basic properties to be able to use base class # functionality # dtype - dtype = np.typeDict[ - 'int' - + str(self._get_unique_image_prop('image pixel size')[0])] + bitpix = self._get_unique_image_prop('image pixel size') Header.__init__(self, - data_dtype=dtype, - shape=self.get_data_shape_in_file(), - zooms=self._get_zooms() - ) + data_dtype=np.dtype('int' + str(bitpix)).type, + shape=self._calc_data_shape(), + zooms=self._calc_zooms()) @classmethod def from_header(klass, header=None): @@ -354,57 +535,130 @@ def from_header(klass, header=None): 'non-PARREC header.') @classmethod - def from_fileobj(klass, fileobj): + def from_fileobj(klass, fileobj, permit_truncated=False): info, image_defs = parse_PAR_header(fileobj) - return klass(info, image_defs) + return klass(info, image_defs, permit_truncated) def copy(self): - return PARRECHeader( - copy.deepcopy(self.general_info), - self.image_defs.copy()) + return PARRECHeader(deepcopy(self.general_info), + self.image_defs.copy()) + + def as_analyze_map(self): + """Convert PAR parameters to NIFTI1 format""" + # Entries in the dict correspond to the parameters found in + # the NIfTI1 header, specifically in nifti1.py `header_dtd` defs. + # Here we set the parameters we can to simplify PAR/REC + # to NIfTI conversion. + descr = ("%s;%s;%s;%s" + % (self.general_info['exam_name'], + self.general_info['patient_name'], + self.general_info['exam_date'].replace(' ', ''), + self.general_info['protocol_name']))[:80] # max len + is_fmri = (self.general_info['max_dynamics'] > 1) + t = 'msec' if is_fmri else 'unknown' + xyzt_units = unit_codes['mm'] + unit_codes[t] + return dict(descr=descr, xyzt_units=xyzt_units) # , pixdim=pixdim) + + def get_water_fat_shift(self): + """Water fat shift, in pixels""" + return self.general_info['water_fat_shift'] + + def get_echo_train_length(self): + """Echo train length of the recording""" + return self.general_info['epi_factor'] + + def get_q_vectors(self): + """Get Q vectors from the data + + Returns + ------- + q_vectors : None or array + Array of q vectors (bvals * bvecs), or None if not a diffusion + acquisition. + """ + bvals, bvecs = self.get_bvals_bvecs() + if bvals is None and bvecs is None: + return None + return bvecs * bvals[:, np.newaxis] + + def get_bvals_bvecs(self): + """Get bvals and bvecs from data + + Returns + ------- + b_vals : None or array + Array of b values, shape (n_directions,), or None if not a + diffusion acquisition. + b_vectors : None or array + Array of b vectors, shape (n_directions, 3), or None if not a + diffusion acquisition. + """ + if self.general_info['diffusion'] == 0: + return None, None + reorder = self.get_sorted_slice_indices() + n_slices, n_vols = self.get_data_shape()[-2:] + bvals = self.image_defs['diffusion_b_factor'][reorder].reshape( + (n_slices, n_vols), order='F') + # All bvals within volume should be the same + assert not np.any(np.diff(bvals, axis=0)) + bvals = bvals[0] + bvecs = self.image_defs['diffusion'][reorder].reshape( + (n_slices, n_vols, 3), order='F') + # All 3 values of bvecs should be same within volume + assert not np.any(np.diff(bvecs, axis=0)) + bvecs = bvecs[0] + # rotate bvecs to match stored image orientation + permute_to_psl = ACQ_TO_PSL[self.get_slice_orientation()] + bvecs = apply_affine(np.linalg.inv(permute_to_psl), bvecs) + return bvals, bvecs def _get_unique_image_prop(self, name): - """Scan image definitions and return unique value of a property. + """ Scan image definitions and return unique value of a property. - If the requested property is an array this method does _not_ behave - like `np.unique`. It will return the unique combination of all array - elements for any image definition, and _not_ the unique element values. + * Get array for named field of ``self.image_defs``; + * Check that all rows in the array are the same and raise error + otherwise; + * Return the row. Parameters ---------- name : str - Name of the property + Name of the property in ``self.image_defs`` Returns ------- - unique_value : array + unique_value : scalar or array Raises ------ - If there is more than a single unique value a `PARRECError` is raised. + PARRECError - if the rows of ``self.image_defs[name]`` do not all + compare equal """ - prop = self.image_defs[name] - if len(prop.shape) > 1: - uprops = [np.unique(prop[i]) for i in range(len(prop.shape))] - else: - uprops = [np.unique(prop)] - if not np.prod([len(uprop) for uprop in uprops]) == 1: - raise PARRECError('Varying %s in image sequence (%s). This is not ' - 'suppported.' % (name, uprops)) - else: - return np.array([uprop[0] for uprop in uprops]) + props = self.image_defs[name] + if np.any(np.diff(props, axis=0)): + raise PARRECError('Varying {0} in image sequence ({1}). This is ' + 'not suppported.'.format(name, props)) + return props[0] def get_voxel_size(self): """Returns the spatial extent of a voxel. Does not include the slice gap in the slice extent. + This function is deprecated and we will remove it in future versions of + nibabel. Please use ``get_zooms`` instead. If you need the slice + thickness not including the slice gap, use ``self.image_defs['slice + thickness']``. + Returns ------- vox_size: shape (3,) ndarray """ + warnings.warn('Please use "get_zooms" instead of "get_voxel_size"', + DeprecationWarning, + stacklevel=2) # slice orientation for the whole image series - slice_thickness = self._get_unique_image_prop('slice thickness')[0] + slice_thickness = self._get_unique_image_prop('slice thickness') voxsize_inplane = self._get_unique_image_prop('pixel spacing') voxsize = np.array((voxsize_inplane[0], voxsize_inplane[1], @@ -420,31 +674,32 @@ def set_data_offset(self, offset): if offset != 0: raise PARRECError("PAR header assumes offset 0") - def get_ndim(self): - """Return the number of dimensions of the image data.""" - if self.general_info['max_dynamics'] > 1 \ - or self.general_info['max_gradient_orient'] > 1: - return 4 - else: - return 3 - - def _get_zooms(self): + def _calc_zooms(self): """Compute image zooms from header data. Spatial axis are first three. + + Returns + ------- + zooms : array + Length 3 array for 3D image, length 4 array for 4D image. + + Notes + ----- + This routine called in ``__init__``, so may not be able to use + some attributes available in the fully initalized object. """ # slice orientation for the whole image series - slice_gap = self._get_unique_image_prop('slice gap')[0] + slice_gap = self._get_unique_image_prop('slice gap') # scaling per image axis - zooms = np.ones(self.get_ndim()) - # spatial axes correspond to voxelsize + inter slice gap - # voxel size (inplaneX, inplaneY, slices) - zooms[:3] = self.get_voxel_size() - zooms[2] += slice_gap - # time axis? - if len(zooms) > 3 and self.general_info['max_dynamics'] > 1: - # DTI also has 4D - # Convert time from milliseconds to seconds + n_dim = 4 if self._get_n_vols() > 1 else 3 + zooms = np.ones(n_dim) + # spatial sizes are inplane X mm, inplane Y mm + inter slice gap + zooms[:2] = self._get_unique_image_prop('pixel spacing') + slice_thickness = self._get_unique_image_prop('slice thickness') + zooms[2] = slice_thickness + slice_gap + # If 4D dynamic scan, convert time from milliseconds to seconds + if len(zooms) > 3 and self.general_info['dyn_scan']: zooms[3] = self.general_info['repetition_time'] / 1000. return zooms @@ -509,42 +764,42 @@ def get_affine(self, origin='scanner'): # Currently in PSL; apply PSL -> RAS return np.dot(PSL_TO_RAS, psl_aff) - def get_data_shape_in_file(self): - """Return the shape of the binary blob in the REC file. + def _get_n_slices(self): + """ Get number of slices for output data """ + return len(set(self.image_defs['slice number'])) + + def _get_n_vols(self): + """ Get number of volumes for output data """ + slice_nos = self.image_defs['slice number'] + vol_nos = vol_numbers(slice_nos) + is_full = vol_is_full(slice_nos, self.general_info['max_slices']) + return len(set(np.array(vol_nos)[is_full])) + + def _calc_data_shape(self): + """ Calculate the output shape of the image data + + Returns length 3 tuple for 3D image, length 4 tuple for 4D. Returns ------- n_inplaneX : int - number of voxels in X direction + number of voxels in X direction. n_inplaneY : int - number of voxels in Y direction + number of voxels in Y direction. n_slices : int - number of slices + number of slices. n_vols : int - number of dynamic scans / number of directions in diffusion - """ - # e.g. number of volumes - ndynamics = len(np.unique(self.image_defs['dynamic scan number'])) - # DTI volumes (b-values-1 x directions) - # there is some awkward exception to this rule for b-values > 2 - # XXX need to get test image... - ndtivolumes = (self.general_info['max_diffusion_values'] - 1) \ - * self.general_info['max_gradient_orient'] - nslices = len(np.unique(self.image_defs['slice number'])) - if not nslices == self.general_info['max_slices']: - raise PARRECError("Header inconsistency: Found %i slices, " - "but header claims to have %i." - % (nslices, self.general_info['max_slices'])) + number of volumes or absent for 3D image. + Notes + ----- + This routine called in ``__init__``, so may not be able to use + some attributes available in the fully initalized object. + """ inplane_shape = tuple(self._get_unique_image_prop('recon resolution')) - - # there should not be both: multiple dynamics and DTI - if ndynamics > 1: - return inplane_shape + (nslices, ndynamics) - elif ndtivolumes > 1: - return inplane_shape + (nslices, ndtivolumes) - else: - return tuple(inplane_shape) + (nslices,) + shape = inplane_shape + (self._get_n_slices(),) + n_vols = self._get_n_vols() + return shape + (n_vols,) if n_vols > 1 else shape def get_data_scaling(self, method="dv"): """Returns scaling slope and intercept. @@ -556,9 +811,9 @@ def get_data_scaling(self, method="dv"): Returns ------- - slope : float + slope : array scaling slope - intercept : float + intercept : array scaling intercept Notes @@ -574,33 +829,24 @@ def get_data_scaling(self, method="dv"): DV = PV * RS + RI FP = DV / (RS * SS) """ - # XXX: FP tends to become HUGE, DV seems to be more reasonable -> figure - # out which one means what - - # although the is a per-image scaling in the header, it looks like - # there is just one unique factor and intercept per whole image series - scale_slope = self._get_unique_image_prop('scale slope') - rescale_slope = self._get_unique_image_prop('rescale slope') - rescale_intercept = self._get_unique_image_prop('rescale intercept') - + # These will be 3D or 4D + scale_slope = self.image_defs['scale slope'] + rescale_slope = self.image_defs['rescale slope'] + rescale_intercept = self.image_defs['rescale intercept'] if method == 'dv': - slope = rescale_slope - intercept = rescale_intercept + slope, intercept = rescale_slope, rescale_intercept elif method == 'fp': - # actual slopes per definition above slope = 1.0 / scale_slope - # actual intercept per definition above intercept = rescale_intercept / (rescale_slope * scale_slope) else: raise ValueError("Unknown scling method '%s'." % method) - return (slope, intercept) - - def get_slope_inter(self): - """ Utility method to get default slope, intercept scaling - """ - return tuple( - np.asscalar(v) - for v in self.get_data_scaling(method=self.default_scaling)) + reorder = self.get_sorted_slice_indices() + slope = slope[reorder] + intercept = intercept[reorder] + shape = (1, 1) + self.get_data_shape()[2:] + slope = slope.reshape(shape, order='F') + intercept = intercept.reshape(shape, order='F') + return slope, intercept def get_slice_orientation(self): """Returns the slice orientation label. @@ -609,58 +855,30 @@ def get_slice_orientation(self): ------- orientation : {'transverse', 'sagittal', 'coronal'} """ - if self._slice_orientation is None: - self._slice_orientation = \ - slice_orientation_codes.label[ - self._get_unique_image_prop('slice orientation')[0]] - return self._slice_orientation - - def raw_data_from_fileobj(self, fileobj): - ''' Read unscaled data array from `fileobj` - - Array axes correspond to x,y,z,t. + lab = self._get_unique_image_prop('slice orientation') + return slice_orientation_codes.label[lab] - Parameters - ---------- - fileobj : file-like - Must be open, and implement ``read`` and ``seek`` methods + def get_rec_shape(self): + inplane_shape = tuple(self._get_unique_image_prop('recon resolution')) + return inplane_shape + (len(self.image_defs),) - Returns - ------- - arr : ndarray - unscaled 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) - - def data_from_fileobj(self, fileobj): - ''' Read scaled data array from `fileobj` - - Use this routine to get the scaled image data from an image file - `fileobj`, given a header `self`. "Scaled" means, with any header - scaling factors applied to the raw data in the file. Use - `raw_data_from_fileobj` to get the raw data. + def get_sorted_slice_indices(self): + """Indices to sort (and maybe discard) slices in REC file - Parameters - ---------- - fileobj : file-like - Must be open, and implement ``read`` and ``seek`` methods + Returns list for indexing into the last (third) dimension of the REC + data array, and (equivalently) the only dimension of + ``self.image_defs``. - Returns - ------- - arr : ndarray - scaled data array - ''' - # read unscaled data - data = self.raw_data_from_fileobj(fileobj) - # get scalings from header. Value of None means not present in header - slope, inter = self.get_slope_inter() - slope = 1.0 if slope is None else slope - inter = 0.0 if inter is None else inter - # Upcast as necessary for big slopes, intercepts - return apply_read_scaling(data, slope, inter) + If the recording is truncated, the returned indices take care of + discarding any indices that are not meant to be used. + """ + slice_nos = self.image_defs['slice number'] + is_full = vol_is_full(slice_nos, self.general_info['max_slices']) + keys = (slice_nos, vol_numbers(slice_nos), np.logical_not(is_full)) + # Figure out how many we need to remove from the end, and trim them + # Based on our sorting, they should always be last + n_used = np.prod(self.get_data_shape()[2:]) + return np.lexsort(keys)[:n_used] class PARRECImage(SpatialImage): @@ -668,19 +886,21 @@ class PARRECImage(SpatialImage): header_class = PARRECHeader files_types = (('image', '.rec'), ('header', '.par')) - ImageArrayProxy = ArrayProxy + ImageArrayProxy = PARRECArrayProxy @classmethod - def from_file_map(klass, file_map): + def from_file_map(klass, file_map, permit_truncated, scaling): + pt = permit_truncated with file_map['header'].get_prepare_fileobj('rt') as hdr_fobj: - hdr = PARRECHeader.from_fileobj(hdr_fobj) + hdr = klass.header_class.from_fileobj(hdr_fobj, + permit_truncated=pt) rec_fobj = file_map['image'].get_prepare_fileobj() - data = klass.ImageArrayProxy(rec_fobj, hdr) - return klass(data, - hdr.get_affine(), - header=hdr, - extra=None, + data = klass.ImageArrayProxy(rec_fobj, hdr, scaling) + return klass(data, hdr.get_affine(), header=hdr, extra=None, file_map=file_map) -load = PARRECImage.load +def load(filename, permit_truncated=False, scaling='dv'): + file_map = PARRECImage.filespec_to_file_map(filename) + return PARRECImage.from_file_map(file_map, permit_truncated, scaling) +load.__doc__ = PARRECImage.load.__doc__ diff --git a/nibabel/testing/__init__.py b/nibabel/testing/__init__.py index a2b966335e..b3f421535e 100644 --- a/nibabel/testing/__init__.py +++ b/nibabel/testing/__init__.py @@ -8,6 +8,7 @@ ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## ''' Utilities for testing ''' from os.path import dirname, abspath, join as pjoin +from warnings import catch_warnings import numpy as np @@ -49,3 +50,28 @@ def assert_allclose_safely(a, b, match_nans=True): if b.dtype.kind in 'ui': b = b.astype(float) assert_true(np.allclose(a, b)) + + +class catch_warn_reset(catch_warnings): + """ Version of ``catch_warnings`` class that resets warning registry + """ + def __init__(self, *args, **kwargs): + self.modules = kwargs.pop('modules', []) + self._warnreg_copies = {} + super(catch_warn_reset, self).__init__(*args, **kwargs) + + def __enter__(self): + for mod in self.modules: + if hasattr(mod, '__warningregistry__'): + mod_reg = mod.__warningregistry__ + self._warnreg_copies[mod] = mod_reg.copy() + mod_reg.clear() + return super(catch_warn_reset, self).__enter__() + + def __exit__(self, *exc_info): + super(catch_warn_reset, self).__exit__(*exc_info) + for mod in self.modules: + if hasattr(mod, '__warningregistry__'): + mod.__warningregistry__.clear() + if mod in self._warnreg_copies: + mod.__warningregistry__.update(self._warnreg_copies[mod]) diff --git a/nibabel/tests/data/DTI.PAR b/nibabel/tests/data/DTI.PAR new file mode 100644 index 0000000000..73e78a5072 --- /dev/null +++ b/nibabel/tests/data/DTI.PAR @@ -0,0 +1,182 @@ +# === DATA DESCRIPTION FILE ====================================================== +# +# CAUTION - Investigational device. +# Limited by Federal Law to investigational use. +# +# Dataset name: H:\Export\05aug14_test_samples_12_1 +# +# CLINICAL TRYOUT Research image export tool V4.2 +# +# === GENERAL INFORMATION ======================================================== +# +. Patient name : 05aug14test +. Examination name : test +. Protocol name : WIP DTI SENSE +. Examination date/time : 2014.08.05 / 11:27:34 +. Series Type : Image MRSERIES +. Acquisition nr : 12 +. Reconstruction nr : 1 +. Scan Duration [sec] : 10.5 +. Max. number of cardiac phases : 1 +. Max. number of echoes : 1 +. Max. number of slices/locations : 10 +. Max. number of dynamics : 1 +. Max. number of mixes : 1 +. Patient position : Head First Supine +. Preparation direction : Right-Left +. Technique : DwiSE +. Scan resolution (x, y) : 76 62 +. Scan mode : MS +. Repetition time [ms] : 1166.614 +. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 +. Water Fat shift [pixels] : 9.087 +. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 +. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 +. Flow compensation <0=no 1=yes> ? : 0 +. Presaturation <0=no 1=yes> ? : 0 +. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 +. MTC <0=no 1=yes> ? : 0 +. SPIR <0=no 1=yes> ? : 1 +. EPI factor <0,1=no EPI> : 27 +. Dynamic scan <0=no 1=yes> ? : 0 +. Diffusion <0=no 1=yes> ? : 1 +. Diffusion echo time [ms] : 0.0000 +. Max. number of diffusion values : 2 +. Max. number of gradient orients : 7 +. Number of label types <0=no ASL> : 0 +# +# === PIXEL VALUES ============================================================= +# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console +# RS = rescale slope, RI = rescale intercept, SS = scale slope +# DV = PV * RS + RI FP = DV / (RS * SS) +# +# === IMAGE INFORMATION DEFINITION ============================================= +# The rest of this file contains ONE line per image, this line contains the following information: +# +# slice number (integer) +# echo number (integer) +# dynamic scan number (integer) +# cardiac phase number (integer) +# image_type_mr (integer) +# scanning sequence (integer) +# index in REC file (in images) (integer) +# image pixel size (in bits) (integer) +# scan percentage (integer) +# recon resolution (x y) (2*integer) +# rescale intercept (float) +# rescale slope (float) +# scale slope (float) +# window center (integer) +# window width (integer) +# image angulation (ap,fh,rl in degrees ) (3*float) +# image offcentre (ap,fh,rl in mm ) (3*float) +# slice thickness (in mm ) (float) +# slice gap (in mm ) (float) +# image_display_orientation (integer) +# slice orientation ( TRA/SAG/COR ) (integer) +# fmri_status_indication (integer) +# image_type_ed_es (end diast/end syst) (integer) +# pixel spacing (x,y) (in mm) (2*float) +# echo_time (float) +# dyn_scan_begin_time (float) +# trigger_time (float) +# diffusion_b_factor (float) +# number of averages (integer) +# image_flip_angle (in degrees) (float) +# cardiac frequency (bpm) (integer) +# minimum RR-interval (in ms) (integer) +# maximum RR-interval (in ms) (integer) +# TURBO factor <0=no turbo> (integer) +# Inversion delay (in ms) (float) +# diffusion b value number (imagekey!) (integer) +# gradient orientation number (imagekey!) (integer) +# contrast type (string) +# diffusion anisotropy type (string) +# diffusion (ap, fh, rl) (3*float) +# label type (ASL) (imagekey!) (integer) +# +# === IMAGE INFORMATION ========================================================== +# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty + + 1 1 1 1 0 1 0 16 81 80 80 0.00000 22.15092 1.35565e-003 69 120 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 2 1 1 1 0 1 1 16 81 80 80 0.00000 22.15092 1.35565e-003 322 560 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 3 1 1 1 0 1 2 16 81 80 80 0.00000 22.15092 1.35565e-003 688 1195 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 4 1 1 1 0 1 3 16 81 80 80 0.00000 22.15092 1.35565e-003 1407 2447 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 5 1 1 1 0 1 4 16 81 80 80 0.00000 22.15092 1.35565e-003 653 1135 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 6 1 1 1 0 1 5 16 81 80 80 0.00000 22.15092 1.35565e-003 502 873 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 7 1 1 1 0 1 6 16 81 80 80 0.00000 22.15092 1.35565e-003 365 635 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 8 1 1 1 0 1 7 16 81 80 80 0.00000 22.15092 1.35565e-003 301 524 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 9 1 1 1 0 1 8 16 81 80 80 0.00000 22.15092 1.35565e-003 450 783 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 10 1 1 1 0 1 9 16 81 80 80 0.00000 22.15092 1.35565e-003 38 66 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 + 1 1 1 1 0 1 10 16 81 80 80 0.00000 22.15092 1.35565e-003 65 112 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 2 1 1 1 0 1 11 16 81 80 80 0.00000 22.15092 1.35565e-003 339 589 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 3 1 1 1 0 1 12 16 81 80 80 0.00000 22.15092 1.35565e-003 721 1253 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 4 1 1 1 0 1 13 16 81 80 80 0.00000 22.15092 1.35565e-003 1581 2748 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 5 1 1 1 0 1 14 16 81 80 80 0.00000 22.15092 1.35565e-003 640 1112 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 6 1 1 1 0 1 15 16 81 80 80 0.00000 22.15092 1.35565e-003 471 819 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 7 1 1 1 0 1 16 16 81 80 80 0.00000 22.15092 1.35565e-003 367 638 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 8 1 1 1 0 1 17 16 81 80 80 0.00000 22.15092 1.35565e-003 214 373 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 9 1 1 1 0 1 18 16 81 80 80 0.00000 22.15092 1.35565e-003 436 758 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 10 1 1 1 0 1 19 16 81 80 80 0.00000 22.15092 1.35565e-003 47 82 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 + 1 1 1 1 0 1 20 16 81 80 80 0.00000 22.15092 1.35565e-003 66 115 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 2 1 1 1 0 1 21 16 81 80 80 0.00000 22.15092 1.35565e-003 334 581 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 3 1 1 1 0 1 22 16 81 80 80 0.00000 22.15092 1.35565e-003 746 1297 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 4 1 1 1 0 1 23 16 81 80 80 0.00000 22.15092 1.35565e-003 1400 2433 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 5 1 1 1 0 1 24 16 81 80 80 0.00000 22.15092 1.35565e-003 631 1096 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 6 1 1 1 0 1 25 16 81 80 80 0.00000 22.15092 1.35565e-003 438 761 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 7 1 1 1 0 1 26 16 81 80 80 0.00000 22.15092 1.35565e-003 374 650 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 8 1 1 1 0 1 27 16 81 80 80 0.00000 22.15092 1.35565e-003 259 450 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 9 1 1 1 0 1 28 16 81 80 80 0.00000 22.15092 1.35565e-003 436 757 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 10 1 1 1 0 1 29 16 81 80 80 0.00000 22.15092 1.35565e-003 50 86 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 + 1 1 1 1 0 1 30 16 81 80 80 0.00000 22.15092 1.35565e-003 67 116 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 2 1 1 1 0 1 31 16 81 80 80 0.00000 22.15092 1.35565e-003 312 542 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 3 1 1 1 0 1 32 16 81 80 80 0.00000 22.15092 1.35565e-003 694 1206 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 4 1 1 1 0 1 33 16 81 80 80 0.00000 22.15092 1.35565e-003 1422 2471 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 5 1 1 1 0 1 34 16 81 80 80 0.00000 22.15092 1.35565e-003 626 1088 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 6 1 1 1 0 1 35 16 81 80 80 0.00000 22.15092 1.35565e-003 472 820 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 7 1 1 1 0 1 36 16 81 80 80 0.00000 22.15092 1.35565e-003 345 600 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 8 1 1 1 0 1 37 16 81 80 80 0.00000 22.15092 1.35565e-003 312 542 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 9 1 1 1 0 1 38 16 81 80 80 0.00000 22.15092 1.35565e-003 457 794 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 10 1 1 1 0 1 39 16 81 80 80 0.00000 22.15092 1.35565e-003 48 83 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 + 1 1 1 1 0 1 40 16 81 80 80 0.00000 22.15092 1.35565e-003 55 95 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 2 1 1 1 0 1 41 16 81 80 80 0.00000 22.15092 1.35565e-003 355 618 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 3 1 1 1 0 1 42 16 81 80 80 0.00000 22.15092 1.35565e-003 738 1284 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 4 1 1 1 0 1 43 16 81 80 80 0.00000 22.15092 1.35565e-003 1440 2504 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 5 1 1 1 0 1 44 16 81 80 80 0.00000 22.15092 1.35565e-003 676 1174 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 6 1 1 1 0 1 45 16 81 80 80 0.00000 22.15092 1.35565e-003 502 872 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 7 1 1 1 0 1 46 16 81 80 80 0.00000 22.15092 1.35565e-003 368 639 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 8 1 1 1 0 1 47 16 81 80 80 0.00000 22.15092 1.35565e-003 330 573 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 9 1 1 1 0 1 48 16 81 80 80 0.00000 22.15092 1.35565e-003 483 839 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 10 1 1 1 0 1 49 16 81 80 80 0.00000 22.15092 1.35565e-003 55 95 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 + 1 1 1 1 0 1 50 16 81 80 80 0.00000 22.15092 1.35565e-003 56 97 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 2 1 1 1 0 1 51 16 81 80 80 0.00000 22.15092 1.35565e-003 359 624 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 3 1 1 1 0 1 52 16 81 80 80 0.00000 22.15092 1.35565e-003 818 1422 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 4 1 1 1 0 1 53 16 81 80 80 0.00000 22.15092 1.35565e-003 1526 2652 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 5 1 1 1 0 1 54 16 81 80 80 0.00000 22.15092 1.35565e-003 645 1121 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 6 1 1 1 0 1 55 16 81 80 80 0.00000 22.15092 1.35565e-003 474 824 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 7 1 1 1 0 1 56 16 81 80 80 0.00000 22.15092 1.35565e-003 386 671 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 8 1 1 1 0 1 57 16 81 80 80 0.00000 22.15092 1.35565e-003 235 409 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 9 1 1 1 0 1 58 16 81 80 80 0.00000 22.15092 1.35565e-003 406 705 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 10 1 1 1 0 1 59 16 81 80 80 0.00000 22.15092 1.35565e-003 50 87 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 + 1 1 1 1 0 1 60 16 81 80 80 0.00000 22.15092 1.35565e-003 52 90 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 2 1 1 1 0 1 61 16 81 80 80 0.00000 22.15092 1.35565e-003 691 1201 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 3 1 1 1 0 1 62 16 81 80 80 0.00000 22.15092 1.35565e-003 1598 2777 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 4 1 1 1 0 1 63 16 81 80 80 0.00000 22.15092 1.35565e-003 4569 7943 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 5 1 1 1 0 1 64 16 81 80 80 0.00000 22.15092 1.35565e-003 1526 2653 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 6 1 1 1 0 1 65 16 81 80 80 0.00000 22.15092 1.35565e-003 1070 1860 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 7 1 1 1 0 1 66 16 81 80 80 0.00000 22.15092 1.35565e-003 836 1453 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 8 1 1 1 0 1 67 16 81 80 80 0.00000 22.15092 1.35565e-003 562 978 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 9 1 1 1 0 1 68 16 81 80 80 0.00000 22.15092 1.35565e-003 1073 1865 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 10 1 1 1 0 1 69 16 81 80 80 0.00000 22.15092 1.35565e-003 42 72 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 + 1 1 1 1 0 1 70 16 81 80 80 0.00000 22.15092 1.35565e-003 53 92 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 2 1 1 1 0 1 71 16 81 80 80 0.00000 22.15092 1.35565e-003 322 561 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 3 1 1 1 0 1 72 16 81 80 80 0.00000 22.15092 1.35565e-003 718 1248 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 4 1 1 1 0 1 73 16 81 80 80 0.00000 22.15092 1.35565e-003 1440 2503 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 5 1 1 1 0 1 74 16 81 80 80 0.00000 22.15092 1.35565e-003 636 1105 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 6 1 1 1 0 1 75 16 81 80 80 0.00000 22.15092 1.35565e-003 467 811 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 7 1 1 1 0 1 76 16 81 80 80 0.00000 22.15092 1.35565e-003 355 616 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 8 1 1 1 0 1 77 16 81 80 80 0.00000 22.15092 1.35565e-003 254 441 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 9 1 1 1 0 1 78 16 81 80 80 0.00000 22.15092 1.35565e-003 442 768 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + 10 1 1 1 0 1 79 16 81 80 80 0.00000 22.15092 1.35565e-003 33 57 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 + +# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/NA.PAR b/nibabel/tests/data/NA.PAR new file mode 100644 index 0000000000..77e3b9c218 --- /dev/null +++ b/nibabel/tests/data/NA.PAR @@ -0,0 +1,111 @@ +# === DATA DESCRIPTION FILE ====================================================== +# +# CAUTION - Investigational device. +# Limited by Federal Law to investigational use. +# +# Dataset name: H:\Export\05aug14_test_samples_4_1 +# +# CLINICAL TRYOUT Research image export tool V4.2 +# +# === GENERAL INFORMATION ======================================================== +# +. Patient name : 05aug14test +. Examination name : test +. Protocol name : Survey_32ch_HeadCoil +. Examination date/time : 2014.08.05 / 11:27:34 +. Series Type : Image MRSERIES +. Acquisition nr : 4 +. Reconstruction nr : 1 +. Scan Duration [sec] : 30.3 +. Max. number of cardiac phases : 1 +. Max. number of echoes : 1 +. Max. number of slices/locations : 9 +. Max. number of dynamics : 1 +. Max. number of mixes : 1 +. Patient position : Head First Supine +. Preparation direction : Anterior-Posterior +. Technique : T1TFE +. Scan resolution (x, y) : 256 128 +. Scan mode : MS +. Repetition time [ms] : 9.816 +. FOV (ap,fh,rl) [mm] : 250.000 250.000 50.000 +. Water Fat shift [pixels] : 3.497 +. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 0.000 +. Off Centre midslice(ap,fh,rl) [mm] : -20.000 20.000 0.000 +. Flow compensation <0=no 1=yes> ? : 0 +. Presaturation <0=no 1=yes> ? : 0 +. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 +. MTC <0=no 1=yes> ? : 0 +. SPIR <0=no 1=yes> ? : 0 +. EPI factor <0,1=no EPI> : 1 +. Dynamic scan <0=no 1=yes> ? : 0 +. Diffusion <0=no 1=yes> ? : 0 +. Diffusion echo time [ms] : 0.0000 +. Max. number of diffusion values : 1 +. Max. number of gradient orients : 1 +. Number of label types <0=no ASL> : 0 +# +# === PIXEL VALUES ============================================================= +# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console +# RS = rescale slope, RI = rescale intercept, SS = scale slope +# DV = PV * RS + RI FP = DV / (RS * SS) +# +# === IMAGE INFORMATION DEFINITION ============================================= +# The rest of this file contains ONE line per image, this line contains the following information: +# +# slice number (integer) +# echo number (integer) +# dynamic scan number (integer) +# cardiac phase number (integer) +# image_type_mr (integer) +# scanning sequence (integer) +# index in REC file (in images) (integer) +# image pixel size (in bits) (integer) +# scan percentage (integer) +# recon resolution (x y) (2*integer) +# rescale intercept (float) +# rescale slope (float) +# scale slope (float) +# window center (integer) +# window width (integer) +# image angulation (ap,fh,rl in degrees ) (3*float) +# image offcentre (ap,fh,rl in mm ) (3*float) +# slice thickness (in mm ) (float) +# slice gap (in mm ) (float) +# image_display_orientation (integer) +# slice orientation ( TRA/SAG/COR ) (integer) +# fmri_status_indication (integer) +# image_type_ed_es (end diast/end syst) (integer) +# pixel spacing (x,y) (in mm) (2*float) +# echo_time (float) +# dyn_scan_begin_time (float) +# trigger_time (float) +# diffusion_b_factor (float) +# number of averages (integer) +# image_flip_angle (in degrees) (float) +# cardiac frequency (bpm) (integer) +# minimum RR-interval (in ms) (integer) +# maximum RR-interval (in ms) (integer) +# TURBO factor <0=no turbo> (integer) +# Inversion delay (in ms) (float) +# diffusion b value number (imagekey!) (integer) +# gradient orientation number (imagekey!) (integer) +# contrast type (string) +# diffusion anisotropy type (string) +# diffusion (ap, fh, rl) (3*float) +# label type (ASL) (imagekey!) (integer) +# +# === IMAGE INFORMATION ========================================================== +# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty + + 1 1 1 1 0 2 0 16 50 256 256 0.00000 4.06325 1.28441e-002 1070 1860 0.00 0.00 0.00 -20.00 20.00 20.00 10.000 10.000 0 2 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + 2 1 1 1 0 2 1 16 50 256 256 0.00000 4.06325 1.28441e-002 777 1351 0.00 0.00 0.00 -20.00 20.00 0.00 10.000 10.000 0 2 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + 3 1 1 1 0 2 2 16 50 256 256 0.00000 4.06325 1.28441e-002 480 835 0.00 0.00 0.00 -20.00 20.00 -20.00 10.000 10.000 0 2 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + 4 1 1 1 0 2 3 16 50 256 256 0.00000 4.06325 1.28441e-002 1645 2859 0.00 0.00 0.00 -20.00 20.00 0.00 10.000 10.000 0 3 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + 5 1 1 1 0 2 4 16 50 256 256 0.00000 4.06325 1.28441e-002 902 1567 0.00 0.00 0.00 0.00 20.00 0.00 10.000 10.000 0 3 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + 6 1 1 1 0 2 5 16 50 256 256 0.00000 4.06325 1.28441e-002 109 190 0.00 0.00 0.00 20.00 20.00 0.00 10.000 10.000 0 3 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + 7 1 1 1 0 2 6 16 50 256 256 0.00000 4.06325 1.28441e-002 1241 2156 0.00 0.00 0.00 0.00 20.00 0.00 10.000 10.000 0 1 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + 8 1 1 1 0 2 7 16 50 256 256 0.00000 4.06325 1.28441e-002 941 1636 0.00 0.00 0.00 0.00 40.00 0.00 10.000 10.000 0 1 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + 9 1 1 1 0 2 8 16 50 256 256 0.00000 4.06325 1.28441e-002 150 260 0.00 0.00 0.00 0.00 60.00 0.00 10.000 10.000 0 1 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 + +# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T1.PAR b/nibabel/tests/data/T1.PAR new file mode 100644 index 0000000000..4abc1987e5 --- /dev/null +++ b/nibabel/tests/data/T1.PAR @@ -0,0 +1,112 @@ +# === DATA DESCRIPTION FILE ====================================================== +# +# CAUTION - Investigational device. +# Limited by Federal Law to investigational use. +# +# Dataset name: H:\Export\05aug14_test_samples_6_1 +# +# CLINICAL TRYOUT Research image export tool V4.2 +# +# === GENERAL INFORMATION ======================================================== +# +. Patient name : 05aug14test +. Examination name : test +. Protocol name : T1 SENSE +. Examination date/time : 2014.08.05 / 11:27:34 +. Series Type : Image MRSERIES +. Acquisition nr : 6 +. Reconstruction nr : 1 +. Scan Duration [sec] : 65 +. Max. number of cardiac phases : 1 +. Max. number of echoes : 1 +. Max. number of slices/locations : 10 +. Max. number of dynamics : 1 +. Max. number of mixes : 1 +. Patient position : Head First Supine +. Preparation direction : Right-Left +. Technique : T1TFE +. Scan resolution (x, y) : 76 62 +. Scan mode : 3D +. Repetition time [ms] : 4.364 +. FOV (ap,fh,rl) [mm] : 130.000 100.000 154.375 +. Water Fat shift [pixels] : 1.117 +. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 +. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 +. Flow compensation <0=no 1=yes> ? : 0 +. Presaturation <0=no 1=yes> ? : 0 +. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 +. MTC <0=no 1=yes> ? : 0 +. SPIR <0=no 1=yes> ? : 0 +. EPI factor <0,1=no EPI> : 1 +. Dynamic scan <0=no 1=yes> ? : 0 +. Diffusion <0=no 1=yes> ? : 0 +. Diffusion echo time [ms] : 0.0000 +. Max. number of diffusion values : 1 +. Max. number of gradient orients : 1 +. Number of label types <0=no ASL> : 0 +# +# === PIXEL VALUES ============================================================= +# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console +# RS = rescale slope, RI = rescale intercept, SS = scale slope +# DV = PV * RS + RI FP = DV / (RS * SS) +# +# === IMAGE INFORMATION DEFINITION ============================================= +# The rest of this file contains ONE line per image, this line contains the following information: +# +# slice number (integer) +# echo number (integer) +# dynamic scan number (integer) +# cardiac phase number (integer) +# image_type_mr (integer) +# scanning sequence (integer) +# index in REC file (in images) (integer) +# image pixel size (in bits) (integer) +# scan percentage (integer) +# recon resolution (x y) (2*integer) +# rescale intercept (float) +# rescale slope (float) +# scale slope (float) +# window center (integer) +# window width (integer) +# image angulation (ap,fh,rl in degrees ) (3*float) +# image offcentre (ap,fh,rl in mm ) (3*float) +# slice thickness (in mm ) (float) +# slice gap (in mm ) (float) +# image_display_orientation (integer) +# slice orientation ( TRA/SAG/COR ) (integer) +# fmri_status_indication (integer) +# image_type_ed_es (end diast/end syst) (integer) +# pixel spacing (x,y) (in mm) (2*float) +# echo_time (float) +# dyn_scan_begin_time (float) +# trigger_time (float) +# diffusion_b_factor (float) +# number of averages (integer) +# image_flip_angle (in degrees) (float) +# cardiac frequency (bpm) (integer) +# minimum RR-interval (in ms) (integer) +# maximum RR-interval (in ms) (integer) +# TURBO factor <0=no turbo> (integer) +# Inversion delay (in ms) (float) +# diffusion b value number (imagekey!) (integer) +# gradient orientation number (imagekey!) (integer) +# contrast type (string) +# diffusion anisotropy type (string) +# diffusion (ap, fh, rl) (3*float) +# label type (ASL) (imagekey!) (integer) +# +# === IMAGE INFORMATION ========================================================== +# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty + + 1 1 1 1 0 2 0 16 81 80 80 0.00000 1.26032 2.84925e-005 133 231 -1.98 0.55 0.02 -18.79 -22.82 -16.42 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 2 1 1 1 0 2 1 16 81 80 80 0.00000 1.26032 2.84925e-005 294 512 -1.98 0.55 0.02 -18.79 -12.82 -16.77 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 3 1 1 1 0 2 2 16 81 80 80 0.00000 1.26032 2.84925e-005 427 742 -1.98 0.55 0.02 -18.80 -2.83 -17.11 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 4 1 1 1 0 2 3 16 81 80 80 0.00000 1.26032 2.84925e-005 565 982 -1.98 0.55 0.02 -18.80 7.17 -17.46 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 5 1 1 1 0 2 4 16 81 80 80 0.00000 1.26032 2.84925e-005 474 825 -1.98 0.55 0.02 -18.80 17.16 -17.80 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 6 1 1 1 0 2 5 16 81 80 80 0.00000 1.26032 2.84925e-005 1070 1860 -1.98 0.55 0.02 -18.81 27.15 -18.15 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 7 1 1 1 0 2 6 16 81 80 80 0.00000 1.26032 2.84925e-005 1179 2049 -1.98 0.55 0.02 -18.81 37.15 -18.49 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 8 1 1 1 0 2 7 16 81 80 80 0.00000 1.26032 2.84925e-005 427 742 -1.98 0.55 0.02 -18.81 47.14 -18.84 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 9 1 1 1 0 2 8 16 81 80 80 0.00000 1.26032 2.84925e-005 175 304 -1.98 0.55 0.02 -18.82 57.14 -19.19 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + 10 1 1 1 0 2 9 16 81 80 80 0.00000 1.26032 2.84925e-005 114 199 -1.98 0.55 0.02 -18.82 67.13 -19.53 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 + +# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T2-interleaved.PAR b/nibabel/tests/data/T2-interleaved.PAR new file mode 100644 index 0000000000..da7d3c0032 --- /dev/null +++ b/nibabel/tests/data/T2-interleaved.PAR @@ -0,0 +1,112 @@ +# === DATA DESCRIPTION FILE ====================================================== +# +# CAUTION - Investigational device. +# Limited by Federal Law to investigational use. +# +# Dataset name: H:\Export\05aug14_test_samples_8_1 +# +# CLINICAL TRYOUT Research image export tool V4.2 +# +# === GENERAL INFORMATION ======================================================== +# +. Patient name : 05aug14test +. Examination name : test +. Protocol name : T2-interleaved SENSE +. Examination date/time : 2014.08.05 / 11:27:34 +. Series Type : Image MRSERIES +. Acquisition nr : 8 +. Reconstruction nr : 1 +. Scan Duration [sec] : 8 +. Max. number of cardiac phases : 1 +. Max. number of echoes : 1 +. Max. number of slices/locations : 10 +. Max. number of dynamics : 1 +. Max. number of mixes : 1 +. Patient position : Head First Supine +. Preparation direction : Right-Left +. Technique : TSE +. Scan resolution (x, y) : 76 56 +. Scan mode : MS +. Repetition time [ms] : 1000.000 +. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 +. Water Fat shift [pixels] : 2.479 +. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 +. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 +. Flow compensation <0=no 1=yes> ? : 0 +. Presaturation <0=no 1=yes> ? : 1 +. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 +. MTC <0=no 1=yes> ? : 0 +. SPIR <0=no 1=yes> ? : 0 +. EPI factor <0,1=no EPI> : 1 +. Dynamic scan <0=no 1=yes> ? : 0 +. Diffusion <0=no 1=yes> ? : 0 +. Diffusion echo time [ms] : 0.0000 +. Max. number of diffusion values : 1 +. Max. number of gradient orients : 1 +. Number of label types <0=no ASL> : 0 +# +# === PIXEL VALUES ============================================================= +# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console +# RS = rescale slope, RI = rescale intercept, SS = scale slope +# DV = PV * RS + RI FP = DV / (RS * SS) +# +# === IMAGE INFORMATION DEFINITION ============================================= +# The rest of this file contains ONE line per image, this line contains the following information: +# +# slice number (integer) +# echo number (integer) +# dynamic scan number (integer) +# cardiac phase number (integer) +# image_type_mr (integer) +# scanning sequence (integer) +# index in REC file (in images) (integer) +# image pixel size (in bits) (integer) +# scan percentage (integer) +# recon resolution (x y) (2*integer) +# rescale intercept (float) +# rescale slope (float) +# scale slope (float) +# window center (integer) +# window width (integer) +# image angulation (ap,fh,rl in degrees ) (3*float) +# image offcentre (ap,fh,rl in mm ) (3*float) +# slice thickness (in mm ) (float) +# slice gap (in mm ) (float) +# image_display_orientation (integer) +# slice orientation ( TRA/SAG/COR ) (integer) +# fmri_status_indication (integer) +# image_type_ed_es (end diast/end syst) (integer) +# pixel spacing (x,y) (in mm) (2*float) +# echo_time (float) +# dyn_scan_begin_time (float) +# trigger_time (float) +# diffusion_b_factor (float) +# number of averages (integer) +# image_flip_angle (in degrees) (float) +# cardiac frequency (bpm) (integer) +# minimum RR-interval (in ms) (integer) +# maximum RR-interval (in ms) (integer) +# TURBO factor <0=no turbo> (integer) +# Inversion delay (in ms) (float) +# diffusion b value number (imagekey!) (integer) +# gradient orientation number (imagekey!) (integer) +# contrast type (string) +# diffusion anisotropy type (string) +# diffusion (ap, fh, rl) (3*float) +# label type (ASL) (imagekey!) (integer) +# +# === IMAGE INFORMATION ========================================================== +# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty + + 1 1 1 1 0 1 0 16 81 80 80 0.00000 8.38730 7.95870e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 2 1 1 1 0 1 1 16 81 80 80 0.00000 8.38730 7.95870e-003 11500 19991 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 3 1 1 1 0 1 2 16 81 80 80 0.00000 8.38730 7.95870e-003 16200 28161 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 4 1 1 1 0 1 3 16 81 80 80 0.00000 8.38730 7.95870e-003 67043 116541 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 5 1 1 1 0 1 4 16 81 80 80 0.00000 8.38730 7.95870e-003 80065 139178 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 6 1 1 1 0 1 5 16 81 80 80 0.00000 8.38730 7.95870e-003 30352 52762 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 7 1 1 1 0 1 6 16 81 80 80 0.00000 8.38730 7.95870e-003 11471 19940 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 8 1 1 1 0 1 7 16 81 80 80 0.00000 8.38730 7.95870e-003 8085 14055 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 9 1 1 1 0 1 8 16 81 80 80 0.00000 8.38730 7.95870e-003 4902 8521 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 10 1 1 1 0 1 9 16 81 80 80 0.00000 8.38730 7.95870e-003 4201 7302 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + +# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T2.PAR b/nibabel/tests/data/T2.PAR new file mode 100644 index 0000000000..819b45a185 --- /dev/null +++ b/nibabel/tests/data/T2.PAR @@ -0,0 +1,112 @@ +# === DATA DESCRIPTION FILE ====================================================== +# +# CAUTION - Investigational device. +# Limited by Federal Law to investigational use. +# +# Dataset name: H:\Export\05aug14_test_samples_7_1 +# +# CLINICAL TRYOUT Research image export tool V4.2 +# +# === GENERAL INFORMATION ======================================================== +# +. Patient name : 05aug14test +. Examination name : test +. Protocol name : T2 SENSE +. Examination date/time : 2014.08.05 / 11:27:34 +. Series Type : Image MRSERIES +. Acquisition nr : 7 +. Reconstruction nr : 1 +. Scan Duration [sec] : 8 +. Max. number of cardiac phases : 1 +. Max. number of echoes : 1 +. Max. number of slices/locations : 10 +. Max. number of dynamics : 1 +. Max. number of mixes : 1 +. Patient position : Head First Supine +. Preparation direction : Right-Left +. Technique : TSE +. Scan resolution (x, y) : 76 56 +. Scan mode : MS +. Repetition time [ms] : 1000.000 +. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 +. Water Fat shift [pixels] : 2.479 +. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 +. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 +. Flow compensation <0=no 1=yes> ? : 0 +. Presaturation <0=no 1=yes> ? : 1 +. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 +. MTC <0=no 1=yes> ? : 0 +. SPIR <0=no 1=yes> ? : 0 +. EPI factor <0,1=no EPI> : 1 +. Dynamic scan <0=no 1=yes> ? : 0 +. Diffusion <0=no 1=yes> ? : 0 +. Diffusion echo time [ms] : 0.0000 +. Max. number of diffusion values : 1 +. Max. number of gradient orients : 1 +. Number of label types <0=no ASL> : 0 +# +# === PIXEL VALUES ============================================================= +# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console +# RS = rescale slope, RI = rescale intercept, SS = scale slope +# DV = PV * RS + RI FP = DV / (RS * SS) +# +# === IMAGE INFORMATION DEFINITION ============================================= +# The rest of this file contains ONE line per image, this line contains the following information: +# +# slice number (integer) +# echo number (integer) +# dynamic scan number (integer) +# cardiac phase number (integer) +# image_type_mr (integer) +# scanning sequence (integer) +# index in REC file (in images) (integer) +# image pixel size (in bits) (integer) +# scan percentage (integer) +# recon resolution (x y) (2*integer) +# rescale intercept (float) +# rescale slope (float) +# scale slope (float) +# window center (integer) +# window width (integer) +# image angulation (ap,fh,rl in degrees ) (3*float) +# image offcentre (ap,fh,rl in mm ) (3*float) +# slice thickness (in mm ) (float) +# slice gap (in mm ) (float) +# image_display_orientation (integer) +# slice orientation ( TRA/SAG/COR ) (integer) +# fmri_status_indication (integer) +# image_type_ed_es (end diast/end syst) (integer) +# pixel spacing (x,y) (in mm) (2*float) +# echo_time (float) +# dyn_scan_begin_time (float) +# trigger_time (float) +# diffusion_b_factor (float) +# number of averages (integer) +# image_flip_angle (in degrees) (float) +# cardiac frequency (bpm) (integer) +# minimum RR-interval (in ms) (integer) +# maximum RR-interval (in ms) (integer) +# TURBO factor <0=no turbo> (integer) +# Inversion delay (in ms) (float) +# diffusion b value number (imagekey!) (integer) +# gradient orientation number (imagekey!) (integer) +# contrast type (string) +# diffusion anisotropy type (string) +# diffusion (ap, fh, rl) (3*float) +# label type (ASL) (imagekey!) (integer) +# +# === IMAGE INFORMATION ========================================================== +# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty + + 1 1 1 1 0 1 0 16 81 80 80 0.00000 11.66129 5.47580e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 2 1 1 1 0 1 1 16 81 80 80 0.00000 11.66129 5.47580e-003 11765 20450 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 3 1 1 1 0 1 2 16 81 80 80 0.00000 11.66129 5.47580e-003 16140 28057 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 4 1 1 1 0 1 3 16 81 80 80 0.00000 11.66129 5.47580e-003 70823 123112 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 5 1 1 1 0 1 4 16 81 80 80 0.00000 11.66129 5.47580e-003 75089 130529 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 6 1 1 1 0 1 5 16 81 80 80 0.00000 11.66129 5.47580e-003 29296 50926 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 7 1 1 1 0 1 6 16 81 80 80 0.00000 11.66129 5.47580e-003 12039 20927 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 8 1 1 1 0 1 7 16 81 80 80 0.00000 11.66129 5.47580e-003 7482 13006 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 9 1 1 1 0 1 8 16 81 80 80 0.00000 11.66129 5.47580e-003 4821 8380 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + 10 1 1 1 0 1 9 16 81 80 80 0.00000 11.66129 5.47580e-003 4027 7000 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 + +# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T2_-interleaved.PAR b/nibabel/tests/data/T2_-interleaved.PAR new file mode 100644 index 0000000000..d73ab881d5 --- /dev/null +++ b/nibabel/tests/data/T2_-interleaved.PAR @@ -0,0 +1,122 @@ +# === DATA DESCRIPTION FILE ====================================================== +# +# CAUTION - Investigational device. +# Limited by Federal Law to investigational use. +# +# Dataset name: H:\Export\05aug14_test_samples_10_1 +# +# CLINICAL TRYOUT Research image export tool V4.2 +# +# === GENERAL INFORMATION ======================================================== +# +. Patient name : 05aug14test +. Examination name : test +. Protocol name : T2*-interleaved SENSE +. Examination date/time : 2014.08.05 / 11:27:34 +. Series Type : Image MRSERIES +. Acquisition nr : 10 +. Reconstruction nr : 1 +. Scan Duration [sec] : 12 +. Max. number of cardiac phases : 1 +. Max. number of echoes : 1 +. Max. number of slices/locations : 10 +. Max. number of dynamics : 2 +. Max. number of mixes : 1 +. Patient position : Head First Supine +. Preparation direction : Right-Left +. Technique : FEEPI +. Scan resolution (x, y) : 76 62 +. Scan mode : MS +. Repetition time [ms] : 2000.000 +. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 +. Water Fat shift [pixels] : 8.014 +. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 +. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 +. Flow compensation <0=no 1=yes> ? : 0 +. Presaturation <0=no 1=yes> ? : 0 +. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 +. MTC <0=no 1=yes> ? : 0 +. SPIR <0=no 1=yes> ? : 1 +. EPI factor <0,1=no EPI> : 27 +. Dynamic scan <0=no 1=yes> ? : 1 +. Diffusion <0=no 1=yes> ? : 0 +. Diffusion echo time [ms] : 0.0000 +. Max. number of diffusion values : 1 +. Max. number of gradient orients : 1 +. Number of label types <0=no ASL> : 0 +# +# === PIXEL VALUES ============================================================= +# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console +# RS = rescale slope, RI = rescale intercept, SS = scale slope +# DV = PV * RS + RI FP = DV / (RS * SS) +# +# === IMAGE INFORMATION DEFINITION ============================================= +# The rest of this file contains ONE line per image, this line contains the following information: +# +# slice number (integer) +# echo number (integer) +# dynamic scan number (integer) +# cardiac phase number (integer) +# image_type_mr (integer) +# scanning sequence (integer) +# index in REC file (in images) (integer) +# image pixel size (in bits) (integer) +# scan percentage (integer) +# recon resolution (x y) (2*integer) +# rescale intercept (float) +# rescale slope (float) +# scale slope (float) +# window center (integer) +# window width (integer) +# image angulation (ap,fh,rl in degrees ) (3*float) +# image offcentre (ap,fh,rl in mm ) (3*float) +# slice thickness (in mm ) (float) +# slice gap (in mm ) (float) +# image_display_orientation (integer) +# slice orientation ( TRA/SAG/COR ) (integer) +# fmri_status_indication (integer) +# image_type_ed_es (end diast/end syst) (integer) +# pixel spacing (x,y) (in mm) (2*float) +# echo_time (float) +# dyn_scan_begin_time (float) +# trigger_time (float) +# diffusion_b_factor (float) +# number of averages (integer) +# image_flip_angle (in degrees) (float) +# cardiac frequency (bpm) (integer) +# minimum RR-interval (in ms) (integer) +# maximum RR-interval (in ms) (integer) +# TURBO factor <0=no turbo> (integer) +# Inversion delay (in ms) (float) +# diffusion b value number (imagekey!) (integer) +# gradient orientation number (imagekey!) (integer) +# contrast type (string) +# diffusion anisotropy type (string) +# diffusion (ap, fh, rl) (3*float) +# label type (ASL) (imagekey!) (integer) +# +# === IMAGE INFORMATION ========================================================== +# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty + + 1 1 1 1 0 2 0 16 81 80 80 0.00000 239.84469 1.19452e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 2 1 1 1 0 2 1 16 81 80 80 0.00000 239.84469 1.19452e-003 1873 3256 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 3 1 1 1 0 2 2 16 81 80 80 0.00000 239.84469 1.19452e-003 3531 6138 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 4 1 1 1 0 2 3 16 81 80 80 0.00000 239.84469 1.19452e-003 8062 14015 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 5 1 1 1 0 2 4 16 81 80 80 0.00000 239.84469 1.19452e-003 4139 7194 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 6 1 1 1 0 2 5 16 81 80 80 0.00000 239.84469 1.19452e-003 6787 11798 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 7 1 1 1 0 2 6 16 81 80 80 0.00000 239.84469 1.19452e-003 1906 3314 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 8 1 1 1 0 2 7 16 81 80 80 0.00000 239.84469 1.19452e-003 1147 1993 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 9 1 1 1 0 2 8 16 81 80 80 0.00000 239.84469 1.19452e-003 1116 1940 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 10 1 1 1 0 2 9 16 81 80 80 0.00000 239.84469 1.19452e-003 933 1622 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 1 1 2 1 0 2 10 16 81 80 80 0.00000 239.84469 1.19452e-003 1089 1892 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 2 1 2 1 0 2 11 16 81 80 80 0.00000 239.84469 1.19452e-003 1826 3175 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 3 1 2 1 0 2 12 16 81 80 80 0.00000 239.84469 1.19452e-003 3655 6353 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 4 1 2 1 0 2 13 16 81 80 80 0.00000 239.84469 1.19452e-003 7595 13203 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 5 1 2 1 0 2 14 16 81 80 80 0.00000 239.84469 1.19452e-003 3657 6357 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 6 1 2 1 0 2 15 16 81 80 80 0.00000 239.84469 1.19452e-003 7312 12710 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 7 1 2 1 0 2 16 16 81 80 80 0.00000 239.84469 1.19452e-003 1663 2891 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 8 1 2 1 0 2 17 16 81 80 80 0.00000 239.84469 1.19452e-003 1278 2221 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 9 1 2 1 0 2 18 16 81 80 80 0.00000 239.84469 1.19452e-003 1041 1809 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 10 1 2 1 0 2 19 16 81 80 80 0.00000 239.84469 1.19452e-003 891 1549 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + +# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T2_.PAR b/nibabel/tests/data/T2_.PAR new file mode 100644 index 0000000000..d37ef17f53 --- /dev/null +++ b/nibabel/tests/data/T2_.PAR @@ -0,0 +1,122 @@ +# === DATA DESCRIPTION FILE ====================================================== +# +# CAUTION - Investigational device. +# Limited by Federal Law to investigational use. +# +# Dataset name: H:\Export\05aug14_test_samples_9_1 +# +# CLINICAL TRYOUT Research image export tool V4.2 +# +# === GENERAL INFORMATION ======================================================== +# +. Patient name : 05aug14test +. Examination name : test +. Protocol name : T2* SENSE +. Examination date/time : 2014.08.05 / 11:27:34 +. Series Type : Image MRSERIES +. Acquisition nr : 9 +. Reconstruction nr : 1 +. Scan Duration [sec] : 12 +. Max. number of cardiac phases : 1 +. Max. number of echoes : 1 +. Max. number of slices/locations : 10 +. Max. number of dynamics : 2 +. Max. number of mixes : 1 +. Patient position : Head First Supine +. Preparation direction : Right-Left +. Technique : FEEPI +. Scan resolution (x, y) : 76 62 +. Scan mode : MS +. Repetition time [ms] : 2000.000 +. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 +. Water Fat shift [pixels] : 8.014 +. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 +. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 +. Flow compensation <0=no 1=yes> ? : 0 +. Presaturation <0=no 1=yes> ? : 0 +. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 +. MTC <0=no 1=yes> ? : 0 +. SPIR <0=no 1=yes> ? : 1 +. EPI factor <0,1=no EPI> : 27 +. Dynamic scan <0=no 1=yes> ? : 1 +. Diffusion <0=no 1=yes> ? : 0 +. Diffusion echo time [ms] : 0.0000 +. Max. number of diffusion values : 1 +. Max. number of gradient orients : 1 +. Number of label types <0=no ASL> : 0 +# +# === PIXEL VALUES ============================================================= +# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console +# RS = rescale slope, RI = rescale intercept, SS = scale slope +# DV = PV * RS + RI FP = DV / (RS * SS) +# +# === IMAGE INFORMATION DEFINITION ============================================= +# The rest of this file contains ONE line per image, this line contains the following information: +# +# slice number (integer) +# echo number (integer) +# dynamic scan number (integer) +# cardiac phase number (integer) +# image_type_mr (integer) +# scanning sequence (integer) +# index in REC file (in images) (integer) +# image pixel size (in bits) (integer) +# scan percentage (integer) +# recon resolution (x y) (2*integer) +# rescale intercept (float) +# rescale slope (float) +# scale slope (float) +# window center (integer) +# window width (integer) +# image angulation (ap,fh,rl in degrees ) (3*float) +# image offcentre (ap,fh,rl in mm ) (3*float) +# slice thickness (in mm ) (float) +# slice gap (in mm ) (float) +# image_display_orientation (integer) +# slice orientation ( TRA/SAG/COR ) (integer) +# fmri_status_indication (integer) +# image_type_ed_es (end diast/end syst) (integer) +# pixel spacing (x,y) (in mm) (2*float) +# echo_time (float) +# dyn_scan_begin_time (float) +# trigger_time (float) +# diffusion_b_factor (float) +# number of averages (integer) +# image_flip_angle (in degrees) (float) +# cardiac frequency (bpm) (integer) +# minimum RR-interval (in ms) (integer) +# maximum RR-interval (in ms) (integer) +# TURBO factor <0=no turbo> (integer) +# Inversion delay (in ms) (float) +# diffusion b value number (imagekey!) (integer) +# gradient orientation number (imagekey!) (integer) +# contrast type (string) +# diffusion anisotropy type (string) +# diffusion (ap, fh, rl) (3*float) +# label type (ASL) (imagekey!) (integer) +# +# === IMAGE INFORMATION ========================================================== +# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty + + 1 1 1 1 0 2 0 16 81 80 80 0.00000 251.05495 1.18150e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 2 1 1 1 0 2 1 16 81 80 80 0.00000 251.05495 1.18150e-003 1697 2951 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 3 1 1 1 0 2 2 16 81 80 80 0.00000 251.05495 1.18150e-003 5912 10277 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 4 1 1 1 0 2 3 16 81 80 80 0.00000 251.05495 1.18150e-003 11675 20295 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 5 1 1 1 0 2 4 16 81 80 80 0.00000 251.05495 1.18150e-003 3596 6251 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 6 1 1 1 0 2 5 16 81 80 80 0.00000 251.05495 1.18150e-003 7385 12838 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 7 1 1 1 0 2 6 16 81 80 80 0.00000 251.05495 1.18150e-003 1846 3209 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 8 1 1 1 0 2 7 16 81 80 80 0.00000 251.05495 1.18150e-003 1121 1948 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 9 1 1 1 0 2 8 16 81 80 80 0.00000 251.05495 1.18150e-003 1001 1741 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 10 1 1 1 0 2 9 16 81 80 80 0.00000 251.05495 1.18150e-003 912 1586 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 1 1 2 1 0 2 10 16 81 80 80 0.00000 251.05495 1.18150e-003 1129 1963 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 2 1 2 1 0 2 11 16 81 80 80 0.00000 251.05495 1.18150e-003 1682 2924 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 3 1 2 1 0 2 12 16 81 80 80 0.00000 251.05495 1.18150e-003 6115 10629 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 4 1 2 1 0 2 13 16 81 80 80 0.00000 251.05495 1.18150e-003 10693 18587 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 5 1 2 1 0 2 14 16 81 80 80 0.00000 251.05495 1.18150e-003 3571 6208 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 6 1 2 1 0 2 15 16 81 80 80 0.00000 251.05495 1.18150e-003 6788 11799 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 7 1 2 1 0 2 16 16 81 80 80 0.00000 251.05495 1.18150e-003 1840 3198 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 8 1 2 1 0 2 17 16 81 80 80 0.00000 251.05495 1.18150e-003 1095 1903 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 9 1 2 1 0 2 18 16 81 80 80 0.00000 251.05495 1.18150e-003 1092 1898 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + 10 1 2 1 0 2 19 16 81 80 80 0.00000 251.05495 1.18150e-003 957 1664 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 + +# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/fieldmap.PAR b/nibabel/tests/data/fieldmap.PAR new file mode 100644 index 0000000000..099871a718 --- /dev/null +++ b/nibabel/tests/data/fieldmap.PAR @@ -0,0 +1,122 @@ +# === DATA DESCRIPTION FILE ====================================================== +# +# CAUTION - Investigational device. +# Limited by Federal Law to investigational use. +# +# Dataset name: H:\Export\05aug14_test_samples_11_1 +# +# CLINICAL TRYOUT Research image export tool V4.2 +# +# === GENERAL INFORMATION ======================================================== +# +. Patient name : 05aug14test +. Examination name : test +. Protocol name : WIP fieldmap SENSE +. Examination date/time : 2014.08.05 / 11:27:34 +. Series Type : Image MRSERIES +. Acquisition nr : 11 +. Reconstruction nr : 1 +. Scan Duration [sec] : 11.3 +. Max. number of cardiac phases : 1 +. Max. number of echoes : 1 +. Max. number of slices/locations : 10 +. Max. number of dynamics : 1 +. Max. number of mixes : 1 +. Patient position : Head First Supine +. Preparation direction : Right-Left +. Technique : FFE +. Scan resolution (x, y) : 76 62 +. Scan mode : MS +. Repetition time [ms] : 188.384 +. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 +. Water Fat shift [pixels] : 0.347 +. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 +. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 +. Flow compensation <0=no 1=yes> ? : 0 +. Presaturation <0=no 1=yes> ? : 0 +. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 +. MTC <0=no 1=yes> ? : 0 +. SPIR <0=no 1=yes> ? : 1 +. EPI factor <0,1=no EPI> : 1 +. Dynamic scan <0=no 1=yes> ? : 0 +. Diffusion <0=no 1=yes> ? : 0 +. Diffusion echo time [ms] : 0.0000 +. Max. number of diffusion values : 1 +. Max. number of gradient orients : 1 +. Number of label types <0=no ASL> : 0 +# +# === PIXEL VALUES ============================================================= +# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console +# RS = rescale slope, RI = rescale intercept, SS = scale slope +# DV = PV * RS + RI FP = DV / (RS * SS) +# +# === IMAGE INFORMATION DEFINITION ============================================= +# The rest of this file contains ONE line per image, this line contains the following information: +# +# slice number (integer) +# echo number (integer) +# dynamic scan number (integer) +# cardiac phase number (integer) +# image_type_mr (integer) +# scanning sequence (integer) +# index in REC file (in images) (integer) +# image pixel size (in bits) (integer) +# scan percentage (integer) +# recon resolution (x y) (2*integer) +# rescale intercept (float) +# rescale slope (float) +# scale slope (float) +# window center (integer) +# window width (integer) +# image angulation (ap,fh,rl in degrees ) (3*float) +# image offcentre (ap,fh,rl in mm ) (3*float) +# slice thickness (in mm ) (float) +# slice gap (in mm ) (float) +# image_display_orientation (integer) +# slice orientation ( TRA/SAG/COR ) (integer) +# fmri_status_indication (integer) +# image_type_ed_es (end diast/end syst) (integer) +# pixel spacing (x,y) (in mm) (2*float) +# echo_time (float) +# dyn_scan_begin_time (float) +# trigger_time (float) +# diffusion_b_factor (float) +# number of averages (integer) +# image_flip_angle (in degrees) (float) +# cardiac frequency (bpm) (integer) +# minimum RR-interval (in ms) (integer) +# maximum RR-interval (in ms) (integer) +# TURBO factor <0=no turbo> (integer) +# Inversion delay (in ms) (float) +# diffusion b value number (imagekey!) (integer) +# gradient orientation number (imagekey!) (integer) +# contrast type (string) +# diffusion anisotropy type (string) +# diffusion (ap, fh, rl) (3*float) +# label type (ASL) (imagekey!) (integer) +# +# === IMAGE INFORMATION ========================================================== +# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty + + 1 1 1 1 0 2 0 16 81 80 80 0.00000 10.40049 1.18623e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 2 1 1 1 0 2 1 16 81 80 80 0.00000 10.40049 1.18623e-003 3042 5288 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 3 1 1 1 0 2 2 16 81 80 80 0.00000 10.40049 1.18623e-003 2802 4871 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 4 1 1 1 0 2 3 16 81 80 80 0.00000 10.40049 1.18623e-003 3542 6157 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 5 1 1 1 0 2 4 16 81 80 80 0.00000 10.40049 1.18623e-003 3267 5679 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 6 1 1 1 0 2 5 16 81 80 80 0.00000 10.40049 1.18623e-003 1350 2346 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 7 1 1 1 0 2 6 16 81 80 80 0.00000 10.40049 1.18623e-003 536 931 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 8 1 1 1 0 2 7 16 81 80 80 0.00000 10.40049 1.18623e-003 516 897 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 9 1 1 1 0 2 8 16 81 80 80 0.00000 10.40049 1.18623e-003 464 807 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 10 1 1 1 0 2 9 16 81 80 80 0.00000 10.40049 1.18623e-003 265 461 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 1 1 1 1 3 4 10 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 2 1 1 1 3 4 11 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 3 1 1 1 3 4 12 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 4 1 1 1 3 4 13 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 5 1 1 1 3 4 14 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 6 1 1 1 3 4 15 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 7 1 1 1 3 4 16 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 8 1 1 1 3 4 17 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 9 1 1 1 3 4 18 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + 10 1 1 1 3 4 19 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 + +# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/nibabel_data.py b/nibabel/tests/nibabel_data.py new file mode 100644 index 0000000000..2c08b75199 --- /dev/null +++ b/nibabel/tests/nibabel_data.py @@ -0,0 +1,47 @@ +""" Functions / decorators for finding / requiring nibabel-data directory +""" + +from os import environ +from os.path import dirname, realpath, join as pjoin, isdir, exists + +from numpy.testing.decorators import skipif + + +def get_nibabel_data(): + """ Return path to nibabel-data or None if missing + + First use ``NIBABEL_DATA_DIR`` environment variable. + + If this variable is missing then look for data in directory below package + directory. + """ + nibabel_data = environ.get('NIBABEL_DATA_DIR') + if nibabel_data is None: + mod = __import__('nibabel') + containing_path = dirname(dirname(realpath(mod.__file__))) + nibabel_data = pjoin(containing_path, 'nibabel-data') + return nibabel_data if isdir(nibabel_data) else None + + +def needs_nibabel_data(subdir = None): + """ Decorator for tests needing nibabel-data + + Parameters + ---------- + subdir : None or str + Subdirectory we need in nibabel-data directory. If None, only require + nibabel-data directory itself. + + Returns + ------- + skip_dec : decorator + Decorator skipping tests if required directory not present + """ + nibabel_data = get_nibabel_data() + if nibabel_data is None: + return skipif(True, "Need nibabel-data directory for this test") + if subdir is None: + return skipif(False) + required_path = pjoin(nibabel_data, subdir) + return skipif(not exists(required_path), + "Need {0} for these tests".format(required_path)) diff --git a/nibabel/tests/test_mriutils.py b/nibabel/tests/test_mriutils.py new file mode 100644 index 0000000000..f65f6b2d26 --- /dev/null +++ b/nibabel/tests/test_mriutils.py @@ -0,0 +1,34 @@ +# 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. +# +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +""" Testing mriutils module +""" +from __future__ import division + +import numpy as np + +from numpy.testing import (assert_almost_equal, + assert_array_equal) + +from nose.tools import (assert_true, assert_false, assert_raises, + assert_equal, assert_not_equal) + + +from ..mriutils import calculate_dwell_time, MRIError + + +def test_calculate_dwell_time(): + # Test dwell time calculation + # This tests only that the calculation does what it appears to; needs some + # external check + assert_almost_equal(calculate_dwell_time(3.3, 2, 3), + 3.3 / (42.576 * 3.4 * 3 * 3)) + # Echo train length of 1 is valid, but returns 0 dwell time + assert_almost_equal(calculate_dwell_time(3.3, 1, 3), 0) + assert_raises(MRIError, calculate_dwell_time, 3.3, 0, 3.0) + assert_raises(MRIError, calculate_dwell_time, 3.3, 2, -0.1) diff --git a/nibabel/tests/test_nibabel_data.py b/nibabel/tests/test_nibabel_data.py new file mode 100644 index 0000000000..885f2439f5 --- /dev/null +++ b/nibabel/tests/test_nibabel_data.py @@ -0,0 +1,32 @@ +""" Tests for ``get_nibabel_data`` +""" + +import os +from os.path import dirname, realpath, join as pjoin, isdir + +from . import nibabel_data as nibd + +from nose.tools import assert_equal + +MY_DIR = dirname(__file__) + + +def setup_module(): + nibd.environ = {} + + +def teardown_module(): + nibd.environ = os.environ + + +def test_get_nibabel_data(): + # Test getting directory + local_data = realpath(pjoin(MY_DIR, '..', '..', 'nibabel-data')) + if isdir(local_data): + assert_equal(nibd.get_nibabel_data(), local_data) + else: + assert_equal(nibd.get_nibabel_data(), None) + nibd.environ['NIBABEL_DATA_DIR'] = 'not_a_path' + assert_equal(nibd.get_nibabel_data(), None) + nibd.environ['NIBABEL_DATA_DIR'] = MY_DIR + assert_equal(nibd.get_nibabel_data(), MY_DIR) diff --git a/nibabel/tests/test_parrec.py b/nibabel/tests/test_parrec.py index 2e56d1b986..1ec0374177 100644 --- a/nibabel/tests/test_parrec.py +++ b/nibabel/tests/test_parrec.py @@ -1,12 +1,16 @@ """ Testing parrec module """ -from os.path import join as pjoin, dirname +from os.path import join as pjoin, dirname, basename +from glob import glob +from warnings import simplefilter import numpy as np from numpy import array as npa -from ..parrec import parse_PAR_header, PARRECHeader, PARRECError +from .. import parrec +from ..parrec import (parse_PAR_header, PARRECHeader, PARRECError, vol_numbers, + vol_is_full) from ..openers import Opener from numpy.testing import (assert_almost_equal, @@ -15,6 +19,8 @@ from nose.tools import (assert_true, assert_false, assert_raises, assert_equal, assert_not_equal) +from ..testing import catch_warn_reset + DATA_PATH = pjoin(dirname(__file__), 'data') EG_PAR = pjoin(DATA_PATH, 'phantom_EPI_asc_CLEAR_2_1.PAR') @@ -89,6 +95,19 @@ [ 0. , 0. , 0. , 1. ]]), } +# Original values for b values in DTI.PAR, still in PSL orientation +DTI_PAR_BVECS = np.array([[-0.667, -0.667, -0.333], + [-0.333, 0.667, -0.667], + [-0.667, 0.333, 0.667], + [-0.707, -0.000, -0.707], + [-0.707, 0.707, 0.000], + [-0.000, 0.707, 0.707], + [ 0.000, 0.000, 0.000], + [ 0.000, 0.000, 0.000]]) + +# DTI.PAR values for bvecs +DTI_PAR_BVALS = [1000] * 6 + [0, 1000] + def test_header(): hdr = PARRECHeader(HDR_INFO, HDR_DEFS) @@ -96,37 +115,35 @@ def test_header(): assert_equal(hdr.get_data_dtype(), np.dtype(np.int16)) assert_equal(hdr.get_zooms(), (3.75, 3.75, 8.0, 2.0)) assert_equal(hdr.get_data_offset(), 0) - assert_almost_equal(hdr.get_slope_inter(), - (1.2903541326522827, 0.0), 5) + si = np.array([np.unique(x) for x in hdr.get_data_scaling()]).ravel() + assert_almost_equal(si, (1.2903541326522827, 0.0), 5) def test_header_scaling(): hdr = PARRECHeader(HDR_INFO, HDR_DEFS) - fp_scaling = np.squeeze(hdr.get_data_scaling('fp')) - dv_scaling = np.squeeze(hdr.get_data_scaling('dv')) + def_scaling = [np.unique(x) for x in hdr.get_data_scaling()] + fp_scaling = [np.unique(x) for x in hdr.get_data_scaling('fp')] + dv_scaling = [np.unique(x) for x in hdr.get_data_scaling('dv')] # Check default is dv scaling - assert_array_equal(np.squeeze(hdr.get_data_scaling()), dv_scaling) + assert_array_equal(def_scaling, dv_scaling) # And that it's almost the same as that from the converted nifti - assert_almost_equal(dv_scaling, (1.2903541326522827, 0.0), 5) + assert_almost_equal(dv_scaling, [[1.2903541326522827], [0.0]], 5) # Check that default for get_slope_inter is dv scaling - for hdr in (hdr, PARRECHeader(HDR_INFO, HDR_DEFS, default_scaling='dv')): - assert_array_equal(hdr.get_slope_inter(), dv_scaling) + for hdr in (hdr, PARRECHeader(HDR_INFO, HDR_DEFS)): + scaling = [np.unique(x) for x in hdr.get_data_scaling()] + assert_array_equal(scaling, dv_scaling) # Check we can change the default assert_false(np.all(fp_scaling == dv_scaling)) - fp_hdr = PARRECHeader(HDR_INFO, HDR_DEFS, default_scaling='fp') - assert_array_equal(fp_hdr.get_slope_inter(), fp_scaling) def test_orientation(): hdr = PARRECHeader(HDR_INFO, HDR_DEFS) assert_array_equal(HDR_DEFS['slice orientation'], 1) assert_equal(hdr.get_slice_orientation(), 'transverse') - hdr_defc = HDR_DEFS.copy() - hdr = PARRECHeader(HDR_INFO, hdr_defc) + hdr_defc = hdr.image_defs hdr_defc['slice orientation'] = 2 assert_equal(hdr.get_slice_orientation(), 'sagittal') hdr_defc['slice orientation'] = 3 - hdr = PARRECHeader(HDR_INFO, hdr_defc) assert_equal(hdr.get_slice_orientation(), 'coronal') @@ -146,7 +163,7 @@ def test_affine(): fov = hdr.get_affine(origin='fov') assert_array_equal(default, scanner) # rotation part is same - assert_array_equal(scanner[:3, :3], fov[:3,:3]) + assert_array_equal(scanner[:3, :3], fov[:3, :3]) # offset not assert_false(np.all(scanner[:3, 3] == fov[:3, 3])) # Regression test against what we were getting before @@ -164,3 +181,189 @@ def test_affine_regression(): with open(fname, 'rt') as fobj: hdr = PARRECHeader.from_fileobj(fobj) assert_almost_equal(hdr.get_affine(), exp_affine) + + +def test_get_voxel_size_deprecated(): + hdr = PARRECHeader(HDR_INFO, HDR_DEFS) + with catch_warn_reset(modules=[parrec], record=True) as wlist: + simplefilter('always') + hdr.get_voxel_size() + assert_equal(wlist[0].category, DeprecationWarning) + + +def test_get_sorted_slice_indices(): + # Test sorted slice indices + hdr = PARRECHeader(HDR_INFO, HDR_DEFS) + n_slices = len(HDR_DEFS) + assert_array_equal(hdr.get_sorted_slice_indices(), range(n_slices)) + # Reverse - volume order preserved + hdr = PARRECHeader(HDR_INFO, HDR_DEFS[::-1]) + assert_array_equal(hdr.get_sorted_slice_indices(), + [8, 7, 6, 5, 4, 3, 2, 1, 0, + 17, 16, 15, 14, 13, 12, 11, 10, 9, + 26, 25, 24, 23, 22, 21, 20, 19, 18]) + # Omit last slice, only two volumes + with catch_warn_reset(modules=[parrec], record=True): + hdr = PARRECHeader(HDR_INFO, HDR_DEFS[:-1], permit_truncated=True) + assert_array_equal(hdr.get_sorted_slice_indices(), range(n_slices - 9)) + + +def test_vol_number(): + # Test algorithm for calculating volume number + assert_array_equal(vol_numbers([1, 3, 0]), [0, 0, 0]) + assert_array_equal(vol_numbers([1, 3, 0, 0]), [ 0, 0, 0, 1]) + assert_array_equal(vol_numbers([1, 3, 0, 0, 0]), [0, 0, 0, 1, 2]) + assert_array_equal(vol_numbers([1, 3, 0, 0, 4]), [0, 0, 0, 1, 0]) + assert_array_equal(vol_numbers([1, 3, 0, 3, 1, 0]), + [0, 0, 0, 1, 1, 1]) + assert_array_equal(vol_numbers([1, 3, 0, 3, 1, 0, 4]), + [0, 0, 0, 1, 1, 1, 0]) + assert_array_equal(vol_numbers([1, 3, 0, 3, 1, 0, 3, 1, 0]), + [0, 0, 0, 1, 1, 1, 2, 2, 2]) + + +def test_vol_is_full(): + assert_array_equal(vol_is_full([3, 2, 1], 3), True) + assert_array_equal(vol_is_full([3, 2, 1], 4), False) + assert_array_equal(vol_is_full([4, 2, 1], 4), False) + assert_array_equal(vol_is_full([3, 2, 4, 1], 4), True) + assert_array_equal(vol_is_full([3, 2, 1], 3, 0), False) + assert_array_equal(vol_is_full([3, 2, 0, 1], 3, 0), True) + assert_raises(ValueError, vol_is_full, [2, 1, 0], 2) + assert_raises(ValueError, vol_is_full, [3, 2, 1], 3, 2) + assert_array_equal(vol_is_full([3, 2, 1, 2, 3, 1], 3), + [True] * 6) + assert_array_equal(vol_is_full([3, 2, 1, 2, 3], 3), + [True, True, True, False, False]) + + +def gen_par_fobj(): + for par in glob(pjoin(DATA_PATH, '*.PAR')): + with open(par, 'rt') as fobj: + yield par, fobj + + +def test_vol_calculations(): + # Test vol_is_full on sample data + for par, fobj in gen_par_fobj(): + gen_info, slice_info = parse_PAR_header(fobj) + slice_nos = slice_info['slice number'] + max_slice = gen_info['max_slices'] + assert_equal(set(slice_nos), set(range(1, max_slice + 1))) + assert_array_equal(vol_is_full(slice_nos, max_slice), True) + if par.endswith('NA.PAR'): + continue # Cannot parse this one + # Fourth dimension shows same number of volumes as vol_numbers + hdr = PARRECHeader(gen_info, slice_info) + shape = hdr.get_data_shape() + d4 = 1 if len(shape) == 3 else shape[3] + assert_equal(max(vol_numbers(slice_nos)), d4 - 1) + + +def test_diffusion_parameters(): + # Check getting diffusion parameters from diffusion example + dti_par = pjoin(DATA_PATH, 'DTI.PAR') + with open(dti_par, 'rt') as fobj: + dti_hdr = PARRECHeader.from_fileobj(fobj) + assert_equal(dti_hdr.get_data_shape(), (80, 80, 10, 8)) + assert_equal(dti_hdr.general_info['diffusion'], 1) + bvals, bvecs = dti_hdr.get_bvals_bvecs() + assert_almost_equal(bvals, DTI_PAR_BVALS) + # DTI_PAR_BVECS gives bvecs copied from first slice each vol in DTI.PAR + # Permute to match bvec directions to acquisition directions + assert_almost_equal(bvecs, DTI_PAR_BVECS[:, [2, 0, 1]]) + # Check q vectors + assert_almost_equal(dti_hdr.get_q_vectors(), bvals[:, None] * bvecs) + + +def test_null_diffusion_params(): + # Test non-diffusion PARs return None for diffusion params + for par, fobj in gen_par_fobj(): + if basename(par) in ('DTI.PAR', 'NA.PAR'): + continue + gen_info, slice_info = parse_PAR_header(fobj) + hdr = PARRECHeader(gen_info, slice_info) + assert_equal(hdr.get_bvals_bvecs(), (None, None)) + assert_equal(hdr.get_q_vectors(), None) + + +def test_epi_params(): + # Check EPI conversion + for par_root in ('T2_-interleaved', 'T2_', 'phantom_EPI_asc_CLEAR_2_1'): + epi_par = pjoin(DATA_PATH, par_root + '.PAR') + with open(epi_par, 'rt') as fobj: + epi_hdr = PARRECHeader.from_fileobj(fobj) + assert_equal(len(epi_hdr.get_data_shape()), 4) + assert_almost_equal(epi_hdr.get_zooms()[-1], 2.0) + + +def test_truncations(): + # Test tests for truncation + par = pjoin(DATA_PATH, 'T2_.PAR') + with open(par, 'rt') as fobj: + gen_info, slice_info = parse_PAR_header(fobj) + # Header is well-formed as is + hdr = PARRECHeader(gen_info, slice_info) + assert_equal(hdr.get_data_shape(), (80, 80, 10, 2)) + # Drop one line, raises error + assert_raises(PARRECError, PARRECHeader, gen_info, slice_info[:-1]) + # When we are permissive, we raise a warning, and drop a volume + with catch_warn_reset(modules=[parrec], record=True) as wlist: + hdr = PARRECHeader(gen_info, slice_info[:-1], permit_truncated=True) + assert_equal(len(wlist), 1) + assert_equal(hdr.get_data_shape(), (80, 80, 10)) + # Increase max slices to raise error + gen_info['max_slices'] = 11 + assert_raises(PARRECError, PARRECHeader, gen_info, slice_info) + gen_info['max_slices'] = 10 + hdr = PARRECHeader(gen_info, slice_info) + # Increase max_echoes + gen_info['max_echoes'] = 2 + assert_raises(PARRECError, PARRECHeader, gen_info, slice_info) + gen_info['max_echoes'] = 1 + hdr = PARRECHeader(gen_info, slice_info) + # dyamics + gen_info['max_dynamics'] = 3 + assert_raises(PARRECError, PARRECHeader, gen_info, slice_info) + gen_info['max_dynamics'] = 2 + hdr = PARRECHeader(gen_info, slice_info) + # number of b values + gen_info['max_diffusion_values'] = 2 + assert_raises(PARRECError, PARRECHeader, gen_info, slice_info) + gen_info['max_diffusion_values'] = 1 + hdr = PARRECHeader(gen_info, slice_info) + # number of unique gradients + gen_info['max_gradient_orient'] = 2 + assert_raises(PARRECError, PARRECHeader, gen_info, slice_info) + gen_info['max_gradient_orient'] = 1 + hdr = PARRECHeader(gen_info, slice_info) + + +def test__get_uniqe_image_defs(): + hdr = PARRECHeader(HDR_INFO, HDR_DEFS.copy()) + uip = hdr._get_unique_image_prop + assert_equal(uip('image pixel size'), 16) + # Make values not same - raise error + hdr.image_defs['image pixel size'][3] = 32 + assert_raises(PARRECError, uip, 'image pixel size') + assert_array_equal(uip('recon resolution'), [64, 64]) + hdr.image_defs['recon resolution'][4, 1] = 32 + assert_raises(PARRECError, uip, 'recon resolution') + assert_array_equal(uip('image angulation'), [-13.26, 0, 0]) + hdr.image_defs['image angulation'][5, 2] = 1 + assert_raises(PARRECError, uip, 'image angulation') + # This one differs from the outset + assert_raises(PARRECError, uip, 'slice number') + + +def test_copy_on_init(): + # Test that input dict / array gets copied when making header + hdr = PARRECHeader(HDR_INFO, HDR_DEFS) + assert_false(hdr.general_info is HDR_INFO) + hdr.general_info['max_slices'] = 10 + assert_equal(hdr.general_info['max_slices'], 10) + assert_equal(HDR_INFO['max_slices'], 9) + assert_false(hdr.image_defs is HDR_DEFS) + hdr.image_defs['image pixel size'] = 8 + assert_array_equal(hdr.image_defs['image pixel size'], 8) + assert_array_equal(HDR_DEFS['image pixel size'], 16) diff --git a/nibabel/tests/test_parrec_data.py b/nibabel/tests/test_parrec_data.py new file mode 100644 index 0000000000..1ac05cbc30 --- /dev/null +++ b/nibabel/tests/test_parrec_data.py @@ -0,0 +1,68 @@ +""" Test we can correctly import example PARREC files +""" +from __future__ import print_function, absolute_import + +from glob import glob +from os.path import join as pjoin, basename, splitext, exists + +import numpy as np + +import nibabel as nib +from ..parrec import load + +from .nibabel_data import get_nibabel_data, needs_nibabel_data + +from nose import SkipTest +from nose.tools import assert_true, assert_false, assert_equal + +from numpy.testing import assert_almost_equal + +NIBABEL_DATA = get_nibabel_data() + +if not NIBABEL_DATA is None: + BALLS = pjoin(NIBABEL_DATA, 'nitest-balls1') + + +# Amount by which affine translation differs from NIFTI conversion +AFF_OFF = [-0.93644031, -0.95572686, 0.03288748] + + +@needs_nibabel_data('nitest-balls1') +def test_loading(): + # Test loading of parrec files + for par in glob(pjoin(BALLS, 'PARREC', '*.PAR')): + par_root, ext = splitext(basename(par)) + # NA.PAR appears to be a localizer, with three slices in each of the + # three orientations: sagittal; coronal, transverse + if par_root == 'NA': + continue + # Check we can load the image + pimg = load(par) + assert_equal(pimg.shape[:3], (80, 80, 10)) + # Compare against NIFTI if present + nifti_fname = pjoin(BALLS, 'NIFTI', par_root + '.nii.gz') + if exists(nifti_fname): + nimg = nib.load(nifti_fname) + assert_almost_equal(nimg.affine[:3, :3], pimg.affine[:3, :3], 3) + # The translation part is always off by the same ammout + aff_off = pimg.affine[:3, 3] - nimg.affine[:3, 3] + assert_almost_equal(aff_off, AFF_OFF, 4) + # The difference is max in the order of 0.5 voxel + vox_sizes = np.sqrt((nimg.affine[:3, :3] ** 2).sum(axis=0)) + assert_true(np.all(np.abs(aff_off / vox_sizes) <= 0.5)) + # The data is very close, unless it's the fieldmap + if par_root != 'fieldmap': + assert_true(np.allclose(pimg.dataobj, nimg.dataobj)) + # Not sure what's going on with the fieldmap image - TBA + + +@needs_nibabel_data('nitest-balls1') +def test_fieldmap(): + # Test fieldmap image + # Exploring the DICOM suggests that the first volume is magnitude and the + # second is phase. The NIfTI has very odd scaling, being all negative. + fieldmap_par = pjoin(BALLS, 'PARREC', 'fieldmap.PAR') + fieldmap_nii = pjoin(BALLS, 'NIFTI', 'fieldmap.nii.gz') + pimg = load(fieldmap_par) + nimg = nib.load(fieldmap_nii) + raise SkipTest('Fieldmap remains puzzling') diff --git a/nibabel/tests/test_scripts.py b/nibabel/tests/test_scripts.py index 39f91ac3a2..7719fdf723 100644 --- a/nibabel/tests/test_scripts.py +++ b/nibabel/tests/test_scripts.py @@ -7,19 +7,25 @@ from __future__ import division, print_function, absolute_import import os -from os.path import dirname, join as pjoin, abspath +from os.path import (dirname, join as pjoin, abspath, splitext, basename, + exists) import re +from glob import glob import numpy as np from ..tmpdirs import InTemporaryDirectory from ..loadsave import load -from nose.tools import assert_true, assert_not_equal, assert_equal +from nose.tools import (assert_true, assert_false, assert_not_equal, + assert_equal) from numpy.testing import assert_almost_equal from .scriptrunner import ScriptRunner +from .nibabel_data import needs_nibabel_data +from .test_parrec import DTI_PAR_BVECS, DTI_PAR_BVALS +from .test_parrec_data import BALLS, AFF_OFF def _proc_stdout(stdout): @@ -96,3 +102,65 @@ def test_parrec2nii(): (data.min(), data.max(), data.mean()), (0.0, 2299.4110643863678, 194.95876256117265))) assert_almost_equal(vox_size(img.get_affine()), (3.75, 3.75, 8)) + + +@script_test +@needs_nibabel_data('nitest-balls1') +def test_parrec2nii_with_data(): + # Use nibabel-data to test conversion + with InTemporaryDirectory(): + for par in glob(pjoin(BALLS, 'PARREC', '*.PAR')): + par_root, ext = splitext(basename(par)) + # NA.PAR appears to be a localizer, with three slices in each of + # the three orientations: sagittal; coronal, transverse + if par_root == 'NA': + continue + # Do conversion + run_command(['parrec2nii', par]) + conved_img = load(par_root + '.nii') + assert_equal(conved_img.shape[:3], (80, 80, 10)) + # Test against converted NIfTI + nifti_fname = pjoin(BALLS, 'NIFTI', par_root + '.nii.gz') + if exists(nifti_fname): + nimg = load(nifti_fname) + assert_almost_equal(nimg.affine[:3, :3], + conved_img.affine[:3, :3], 3) + # The translation part is always off by the same ammout + aff_off = conved_img.affine[:3, 3] - nimg.affine[:3, 3] + assert_almost_equal(aff_off, AFF_OFF, 4) + # The difference is max in the order of 0.5 voxel + vox_sizes = np.sqrt((nimg.affine[:3, :3] ** 2).sum(axis=0)) + assert_true(np.all(np.abs(aff_off / vox_sizes) <= 0.5)) + # The data is very close, unless it's the fieldmap + if par_root != 'fieldmap': + assert_true(np.allclose(conved_img.dataobj, + nimg.dataobj)) + with InTemporaryDirectory(): + # Test some options + dti_par = pjoin(BALLS, 'PARREC', 'DTI.PAR') + run_command(['parrec2nii', dti_par]) + assert_true(exists('DTI.nii')) + assert_false(exists('DTI.bvals')) + assert_false(exists('DTI.bvecs')) + # Does not overwrite unless option given + code, stdout, stderr = run_command(['parrec2nii', dti_par], + check_code=False) + assert_equal(code, 1) + # Writes bvals, bvecs files if asked + run_command(['parrec2nii', '--overwrite', '--bvs', dti_par]) + assert_almost_equal(np.loadtxt('DTI.bvals'), DTI_PAR_BVALS) + assert_almost_equal(np.loadtxt('DTI.bvecs'), + DTI_PAR_BVECS[:, [2, 0, 1]].T) + assert_false(exists('DTI.dwell_time')) + # Need field strength if requesting dwell time + code, _, _, = run_command( + ['parrec2nii', '--overwrite', '--dwell-time', dti_par], + check_code=False) + assert_equal(code, 1) + run_command( + ['parrec2nii', '--overwrite', '--dwell-time', + '--field-strength', '3', dti_par]) + exp_dwell = (26 * 9.087) / (42.576 * 3.4 * 3 * 28) + with open('DTI.dwell_time', 'rt') as fobj: + contents = fobj.read().strip() + assert_almost_equal(float(contents), exp_dwell) diff --git a/nibabel/tests/test_testing.py b/nibabel/tests/test_testing.py new file mode 100644 index 0000000000..476f048ba6 --- /dev/null +++ b/nibabel/tests/test_testing.py @@ -0,0 +1,32 @@ +""" Test testing utilties +""" + +import sys + +from warnings import warn, simplefilter + +from ..testing import catch_warn_reset + +from nose.tools import assert_equal + + +def test_catch_warn_reset(): + # Initial state of module, no warnings + my_mod = sys.modules[__name__] + assert_equal(getattr(my_mod, '__warningregistry__', None), None) + with catch_warn_reset(modules=[my_mod]): + simplefilter('ignore') + warn('Some warning') + assert_equal(my_mod.__warningregistry__, {}) + with catch_warn_reset(): + simplefilter('ignore') + warn('Some warning') + assert_equal(len(my_mod.__warningregistry__), 1) + with catch_warn_reset(modules=[my_mod]): + simplefilter('ignore') + warn('Another warning') + assert_equal(len(my_mod.__warningregistry__), 1) + with catch_warn_reset(): + simplefilter('ignore') + warn('Another warning') + assert_equal(len(my_mod.__warningregistry__), 2) diff --git a/nibabel/tests/test_utils.py b/nibabel/tests/test_utils.py index 47a3c77e23..c387c1cd77 100644 --- a/nibabel/tests/test_utils.py +++ b/nibabel/tests/test_utils.py @@ -9,6 +9,8 @@ ''' Test for volumeutils module ''' from __future__ import division +from os.path import exists + from ..externals.six import BytesIO import tempfile import warnings @@ -23,6 +25,7 @@ array_to_file, allopen, # for backwards compatibility BinOpener, + fname_ext_ul_case, calculate_scale, can_cast, write_zeros, @@ -131,6 +134,11 @@ def test_array_to_file(): data_back = write_return(arr, str_io, ndt, 0, intercept, scale) assert_array_almost_equal(arr, data_back) + # Test array-like + str_io = BytesIO() + array_to_file(arr.tolist(), str_io, float) + data_back = array_from_file(arr.shape, float, str_io) + assert_array_almost_equal(arr, data_back) def test_a2f_intercept_scale(): @@ -820,6 +828,31 @@ def test_BinOpener(): assert_true(hasattr(fobj.fobj, 'compress')) +def test_fname_ext_ul_case(): + # Get filename ignoring the case of the filename extension + with InTemporaryDirectory(): + with open('afile.TXT', 'wt') as fobj: + fobj.write('Interesting information') + # OSX usually has case-insensitive file systems; Windows also + os_cares_case = not exists('afile.txt') + with open('bfile.txt', 'wt') as fobj: + fobj.write('More interesting information') + # If there is no file, the case doesn't change + assert_equal(fname_ext_ul_case('nofile.txt'), 'nofile.txt') + assert_equal(fname_ext_ul_case('nofile.TXT'), 'nofile.TXT') + # If there is a file, accept upper or lower case for ext + if os_cares_case: + assert_equal(fname_ext_ul_case('afile.txt'), 'afile.TXT') + assert_equal(fname_ext_ul_case('bfile.TXT'), 'bfile.txt') + else: + assert_equal(fname_ext_ul_case('afile.txt'), 'afile.txt') + assert_equal(fname_ext_ul_case('bfile.TXT'), 'bfile.TXT') + assert_equal(fname_ext_ul_case('afile.TXT'), 'afile.TXT') + assert_equal(fname_ext_ul_case('bfile.txt'), 'bfile.txt') + # Not mixed case though + assert_equal(fname_ext_ul_case('afile.TxT'), 'afile.TxT') + + def test_allopen(): # This import into volumeutils is for compatibility. The code is the # ``openers`` module. diff --git a/nibabel/volumeutils.py b/nibabel/volumeutils.py index 3b34ae245d..1cabc62597 100644 --- a/nibabel/volumeutils.py +++ b/nibabel/volumeutils.py @@ -15,6 +15,7 @@ import numpy as np +from os.path import exists, splitext from .casting import (shared_range, type_info, OK_FLOATS) from .openers import Opener @@ -524,8 +525,8 @@ def array_to_file(data, fileobj, out_dtype=None, offset=0, Parameters ---------- - data : array - array to write + data : array-like + array or array-like to write. fileobj : file-like file-like object implementing ``write`` method. out_dtype : None or dtype, optional @@ -589,7 +590,7 @@ def array_to_file(data, fileobj, out_dtype=None, offset=0, # Shield special case div_none = divslope is None if not np.all( - np.isfinite((intercept, 1.0 if div_none else divslope))): + np.isfinite((intercept, 1.0 if div_none else divslope))): raise ValueError('divslope and intercept must be finite') if divslope == 0: raise ValueError('divslope cannot be zero') @@ -599,15 +600,14 @@ def array_to_file(data, fileobj, out_dtype=None, offset=0, out_dtype = in_dtype else: out_dtype = np.dtype(out_dtype) - if not offset is None: + if offset is not None: seek_tell(fileobj, offset) if (div_none or - (mn, mx) == (0, 0) or - (None not in (mn, mx) and mx < mn) - ): + (mn, mx) == (0, 0) or + (None not in (mn, mx) and mx < mn)): write_zeros(fileobj, data.size * out_dtype.itemsize) return - if not order in 'FC': + if order not in 'FC': raise ValueError('Order should be one of F or C') # Simple cases pre_clips = None if (mn, mx) == (None, None) else (mn, mx) @@ -615,10 +615,10 @@ def array_to_file(data, fileobj, out_dtype=None, offset=0, if in_dtype.type == np.void: if not null_scaling: raise ValueError('Cannot scale non-numeric types') - if not pre_clips is None: + if pre_clips is not None: raise ValueError('Cannot clip non-numeric types') return _write_data(data, fileobj, out_dtype, order) - if not pre_clips is None: + if pre_clips is not None: pre_clips = _dt_min_max(in_dtype, *pre_clips) if null_scaling and np.can_cast(in_dtype, out_dtype): return _write_data(data, fileobj, out_dtype, order, @@ -640,8 +640,8 @@ def array_to_file(data, fileobj, out_dtype=None, offset=0, assert out_kind in 'iu' if in_kind in 'iu': if null_scaling: - # Must be large int to small int conversion; add clipping to pre scale - # thresholds + # Must be large int to small int conversion; add clipping to + # pre scale thresholds mn, mx = _dt_min_max(in_dtype, mn, mx) mn_out, mx_out = _dt_min_max(out_dtype) pre_clips = max(mn, mn_out), min(mx, mx_out) @@ -693,7 +693,7 @@ def array_to_file(data, fileobj, out_dtype=None, offset=0, specials = specials / slope assert specials.dtype.type == w_type post_mn, post_mx, nan_fill = np.rint(specials) - if post_mn > post_mx: # slope could be negative + if post_mn > post_mx: # slope could be negative post_mn, post_mx = post_mx, post_mn # Make sure that the thresholds exclude any value that will get badly cast # to the integer type. This is not the same as using the maximumum of the @@ -710,7 +710,7 @@ def array_to_file(data, fileobj, out_dtype=None, offset=0, # rounding est_err = np.round(2 * np.finfo(w_type).eps * abs(inter / slope)) if ((nan_fill < both_mn and abs(nan_fill - both_mn) < est_err) or - (nan_fill > both_mx and abs(nan_fill - both_mx) < est_err)): + (nan_fill > both_mx and abs(nan_fill - both_mx) < est_err)): # nan_fill can be (just) outside clip range nan_fill = np.clip(nan_fill, both_mn, both_mx) else: @@ -723,24 +723,24 @@ def array_to_file(data, fileobj, out_dtype=None, offset=0, post_mx = np.min([post_mx, both_mx]) in_cast = None if cast_in_dtype == in_dtype else cast_in_dtype return _write_data(data, fileobj, out_dtype, order, - in_cast = in_cast, - pre_clips = pre_clips, - inter = inter, - slope = slope, - post_clips = (post_mn, post_mx), - nan_fill = nan_fill if nan2zero else None) + in_cast=in_cast, + pre_clips=pre_clips, + inter=inter, + slope=slope, + post_clips=(post_mn, post_mx), + nan_fill=nan_fill if nan2zero else None) def _write_data(data, fileobj, out_dtype, order, - in_cast = None, - pre_clips = None, - inter = 0., - slope = 1., - post_clips = None, - nan_fill = None): + in_cast=None, + pre_clips=None, + inter=0., + slope=1., + post_clips=None, + nan_fill=None): """ Write array `data` to `fileobj` as `out_dtype` type, layout `order` Does not modify `data` in-place. @@ -877,19 +877,19 @@ def seek_tell(fileobj, offset, write0=False): assert fileobj.tell() == offset -def apply_read_scaling(arr, slope = None, inter = None): +def apply_read_scaling(arr, slope=None, inter=None): """ Apply scaling in `slope` and `inter` to array `arr` - This is for loading the array from a file (as opposed to the reverse scaling - when saving an array to file) + This is for loading the array from a file (as opposed to the reverse + scaling when saving an array to file) Return data will be ``arr * slope + inter``. The trick is that we have to find a good precision to use for applying the scaling. The heuristic is that the data is always upcast to the higher of the types from `arr, `slope`, `inter` if `slope` and / or `inter` are not default values. If the - dtype of `arr` is an integer, then we assume the data more or less fills the - integer range, and upcast to a type such that the min, max of ``arr.dtype`` - * scale + inter, will be finite. + dtype of `arr` is an integer, then we assume the data more or less fills + the integer range, and upcast to a type such that the min, max of + ``arr.dtype`` * scale + inter, will be finite. Parameters ---------- @@ -1515,6 +1515,38 @@ class BinOpener(Opener): compress_ext_map['.mgz'] = Opener.gz_def +def fname_ext_ul_case(fname): + """ `fname` with ext changed to upper / lower case if file exists + + Check for existence of `fname`. If it does exist, return unmodified. If + it doesn't, check for existence of `fname` with case changed from lower to + upper, or upper to lower. Return this modified `fname` if it exists. + Otherwise return `fname` unmodified + + Parameters + ---------- + fname : str + filename. + + Returns + ------- + mod_fname : str + filename, maybe with extension of opposite case + """ + if exists(fname): + return fname + froot, ext = splitext(fname) + if ext == ext.lower(): + mod_fname = froot + ext.upper() + if exists(mod_fname): + return mod_fname + elif ext == ext.upper(): + mod_fname = froot + ext.lower() + if exists(mod_fname): + return mod_fname + return fname + + def allopen(fileish, *args, **kwargs): """ Compatibility wrapper for old ``allopen`` function