def diagnose(args): """ Calculate, write results from diagnostic screen Parameters ---------- args : object object with attributes: * filename : str - 4D image filename * time_axis : str - name or number of time axis in `filename` * slice_axis : str - name or number of slice axis in `filename` * out_path : None or str - path to which to write results * out_fname_label : None or filename - suffix of output results files * ncomponents : int - number of PCA components to write images for Returns ------- res : dict Results of running :func:`screen` on `filename` """ img, time_axis, slice_axis = parse_fname_axes(args.filename, args.time_axis, args.slice_axis) res = screen(img, args.ncomponents, time_axis, slice_axis) froot, ext, addext = splitext_addext(args.filename) fpath, fbase = psplit(froot) fpath = fpath if args.out_path is None else args.out_path fbase = fbase if args.out_fname_label is None else args.out_fname_label write_screen_res(res, fpath, fbase, ext + addext) return res
def get_dtype(data): """ Determines neuroimaging format of `data` Parameters ---------- data : list-of-str or str or img_like Data to determine format of Returns ------- dtype : {'NIFTI', 'GIFTI', 'OTHER'} str Format of input data """ if isinstance(data, list): dtypes = np.unique([get_dtype(d) for d in data]) if dtypes.size > 1: raise ValueError('Provided data detected to have varying formats: ' '{}'.format(dtypes)) return dtypes[0] elif isinstance(data, str): dtype = splitext_addext(data)[1] else: # img_like? if not hasattr(data, 'valid_exts'): raise TypeError('Input data format cannot be detected.') dtype = data.valid_exts[0] return FORMATS.get(dtype, 'OTHER')
def get_dtype(data): """ Determines neuroimaging format of `data` Parameters ---------- data : :obj:`list` of :obj:`str` or :obj:`str` or img_like Data to determine format of Returns ------- dtype : {'NIFTI', 'OTHER'} str Format of input data """ if isinstance(data, list): dtypes = np.unique([get_dtype(d) for d in data]) if dtypes.size > 1: raise ValueError('Provided data detected to have varying formats: ' '{}'.format(dtypes)) return dtypes[0] elif isinstance(data, str): dtype = splitext_addext(data)[1] else: # img_like? if not hasattr(data, 'valid_exts'): raise TypeError('Input data format cannot be detected.') dtype = data.valid_exts[0] return FORMATS.get(dtype, 'OTHER')
def napari_get_reader(path): """A basic implementation of the napari_get_reader hook specification. Parameters ---------- path : str or list of str Path to file, or list of paths. Returns ------- function or None If the path is a recognized format, return a function that accepts the same path or list of paths, and returns a list of layer data tuples. """ if isinstance(path, list): # reader plugins may be handed single path, or a list of paths. # if it is a list, it is assumed to be an image stack... # so we are only going to look at the first file. path = path[0] froot, ext, addext = splitext_addext(path) # if we know we cannot read the file, we immediately return None. if not ext.lower() in all_valid_exts: return None # otherwise we return the *function* that can read ``path``. return reader_function
def filewrite(data, filename, ref_img, gzip=False, copy_header=True, copy_meta=False): """ Writes `data` to `filename` in format of `ref_img` If `ref_img` dtype is GIFTI, then `data` is assumed to be stacked L/R hemispheric and will be split and saved as two files Parameters ---------- data : (S [x T]) array_like Data to be saved filename : str Filepath where data should be saved to ref_img : str or img_like Reference image gzip : bool, optional Whether to gzip output (if not specified in `filename`). Only applies if output dtype is NIFTI. Default: False copy_header : bool, optional Whether to copy header from `ref_img` to new image. Default: True copy_meta : bool, optional Whether to copy meta from `ref_img` to new image. Only applies if output dtype is GIFTI. Default: False Returns ------- name : str Path of saved image (with added extensions, as appropriate) """ # get datatype and reference image for comparison dtype = get_dtype(ref_img) if isinstance(ref_img, list): ref_img = ref_img[0] # ensure that desired output type (from name) is compatible with `dtype` root, ext, add = splitext_addext(filename) if ext != '' and FORMATS[ext] != dtype: raise ValueError('Cannot write {} data to {} file. Please ensure file' 'formats are compatible'.format(dtype, FORMATS[ext])) if dtype == 'NIFTI': out = new_nii_like(ref_img, data, copy_header=copy_header) name = '{}.{}'.format(root, 'nii.gz' if gzip else 'nii') out.to_filename(name) elif dtype == 'GIFTI': # remove possible hemispheric denotations from root root = op.join(op.dirname(root), op.basename(root).split('.')[0]) # save hemispheres separately for n, (hdata, hemi) in enumerate(zip(np.split(data, 2, axis=0), ['L', 'R'])): out = new_gii_like(ref_img[n], hdata, copy_header=copy_header, copy_meta=copy_meta) name = '{}.{}.func.gii'.format(root, hemi) out.to_filename(name) return name
def filewrite(data, filename, ref_img, gzip=False, copy_header=True, copy_meta=False): """ Writes `data` to `filename` in format of `ref_img` If `ref_img` dtype is GIFTI, then `data` is assumed to be stacked L/R hemispheric and will be split and saved as two files Parameters ---------- data : (S [x T]) array_like Data to be saved filename : str Filepath where data should be saved to ref_img : str or img_like Reference image gzip : bool, optional Whether to gzip output (if not specified in `filename`). Only applies if output dtype is NIFTI. Default: False copy_header : bool, optional Whether to copy header from `ref_img` to new image. Default: True copy_meta : bool, optional Whether to copy meta from `ref_img` to new image. Only applies if output dtype is GIFTI. Default: False Returns ------- name : str Path of saved image (with added extensions, as appropriate) """ # get datatype and reference image for comparison dtype = get_dtype(ref_img) if isinstance(ref_img, list): ref_img = ref_img[0] # ensure that desired output type (from name) is compatible with `dtype` root, ext, add = splitext_addext(filename) if ext != '' and FORMATS[ext] != dtype: raise ValueError('Cannot write {} data to {} file. Please ensure file' 'formats are compatible'.format(dtype, FORMATS[ext])) if dtype == 'NIFTI': out = new_nii_like(ref_img, data, copy_header=copy_header) name = '{}.{}'.format(root, 'nii.gz' if gzip else 'nii') out.to_filename(name) elif dtype == 'GIFTI': # remove possible hemispheric denotations from root root = op.join(op.dirname(root), op.basename(root).split('.')[0]) # save hemispheres separately for n, (hdata, hemi) in enumerate(zip(np.split(data, 2, axis=0), ['L', 'R'])): out = new_gii_like(ref_img, hdata, copy_header=copy_header, copy_meta=copy_meta) name = '{}.{}.func.gii'.format(root, hemi) out.to_filename(name) return name
def guessed_image_type(filename): froot, ext, trailing = splitext_addext(filename, ('.gz', '.bz2')) lext = ext.lower() if lext == '.img' and os.path.isfile(os.path.join(froot,'.hdr')): return nibabel_guess(filename) else: return WashUImage
def tsdiffana(args): """ Generate tsdiffana plots from command line params `args` Parameters ---------- args : object object with attributes * filename : str - 4D image filename * out_file : str - graphics file to write to instead of leaving graphics on screen * time_axis : str - name or number of time axis in `filename` * slice_axis : str - name or number of slice axis in `filename` * write_results : bool - if True, write images and plots to files * out_path : None or str - path to which to write results * out_fname_label : None or filename - suffix of output results files Returns ------- axes : Matplotlib axes Axes on which we have done the plots. """ if args.out_file is not None and args.write_results: raise ValueError("Cannot have OUT_FILE and WRITE_RESULTS options " "together") img, time_axis, slice_axis = parse_fname_axes(args.filename, args.time_axis, args.slice_axis) results = time_slice_diffs_image(img, time_axis, slice_axis) axes = plot_tsdiffs(results) if args.out_file is None and not args.write_results: # interactive mode return axes if args.out_file is not None: # plot only mode axes[0].figure.savefig(args.out_file) return axes # plot and images mode froot, ext, addext = splitext_addext(args.filename) fpath, fbase = psplit(froot) fpath = fpath if args.out_path is None else args.out_path fbase = fbase if args.out_fname_label is None else args.out_fname_label axes[0].figure.savefig(pjoin(fpath, 'tsdiff_' + fbase + '.png')) # Save image volumes for key, prefix in (('slice_diff2_max_vol', 'dv2_max_'), ('diff2_mean_vol', 'dv2_mean_')): fname = pjoin(fpath, prefix + fbase + ext + addext) nipy.save_image(results[key], fname) # Save time courses into npz np.savez( pjoin(fpath, 'tsdiff_' + fbase + '.npz'), volume_means=results['volume_means'], slice_mean_diff2=results['slice_mean_diff2'], ) return axes
def tsdiffana(args): """ Generate tsdiffana plots from command line params `args` Parameters ---------- args : object object with attributes * filename : str - 4D image filename * out_file : str - graphics file to write to instead of leaving graphics on screen * time_axis : str - name or number of time axis in `filename` * slice_axis : str - name or number of slice axis in `filename` * write_results : bool - if True, write images and plots to files * out_path : None or str - path to which to write results * out_fname_label : None or filename - suffix of output results files Returns ------- axes : Matplotlib axes Axes on which we have done the plots. """ if args.out_file is not None and args.write_results: raise ValueError("Cannot have OUT_FILE and WRITE_RESULTS options " "together") img, time_axis, slice_axis = parse_fname_axes(args.filename, args.time_axis, args.slice_axis) results = time_slice_diffs_image(img, time_axis, slice_axis) axes = plot_tsdiffs(results) if args.out_file is None and not args.write_results: # interactive mode return axes if args.out_file is not None: # plot only mode axes[0].figure.savefig(args.out_file) return axes # plot and images mode froot, ext, addext = splitext_addext(args.filename) fpath, fbase = psplit(froot) fpath = fpath if args.out_path is None else args.out_path fbase = fbase if args.out_fname_label is None else args.out_fname_label axes[0].figure.savefig(pjoin(fpath, 'tsdiff_' + fbase + '.png')) # Save image volumes for key, prefix in (('slice_diff2_max_vol', 'dv2_max_'), ('diff2_mean_vol', 'dv2_mean_')): fname = pjoin(fpath, prefix + fbase + ext + addext) nipy.save_image(results[key], fname) # Save time courses into npz np.savez(pjoin(fpath, 'tsdiff_' + fbase + '.npz'), volume_means=results['volume_means'], slice_mean_diff2=results['slice_mean_diff2'], ) return axes
def _list_outputs(self): outputs = self._outputs().get() filename = splitext_addext(os.path.basename(self.inputs.in_files[0]))[0] out_dir = os.path.abspath('TED.{}'.format(filename)) outputs['t2star_map'] = os.path.join(out_dir, 't2sv.nii') outputs['s0_map'] = os.path.join(out_dir, 's0v.nii') outputs['t2star_adaptive_map'] = os.path.join(out_dir, 't2svG.nii') outputs['s0_adaptive_map'] = os.path.join(out_dir, 's0vG.nii') outputs['optimal_comb'] = os.path.join(out_dir, 'ts_OC.nii') return outputs
def _list_outputs(self): outputs = self._outputs().get() filename = splitext_addext(os.path.basename( self.inputs.in_files[0]))[0] out_dir = os.path.abspath('TED.{}'.format(filename)) outputs['t2star_map'] = os.path.join(out_dir, 't2sv.nii') outputs['s0_map'] = os.path.join(out_dir, 's0v.nii') outputs['t2star_adaptive_map'] = os.path.join(out_dir, 't2svG.nii') outputs['s0_adaptive_map'] = os.path.join(out_dir, 's0vG.nii') outputs['optimal_comb'] = os.path.join(out_dir, 'ts_OC.nii') return outputs
def filewrite(data, filename, ref_img, gzip=False, copy_header=True): """ Writes `data` to `filename` in format of `ref_img` Parameters ---------- data : (S [x T]) array_like Data to be saved filename : :obj:`str` Filepath where data should be saved to ref_img : :obj:`str` or img_like Reference image gzip : :obj:`bool`, optional Whether to gzip output (if not specified in `filename`). Only applies if output dtype is NIFTI. Default: False copy_header : :obj:`bool`, optional Whether to copy header from `ref_img` to new image. Default: True Returns ------- name : :obj:`str` Path of saved image (with added extensions, as appropriate) """ # get datatype and reference image for comparison dtype = get_dtype(ref_img) if isinstance(ref_img, list): ref_img = ref_img[0] # ensure that desired output type (from name) is compatible with `dtype` root, ext, add = splitext_addext(filename) if ext != '' and FORMATS[ext] != dtype: raise ValueError('Cannot write {} data to {} file. Please ensure file' 'formats are compatible'.format(dtype, FORMATS[ext])) if dtype == 'NIFTI': out = new_nii_like(ref_img, data, copy_header=copy_header) name = '{}.{}'.format(root, 'nii.gz' if gzip else 'nii') out.to_filename(name) return name
def filewrite(data, filename, ref_img, gzip=True, copy_header=True): """ Writes `data` to `filename` in format of `ref_img` Parameters ---------- data : (S [x T]) array_like Data to be saved filename : :obj:`str` Filepath where data should be saved to ref_img : :obj:`str` or img_like Reference image gzip : :obj:`bool`, optional Whether to gzip output (if not specified in `filename`). Only applies if output dtype is NIFTI. Default: True copy_header : :obj:`bool`, optional Whether to copy header from `ref_img` to new image. Default: True Returns ------- name : :obj:`str` Path of saved image (with added extensions, as appropriate) """ # get reference image for comparison if isinstance(ref_img, list): ref_img = ref_img[0] # generate out file for saving out = new_nii_like(ref_img, data, copy_header=copy_header) # FIXME: we only handle writing to nifti right now # get root of desired output file and save as nifti image root = op.dirname(filename) base = op.basename(filename) base, ext, add = splitext_addext(base) root = op.join(root, base) name = '{}.{}'.format(root, 'nii.gz' if gzip else 'nii') out.to_filename(name) return name
def proc_file(infile, opts): # 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 = 'dv' if opts.scaling == 'off' else opts.scaling infile = fname_ext_ul_case(infile) pr_img = pr.load(infile, permit_truncated=opts.permit_truncated, scaling=scaling, strict_sort=opts.strict_sort) pr_hdr = pr_img.header affine = pr_hdr.get_affine(origin=opts.origin) slope, intercept = pr_hdr.get_data_scaling(scaling) if opts.scaling != 'off': verbose('Using data scaling "%s"' % opts.scaling) # get original scaling, and decide if we scale in-place or not if opts.scaling == 'off': slope = np.array([1.]) intercept = np.array([0.]) in_data = pr_img.dataobj.get_unscaled() out_dtype = pr_hdr.get_data_dtype() elif not np.any(np.diff(slope)) and not np.any(np.diff(intercept)): # Single scalefactor case slope = slope.ravel()[0] intercept = intercept.ravel()[0] in_data = pr_img.dataobj.get_unscaled() out_dtype = pr_hdr.get_data_dtype() else: # Multi scalefactor case slope = np.array([1.]) intercept = np.array([0.]) in_data = np.array(pr_img.dataobj) out_dtype = np.float64 # Reorient data block to LAS+ if necessary ornt = io_orientation(np.diag([-1, 1, 1, 1]).dot(affine)) if np.all(ornt == [[0, 1], [1, 1], [2, 1]]): # already in LAS+ t_aff = np.eye(4) else: # Not in LAS+ t_aff = inv_ornt_aff(ornt, pr_img.shape) affine = np.dot(affine, t_aff) in_data = apply_orientation(in_data, ornt) bvals, bvecs = pr_hdr.get_bvals_bvecs() if not opts.keep_trace: # discard Philips DTI trace if present if bvecs is not None: bad_mask = np.logical_and(bvals != 0, (bvecs == 0).all(axis=1)) if bad_mask.sum() > 0: pl = 's' if bad_mask.sum() != 1 else '' verbose('Removing %s DTI trace volume%s' % (bad_mask.sum(), pl)) good_mask = ~bad_mask in_data = in_data[..., good_mask] bvals = bvals[good_mask] bvecs = bvecs[good_mask] # Make corresponding NIfTI image nimg = nifti1.Nifti1Image(in_data, affine, pr_hdr) nhdr = nimg.header nhdr.set_data_dtype(out_dtype) nhdr.set_slope_inter(slope, intercept) nhdr.set_sform(affine, code=1) nhdr.set_qform(affine, code=1) if 'parse' in opts.minmax: # need to get the scaled data verbose('Loading (and scaling) the data to determine value range') if opts.minmax[0] == 'parse': nhdr['cal_min'] = in_data.min() * slope + intercept else: nhdr['cal_min'] = float(opts.minmax[0]) if opts.minmax[1] == 'parse': nhdr['cal_max'] = in_data.max() * slope + intercept else: nhdr['cal_max'] = float(opts.minmax[1]) # container for potential NIfTI1 header extensions if opts.store_header: # dump the full PAR header content into an extension with open(infile, 'rb') as fobj: # contents must be bytes hdr_dump = fobj.read() dump_ext = nifti1.Nifti1Extension('comment', hdr_dump) nhdr.extensions.append(dump_ext) verbose('Writing %s' % outfilename) nibabel.save(nimg, outfilename) # write out bvals/bvecs if requested if opts.bvs: if bvals is None and bvecs is None: verbose('No DTI volumes detected, bvals and bvecs not written') elif bvecs is None: verbose('DTI volumes detected, but no diffusion direction info was' 'found. Writing .bvals file only.') 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') else: verbose('Writing .bvals and .bvecs files') # Transform bvecs with reorientation affine orig2new = npl.inv(t_aff) bv_reorient = from_matvec(to_matvec(orig2new)[0], [0, 0, 0]) bvecs = apply_affine(bv_reorient, bvecs) 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') # export data labels varying along the 4th dimensions if requested if opts.vol_info: labels = pr_img.header.get_volume_labels() if len(labels) > 0: vol_keys = list(labels.keys()) with open(basefilename + '.ordering.csv', 'w') as csvfile: csvwriter = csv.writer(csvfile, delimiter=',') csvwriter.writerow(vol_keys) for vals in zip(*[labels[k] for k in vol_keys]): csvwriter.writerow(vals) # 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)
def space_time_realign( input, tr, slice_order="descending", slice_dim=2, slice_dir=1, apply=True, make_figure=False, out_name=None ): """ This is a scripting interface to `nipy.algorithms.registration.SpaceTimeRealign` Parameters ---------- input : str or list A full path to a file-name (4D nifti time-series) , or to a directory containing 4D nifti time-series, or a list of full-paths to files. tr : float The repetition time slice_order : str (optional) This is the order of slice-times in the acquisition. This is used as a key into the ``SLICETIME_FUNCTIONS`` dictionary from :mod:`nipy.algorithms.slicetiming.timefuncs`. Default: 'descending'. slice_dim : int (optional) Denotes the axis in `images` that is the slice axis. In a 4D image, this will often be axis = 2 (default). slice_dir : int (optional) 1 if the slices were acquired slice 0 first (default), slice -1 last, or -1 if acquire slice -1 first, slice 0 last. apply : bool (optional) Whether to apply the transformation and produce an output. Default: True. make_figure : bool (optional) Whether to generate a .png figure with the parameters across scans. out_name : bool (optional) Specify an output location (full path) for the files that are generated. Default: generate files in the path of the inputs (with an `_mc` suffix added to the file-names. Returns ------- transforms : ndarray An (n_times_points,) shaped array containing `nipy.algorithms.registration.affine.Rigid` class instances for each time point in the time-series. These can be used as affine transforms by referring to their `.as_affine` attribute. """ if make_figure: if not HAVE_MPL: e_s = "You need to have matplotlib installed to run this function" e_s += " with `make_figure` set to `True`" raise RuntimeError(e_s) # If we got only a single file, we motion correct that one: if op.isfile(input): if not (input.endswith(".nii") or input.endswith(".nii.gz")): e_s = "Input needs to be a nifti file ('.nii' or '.nii.gz'" raise ValueError(e_s) fnames = [input] input = nib.load(input) # If this is a full-path to a directory containing files, it's still a # string: elif isinstance(input, str): list_of_files = os.listdir(input) fnames = [op.join(input, f) for f in np.sort(list_of_files) if (f.endswith(".nii") or f.endswith(".nii.gz"))] input = [nib.load(x) for x in fnames] # Assume that it's a list of full-paths to files: else: input = [nib.load(x) for x in input] slice_times = timefuncs[slice_order] slice_info = [slice_dim, slice_dir] reggy = SpaceTimeRealign(input, tr, slice_times, slice_info) reggy.estimate(align_runs=True) # We now have the transformation parameters in here: transforms = np.squeeze(np.array(reggy._transforms)) rot = np.array([t.rotation for t in transforms]) trans = np.array([t.translation for t in transforms]) if apply: new_reggy = reggy.resample(align_runs=True) for run_idx, new_im in enumerate(new_reggy): # Fix output TR - it was probably lost in the image realign step assert new_im.affine.shape == (5, 5) new_im.affine[:] = new_im.affine.dot(np.diag([1, 1, 1, tr, 1])) # Save it out to a '.nii.gz' file: froot, ext, trail_ext = splitext_addext(fnames[run_idx]) path, fname = op.split(froot) # We retain the file-name adding '_mc' regardless of where it's # saved new_path = path if out_name is None else out_name save_image(new_im, op.join(new_path, fname + "_mc.nii.gz")) if make_figure: # Delay MPL plotting import to latest moment to avoid errors trying # import the default MPL backend (such as tkinter, which may not be # installed). See: https://github.com/nipy/nipy/issues/414 import matplotlib.pyplot as plt figure, ax = plt.subplots(2) figure.set_size_inches([8, 6]) ax[0].plot(rot) ax[0].set_xlabel("Time (TR)") ax[0].set_ylabel("Translation (mm)") ax[1].plot(trans) ax[1].set_xlabel("Time (TR)") ax[1].set_ylabel("Rotation (radians)") figure.savefig(op.join(os.path.split(fnames[0])[0], "mc_params.png")) return transforms
def proc_file(infile, opts): # 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 = "dv" if opts.scaling == "off" else opts.scaling infile = fname_ext_ul_case(infile) pr_img = pr.load(infile, permit_truncated=opts.permit_truncated, scaling=scaling, strict_sort=opts.strict_sort) pr_hdr = pr_img.header affine = pr_hdr.get_affine(origin=opts.origin) slope, intercept = pr_hdr.get_data_scaling(scaling) if opts.scaling != "off": verbose('Using data scaling "%s"' % opts.scaling) # get original scaling, and decide if we scale in-place or not if opts.scaling == "off": slope = np.array([1.0]) intercept = np.array([0.0]) in_data = pr_img.dataobj.get_unscaled() out_dtype = pr_hdr.get_data_dtype() elif not np.any(np.diff(slope)) and not np.any(np.diff(intercept)): # Single scalefactor case slope = slope.ravel()[0] intercept = intercept.ravel()[0] in_data = pr_img.dataobj.get_unscaled() out_dtype = pr_hdr.get_data_dtype() else: # Multi scalefactor case slope = np.array([1.0]) intercept = np.array([0.0]) in_data = np.array(pr_img.dataobj) out_dtype = np.float64 # Reorient data block to LAS+ if necessary ornt = io_orientation(np.diag([-1, 1, 1, 1]).dot(affine)) if np.all(ornt == [[0, 1], [1, 1], [2, 1]]): # already in LAS+ t_aff = np.eye(4) else: # Not in LAS+ t_aff = inv_ornt_aff(ornt, pr_img.shape) affine = np.dot(affine, t_aff) in_data = apply_orientation(in_data, ornt) bvals, bvecs = pr_hdr.get_bvals_bvecs() if not opts.keep_trace: # discard Philips DTI trace if present if bvecs is not None: bad_mask = np.logical_and(bvals != 0, (bvecs == 0).all(axis=1)) if bad_mask.sum() > 0: pl = "s" if bad_mask.sum() != 1 else "" verbose("Removing %s DTI trace volume%s" % (bad_mask.sum(), pl)) good_mask = ~bad_mask in_data = in_data[..., good_mask] bvals = bvals[good_mask] bvecs = bvecs[good_mask] # Make corresponding NIfTI image nimg = nifti1.Nifti1Image(in_data, affine, pr_hdr) nhdr = nimg.header nhdr.set_data_dtype(out_dtype) nhdr.set_slope_inter(slope, intercept) nhdr.set_sform(affine, code=1) nhdr.set_qform(affine, code=1) if "parse" in opts.minmax: # need to get the scaled data verbose("Loading (and scaling) the data to determine value range") if opts.minmax[0] == "parse": nhdr["cal_min"] = in_data.min() * slope + intercept else: nhdr["cal_min"] = float(opts.minmax[0]) if opts.minmax[1] == "parse": nhdr["cal_max"] = in_data.max() * slope + intercept else: nhdr["cal_max"] = float(opts.minmax[1]) # container for potential NIfTI1 header extensions if opts.store_header: # dump the full PAR header content into an extension with open(infile, "rb") as fobj: # contents must be bytes hdr_dump = fobj.read() dump_ext = nifti1.Nifti1Extension("comment", hdr_dump) nhdr.extensions.append(dump_ext) verbose("Writing %s" % outfilename) nibabel.save(nimg, outfilename) # write out bvals/bvecs if requested if opts.bvs: if bvals is None and bvecs is None: verbose("No DTI volumes detected, bvals and bvecs not written") elif bvecs is None: verbose("DTI volumes detected, but no diffusion direction info was" "found. Writing .bvals file only.") 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") else: verbose("Writing .bvals and .bvecs files") # Transform bvecs with reorientation affine orig2new = npl.inv(t_aff) bv_reorient = from_matvec(to_matvec(orig2new)[0], [0, 0, 0]) bvecs = apply_affine(bv_reorient, bvecs) 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") # export data labels varying along the 4th dimensions if requested if opts.vol_info: labels = pr_img.header.get_volume_labels() if len(labels) > 0: vol_keys = list(labels.keys()) with open(basefilename + ".ordering.csv", "w") as csvfile: csvwriter = csv.writer(csvfile, delimiter=",") csvwriter.writerow(vol_keys) for vals in zip(*[labels[k] for k in vol_keys]): csvwriter.writerow(vals) # 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)
def space_time_realign(input, tr, slice_order='descending', slice_dim=2, slice_dir=1, apply=True, make_figure=False, out_name=None): """ This is a scripting interface to `nipy.algorithms.registration.SpaceTimeRealign` Parameters ---------- input : str or list A full path to a file-name (4D nifti time-series) , or to a directory containing 4D nifti time-series, or a list of full-paths to files. tr : float The repetition time slice_order : str (optional) This is the order of slice-times in the acquisition. This is used as a key into the ``SLICETIME_FUNCTIONS`` dictionary from :mod:`nipy.algorithms.slicetiming.timefuncs`. Default: 'descending'. slice_dim : int (optional) Denotes the axis in `images` that is the slice axis. In a 4D image, this will often be axis = 2 (default). slice_dir : int (optional) 1 if the slices were acquired slice 0 first (default), slice -1 last, or -1 if acquire slice -1 first, slice 0 last. apply : bool (optional) Whether to apply the transformation and produce an output. Default: True. make_figure : bool (optional) Whether to generate a .png figure with the parameters across scans. out_name : bool (optional) Specify an output location (full path) for the files that are generated. Default: generate files in the path of the inputs (with an `_mc` suffix added to the file-names. Returns ------- transforms : ndarray An (n_times_points,) shaped array containing `nipy.algorithms.registration.affine.Rigid` class instances for each time point in the time-series. These can be used as affine transforms by referring to their `.as_affine` attribute. """ if make_figure: if not HAVE_MPL: e_s = "You need to have matplotlib installed to run this function" e_s += " with `make_figure` set to `True`" raise RuntimeError(e_s) # If we got only a single file, we motion correct that one: if op.isfile(input): if not (input.endswith('.nii') or input.endswith('.nii.gz')): e_s = "Input needs to be a nifti file ('.nii' or '.nii.gz'" raise ValueError(e_s) fnames = [input] input = nib.load(input) # If this is a full-path to a directory containing files, it's still a # string: elif isinstance(input, str): list_of_files = os.listdir(input) fnames = [ op.join(input, f) for f in np.sort(list_of_files) if (f.endswith('.nii') or f.endswith('.nii.gz')) ] input = [nib.load(x) for x in fnames] # Assume that it's a list of full-paths to files: else: input = [nib.load(x) for x in input] slice_times = timefuncs[slice_order] slice_info = [slice_dim, slice_dir] reggy = SpaceTimeRealign(input, tr, slice_times, slice_info) reggy.estimate(align_runs=True) # We now have the transformation parameters in here: transforms = np.squeeze(np.array(reggy._transforms)) rot = np.array([t.rotation for t in transforms]) trans = np.array([t.translation for t in transforms]) if apply: new_reggy = reggy.resample(align_runs=True) for run_idx, new_im in enumerate(new_reggy): # Fix output TR - it was probably lost in the image realign step assert new_im.affine.shape == (5, 5) new_im.affine[:] = new_im.affine.dot(np.diag([1, 1, 1, tr, 1])) # Save it out to a '.nii.gz' file: froot, ext, trail_ext = splitext_addext(fnames[run_idx]) path, fname = op.split(froot) # We retain the file-name adding '_mc' regardless of where it's # saved new_path = path if out_name is None else out_name save_image(new_im, op.join(new_path, fname + '_mc.nii.gz')) if make_figure: figure, ax = plt.subplots(2) figure.set_size_inches([8, 6]) ax[0].plot(rot) ax[0].set_xlabel('Time (TR)') ax[0].set_ylabel('Translation (mm)') ax[1].plot(trans) ax[1].set_xlabel('Time (TR)') ax[1].set_ylabel('Rotation (radians)') figure.savefig(op.join(os.path.split(fnames[0])[0], 'mc_params.png')) return transforms