def isMelodicDir(path): """Returns ``True`` if the given path looks like it is a MELODIC directory, ``False`` otherwise. A MELODIC directory: - Must contain a file called ``melodic_IC.nii.gz`` or ``melodic_oIC.nii.gz``. - Must contain a file called ``melodic_mix``. - Must contain a file called ``melodic_FTmix``. """ path = op.abspath(path) if not op.isdir(path): return False # Must contain an image file called # melodic_IC or melodic_oIC prefixes = ['melodic_IC', 'melodic_oIC'] for p in prefixes: try: fslimage.addExt(op.join(path, p)) break except fslimage.PathError: pass else: return False # Must contain files called # melodic_mix and melodic_FTmix if not op.exists(op.join(path, 'melodic_mix')): return False if not op.exists(op.join(path, 'melodic_FTmix')): return False return True
def isMelodicDir(path): """Returns ``True`` if the given path looks like it is contained within a MELODIC directory, ``False`` otherwise. A melodic directory: - Must be named ``*.ica``. - Must contain a file called ``melodic_IC.nii.gz``. - Must contain a file called ``melodic_mix``. - Must contain a file called ``melodic_FTmix``. """ path = op.abspath(path) if op.isdir(path): dirname = path else: dirname = op.dirname(path) sufs = ['.ica'] if not any([dirname.endswith(suf) for suf in sufs]): return False # Must contain an image file called melodic_IC try: fslimage.addExt(op.join(dirname, 'melodic_IC'), mustExist=True) except fslimage.PathError: return False # Must contain files called # melodic_mix and melodic_FTmix if not op.exists(op.join(dirname, 'melodic_mix')): return False if not op.exists(op.join(dirname, 'melodic_FTmix')): return False return True
def test_addExt(): """Test the addExt function. """ default = fslimage.defaultExt() testdir = tempfile.mkdtemp() toCreate = [ 'compressed.nii.gz', 'uncompressed.nii', 'img_hdr_pair.img', 'compressed_img_hdr_pair.img.gz', 'ambiguous.nii', 'ambiguous.nii.gz', 'ambiguous.img', 'ambiguous.img.gz' ] # (file, mustExist, expected) tests = [('blah', False, 'blah{}'.format(default)), ('blah.nii', False, 'blah.nii'), ('blah.nii.gz', False, 'blah.nii.gz'), ('blah.img', False, 'blah.img'), ('blah.hdr', False, 'blah.hdr'), ('blah.img.gz', False, 'blah.img.gz'), ('blah.hdr.gz', False, 'blah.hdr.gz'), ('compressed', True, 'compressed.nii.gz'), ('compressed.nii.gz', True, 'compressed.nii.gz'), ('uncompressed', True, 'uncompressed.nii'), ('uncompressed.nii', True, 'uncompressed.nii'), ('img_hdr_pair', True, 'img_hdr_pair.hdr'), ('img_hdr_pair.hdr', True, 'img_hdr_pair.hdr'), ('img_hdr_pair.img', True, 'img_hdr_pair.img'), ('compressed_img_hdr_pair', True, 'compressed_img_hdr_pair.hdr.gz'), ('compressed_img_hdr_pair.img.gz', True, 'compressed_img_hdr_pair.img.gz'), ('compressed_img_hdr_pair.hdr.gz', True, 'compressed_img_hdr_pair.hdr.gz'), ('ambiguous.nii', True, 'ambiguous.nii'), ('ambiguous.nii.gz', True, 'ambiguous.nii.gz'), ('ambiguous.img', True, 'ambiguous.img'), ('ambiguous.hdr', True, 'ambiguous.hdr'), ('ambiguous.img.gz', True, 'ambiguous.img.gz'), ('ambiguous.hdr.gz', True, 'ambiguous.hdr.gz')] for path in toCreate: path = op.abspath(op.join(testdir, path)) make_random_image(path) try: for path, mustExist, expected in tests: path = op.abspath(op.join(testdir, path)) expected = op.abspath(op.join(testdir, expected)) assert fslimage.addExt(path, mustExist) == expected # Make sure that an ambiguous path fails with pytest.raises(fslimage.PathError): path = op.join(testdir, 'ambiguous') fslimage.addExt(path, mustExist=True) finally: shutil.rmtree(testdir)
def ensureIsImage(img): """Ensures that the given ``img`` is an in-memory ``nibabel`` object. """ if isinstance(img, six.string_types): img = fslimage.addExt(img) img = nib.load(img) return img
def getDataFile(meldir): """If the given melodic directory is contained within another analysis directory, the path to the data file is returned. Otherwise ``None`` is returned. """ topDir = getTopLevelAnalysisDir(meldir) if topDir is None: return None # People often rename filtered_func_data.nii.gz # to something like filtered_func_data_clean.nii.gz, # because that is the recommended approach when # performing ICA-based denoising). So we try both. candidates = ['filtered_func_data', 'filtered_func_data_clean'] for candidate in candidates: dataFile = op.join(topDir, candidate) try: return fslimage.addExt(dataFile) except fslimage.PathError: continue return None
def ensureIsImage(img): """Ensures that the given ``img`` is an in-memory ``nibabel`` object. """ if isinstance(img, (str, pathlib.Path)): img = fslimage.addExt(img) img = nib.load(img) return img
def findReferenceImage(modelfile): """Given a ``vtk`` file, attempts to find a corresponding ``NIFTI`` image file. Return the path to the image, or ``None`` if no image was found. Currently this function will only return an image for ``vtk`` files generated by `FIRST <https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FIRST>`_. """ try: dirname = op.dirname(modelfile) prefixes = [getFIRSTPrefix(modelfile)] except ValueError: return None if prefixes[0].endswith('_first'): prefixes.append(prefixes[0][:-6]) for p in prefixes: try: return fslimage.addExt(op.join(dirname, p), mustExist=True) except fslimage.PathError: continue return None
def checkFilesToExpect(files, outdir, outputType, datahashes): exts = { 'NIFTI' : ['.nii'], 'NIFTI_PAIR' : ['.hdr', '.img'], 'NIFTI_GZ' : ['.nii.gz'], '' : ['.nii.gz'], }.get(outputType, None) allFiles = [] if isinstance(files, str): files = files.split() for f in files: f, fe = fslimage.splitExt(f) fexts = exts if fexts is None: fexts = { '.img' : ['.hdr', '.img'], '.hdr' : ['.hdr', '.img'], '.nii' : ['.nii'], '.nii.gz' : ['.nii.gz'] }.get(fe, []) # filename already has a different extension elif fe != '' and fe not in fexts: fexts = [fe] for e in fexts: expected = op.join(outdir, f + e) allFiles.append(expected) print(' ', expected) assert op.exists(expected) allThatExist = os.listdir(outdir) allThatExist = [f for f in allThatExist if op.isfile(op.join(outdir, f))] assert len(allThatExist) == len(allFiles) for i, f in enumerate(files): f = fslimage.addExt(op.join(outdir, f), mustExist=True) if isinstance(datahashes, list): if len(datahashes) > len(files): diff = len(datahashes) - len(files) h = datahashes[i + diff] else: h = datahashes[i] else: h = datahashes[op.basename(f)] checkImageHash(f, h)
def ImageOut(basename): """ Uses the FSL convention to create a complete image output filename :param basename: filename provided by the user :return: filename with extension """ return image.addExt(basename, mustExist=False)
def guessDataSourceType(path): """A convenience function which, given the name of a file or directory, figures out a suitable overlay type. Returns a tuple containing two values - a type which should be able to load the path, and the path itself, possibly adjusted. If the type is unrecognised, the first tuple value will be ``None``. """ import fsl.data.mesh as fslmesh import fsl.data.gifti as fslgifti import fsl.data.featimage as featimage import fsl.data.melodicimage as melimage import fsl.data.dtifit as dtifit import fsl.data.melodicanalysis as melanalysis import fsl.data.featanalysis as featanalysis # Support files opened via fsleyes:// URL if path.startswith('fsleyes://'): path = path[10:] path = op.abspath(path) # VTK files are easy if path.endswith('.vtk'): return fslmesh.TriangleMesh, path # So are GIFTIS if path.endswith('.gii'): return fslgifti.GiftiSurface, path # Analysis directory? if op.isdir(path): if melanalysis.isMelodicDir(path): return melimage.MelodicImage, path elif featanalysis.isFEATDir(path): return featimage.FEATImage, path elif dtifit.isDTIFitPath(path): return dtifit.DTIFitTensor, path # Assume it's a NIFTI image try: path = fslimage.addExt(path, mustExist=True) except fslimage.PathError: return None, path if melanalysis.isMelodicImage(path): return melimage.MelodicImage, path elif featanalysis.isFEATImage(path): return featimage.FEATImage, path else: return fslimage.Image, path # Otherwise, I don't # know what to do return None, path
def getfmt(arg, fname, exist): ext = op.splitext(fname)[1] if ext in ('.mat', '.x5'): return ext[1:] fname = fslimage.addExt(fname, mustExist=exist) if fslimage.looksLikeImage(fname): return 'nii' parser.error('Could not infer format from ' 'filename: {}'.format(args.input))
def imglob(paths, output=None): """Given a list of file names, identifies and returns the unique NIFTI/ANALYZE image files that exist. :arg paths: Sequence of paths/prefixes to glob. :arg output: One of ``'prefix'`` (the default), ``'all'``, or ``'primary'``: - ``'prefix'``: Returns the files without extensions. - ``'all'``: Returns all files that match (e.g. both ``.img`` and ``.hdr`` files will be returned). - ``'primary'``: Returns only the primary file of each matching file group, e.g. only the ``.hdr`` file would be returned from an ``.img``/``.hdr`` pair. :returns: A sequence of resolved path names, in the form specified by the ``output`` parameter. """ if output is None: output = 'prefix' if output not in ('prefix', 'all', 'primary'): raise ValueError('Unsupported output format: {}'.format(output)) imgfiles = [] # Build a list of all image files (both # hdr and img and otherwise) that match for path in paths: try: path = fslimage.removeExt(path) imgfiles.extend(fslimage.addExt(path, unambiguous=False)) except fslpath.PathError: continue if output == 'prefix': imgfiles = fslpath.removeDuplicates(imgfiles, allowedExts=exts, fileGroups=groups) imgfiles = [fslpath.removeExt(f, exts) for f in imgfiles] elif output == 'primary': imgfiles = fslpath.removeDuplicates(imgfiles, allowedExts=exts, fileGroups=groups) return list(sorted(set(imgfiles)))
def isMelodicImage(path): """Returns ``True`` if the given path looks like it is a melodic component image file, ``False`` otherwise. """ try: path = fslimage.addExt(path, mustExist=True) except fslimage.PathError: return False dirname = op.dirname(path) filename = op.basename(path) return filename.startswith('melodic_IC') and isMelodicDir(dirname)
def flipdir(dirname): newdir = '{}_flipdir'.format(dirname.strip(op.sep)) if op.exists(newdir): return newdir os.mkdir(newdir) for f in os.listdir(dirname): if not op.exists(op.join(dirname, f)): continue flipped = fliporient(op.join(dirname, f)) flipped = fslimage.addExt(flipped) shutil.move(flipped, op.join(newdir, f)) return newdir
def Image(filename, *args, **kwargs): """ Reads in an image from a NIFTI or Analyze file. :arg filename: filename provided by the user :return: fsl.data.image.Image object All other arguments are passed through to the :class:`.Image` upon creation. """ try: full_filename = image.addExt(filename) except path.PathError as e: raise argparse.ArgumentTypeError(*e.args) return image.Image(full_filename, *args, **kwargs)
def getDataFile(meldir): """If the given melodic directory is contained within another analysis directory, the path to the data file is returned. Otherwise ``None`` is returned. """ topDir = getTopLevelAnalysisDir(meldir) if topDir is None: return None dataFile = op.join(topDir, 'filtered_func_data') try: return fslimage.addExt(dataFile) except fslimage.PathError: return None
def asrt(path, cls): restype, respath = dutils.guessType(path) assert restype is cls if path.startswith('fsleyes://'): path = path[10:] # image path might not have an extension try: path = fslimage.addExt(path, mustExist=True) except fslimage.PathError: pass assert respath == op.abspath(path)
def test_MGHImage_save(): testfile = op.join(datadir, 'example.mgz') with tempdir.tempdir(): shutil.copy(testfile, 'example.mgz') testfile = 'example.mgz' img = fslmgh.MGHImage(testfile) img.save() expfile = op.abspath(fslimage.addExt('example', mustExist=False)) assert img.dataSource == op.abspath(expfile)
def isMelodicImage(path): """Returns ``True`` if the given path looks like it is a melodic component image file, ``False`` otherwise. """ try: path = fslimage.addExt(path) except fslimage.PathError: return False dirname = op.dirname(path) filename = op.basename(path) filename = fslimage.removeExt(filename) prefixes = ['melodic_IC', 'melodic_oIC'] return any([filename == p for p in prefixes]) and isMelodicDir(dirname)
def run(job_id: int, njobs: int, mask_fn: str, command: Sequence[str]): """ Runs part of the script :param job_id: job ID :param njobs: number of jobs :param mask_fn: mask filename :param command: script to run """ logger.debug(f'loading original mask from {mask_fn}') mask_img = nib.load(addExt(mask_fn, mustExist=True, unambiguous=True)) mask = mask_img.get_data() > 0 voxels = np.where(mask) nvox = voxels[0].size boundaries = np.round(np.linspace(0, nvox, njobs + 1)).astype('int') use = tuple(vox[boundaries[job_id]:boundaries[job_id + 1]] for vox in voxels) logger.debug( f'creating new mask covering voxels {boundaries[job_id]} to ' + f'{boundaries[job_id + 1]} out of {nvox} voxels') mask[()] = False mask[use] = True marker = get_markers(njobs)[job_id] with tempfile.NamedTemporaryFile(prefix='mask' + marker, suffix='.nii.gz') as temp_mask: logger.debug(f'Storing new mask under {temp_mask}') nib.Nifti1Image(mask.astype('i4'), affine=None, header=mask_img.header).to_filename(temp_mask.name) if not any('MASK' in part for part in command): raise ValueError('MASK not found') new_cmd = [ part.replace('MASK', temp_mask.name).replace('JOBID', marker) for part in command ] logger.info(f'Running {new_cmd}') srun(new_cmd, check=True)
def guessFlirtFiles(path): """Given a ``path`` to a NIFTI image file, tries to guess an appropriate FLIRT transformation matrix file and reference image. The guess is based on the path location (e.g. if it is a FEAT or MELODIC image). Returns a tuple containing paths to the matrix file and reference image, or ``(None, None)`` if a guess couldn't be made. """ import fsl.data.featanalysis as featanalysis import fsl.data.melodicanalysis as melodicanalysis if path is None: return None, None filename = op.basename(path) dirname = op.dirname(path) regDir = None srcRefMap = {} func2struc = 'example_func2highres.mat' struc2std = 'highres2standard.mat' featDir = featanalysis.getAnalysisDir(dirname) melDir = melodicanalysis.getAnalysisDir(dirname) # TODO more heuristics if featDir is not None: regDir = op.join(featDir, 'reg') srcRefMap = { 'example_func': ('highres', func2struc), 'filtered_func_data': ('highres', func2struc), 'mean_func': ('highres', func2struc), 'thresh_zstat': ('highres', func2struc), op.join('stats', 'zstat'): ('highres', func2struc), op.join('stats', 'tstat'): ('highres', func2struc), op.join('stats', 'cope'): ('highres', func2struc), op.join('stats', 'pe'): ('highres', func2struc), 'highres': ('standard', struc2std), 'highres_head': ('standard', struc2std), 'struc': ('standard', struc2std), 'struct': ('standard', struc2std), 'struct_brain': ('standard', struc2std), 'struc_brain': ('standard', struc2std), } elif melodicanalysis.isMelodicDir(dirname): if melDir.startswith('filtered_func_data'): regDir = op.join(melDir, '..', 'reg') else: regDir = op.join(melDir, 'reg') srcRefMap = { 'filtered_func_data': ('highres', func2struc), 'melodic_IC': ('highres', func2struc), 'example_func': ('highres', func2struc), 'mean_func': ('highres', func2struc), 'highres': ('standard', struc2std), 'highres_head': ('standard', struc2std), 'struc': ('standard', struc2std), 'struct': ('standard', struc2std), 'struct_brain': ('standard', struc2std), 'struc_brain': ('standard', struc2std), } matFile = None refFile = None for src, (ref, mat) in srcRefMap.items(): if not filename.startswith(src): continue mat = op.join(regDir, mat) ref = op.join(regDir, ref) try: ref = fslimage.addExt(ref) except Exception: continue if op.exists(mat): matFile = mat refFile = ref break return matFile, refFile
def getICFile(meldir): """Returns the path to the melodic IC image. """ try: return fslimage.addExt(op.join(meldir, 'melodic_IC')) except fslimage.PathError: return fslimage.addExt(op.join(meldir, 'melodic_oIC'))
def getMeanFile(meldir): """Return a path to the mean image of the meloidic input data. """ return fslimage.addExt(op.join(meldir, 'mean'))
def getICFile(meldir): """Returns the path to the melodic IC image. """ return fslimage.addExt(op.join(meldir, 'melodic_IC'))
def guessDataSourceType(path): """A convenience function which, given the name of a file or directory, figures out a suitable overlay type. Returns a tuple containing two values - a type which should be able to load the path, and the path itself, possibly adjusted. If the type is unrecognised, the first tuple value will be ``None``. """ import fsl.data.vtk as fslvtk import fsl.data.gifti as fslgifti import fsl.data.freesurfer as fslfs import fsl.data.mghimage as fslmgh import fsl.data.featimage as featimage import fsl.data.melodicimage as melimage import fsl.data.dtifit as dtifit import fsl.data.melodicanalysis as melanalysis import fsl.data.featanalysis as featanalysis # Support files opened via fsleyes:// URL if path.startswith('fsleyes://'): path = path[10:] path = op.abspath(path) # Accept images sans-extension try: path = fslimage.addExt(path, mustExist=True) except fslimage.PathError: pass if op.isfile(path): # Some types are easy - just check the extensions if fslpath.hasExt(path, fslvtk.ALLOWED_EXTENSIONS): return fslvtk.VTKMesh, path elif fslpath.hasExt(path, fslgifti.ALLOWED_EXTENSIONS): return fslgifti.GiftiMesh, path elif fslfs.isGeometryFile(path): return fslfs.FreesurferMesh, path elif fslpath.hasExt(path, fslmgh.ALLOWED_EXTENSIONS): return fslmgh.MGHImage, path # Other specialised image types elif melanalysis.isMelodicImage(path): return melimage.MelodicImage, path elif featanalysis.isFEATImage(path): return featimage.FEATImage, path elif fslimage.looksLikeImage(path): return fslimage.Image, path # Analysis directory? elif op.isdir(path): if melanalysis.isMelodicDir(path): return melimage.MelodicImage, path elif featanalysis.isFEATDir(path): return featimage.FEATImage, path elif dtifit.isDTIFitPath(path): return dtifit.DTIFitTensor, path # Otherwise, I don't # know what to do return None, path
def imcp(src, dest, overwrite=False, useDefaultExt=False, move=False): """Copy the given ``src`` file to destination ``dest``. A :class:`.fsl.utils.path.PathError` is raised if anything goes wrong. :arg src: Path to copy. If ``allowedExts`` is provided, the file extension can be omitted. :arg dest: Destination path. Can be an incomplete file specification (i.e. without the extension), or a directory. :arg overwrite: If ``True`` this function will overwrite files that already exist. Defaults to ``False``. :arg useDefaultExt: Defaults to ``False``. If ``True``, the destination file type will be set according to the default extension, specified by :func:`~fsl.data.image.defaultExt`. If the source file does not have the same type as the default extension, it will be converted. If ``False``, the source file type is not changed. :arg move: If ``True``, the files are moved, instead of being copied. See :func:`immv`. """ import nibabel as nib if op.isdir(dest): dest = op.join(dest, op.basename(src)) srcBase, srcExt = fslimage.splitExt(src) destBase, destExt = fslimage.splitExt(dest) # src was specified without an # extension, or the specified # src does not have an allowed # extension. if srcExt == '': # Try to resolve the specified src # path - if src does not exist, or # does not have an allowed extension, # addExt will raise an error src = fslimage.addExt(src, mustExist=True) # We've resolved src to a # full filename - split it # again to get its extension srcBase, srcExt = fslimage.splitExt(src) if not op.exists(src): raise fslpath.PathError('imcp error - source path ' 'does not exist: {}'.format(src)) # Figure out the destination file # extension/type. If useDefaultExt # is True, we use the default # extension. Otherwise, if no # destination file extension is # provided, we use the source # extension. if useDefaultExt: destExt = fslimage.defaultExt() elif destExt == '': destExt = srcExt # Resolve any file group differences # e.g. we don't care if the src is # specified as file.hdr, and the dest # is specified as file.img - if src # and dest are part of the same file # group, we replace the dest extension # with the src extension. if srcExt != destExt: for group in fslimage.FILE_GROUPS: if srcExt in group and destExt in group: destExt = srcExt break dest = destBase + destExt # Give up if we don't have permission. if not os.access(op.dirname(dest), os.W_OK | os.X_OK): raise fslpath.PathError('imcp error - cannot write to {}'.format(dest)) if move and not os.access(op.dirname(src), os.W_OK | os.X_OK): raise fslpath.PathError('imcp error - cannot move from {}'.format(src)) # If the source file type does not # match the destination file type, # we need to perform a conversion. # # This is more expensive in terms of # io and cpu, but programmatically # very easy - nibabel does all the # hard work. if srcExt != destExt: if not overwrite and op.exists(dest): raise fslpath.PathError('imcp error - destination already ' 'exists ({})'.format(dest)) img = nib.load(src) nib.save(img, dest) if move: os.remove(src) return # Otherwise we do a file copy. This # is actually more complicated than # converting the file type due to # hdr/img pairs ... # # If the source is part of a file group, # e.g. src.img/src.hdr), we need to copy # the whole set of files. So here we # build a list of source files that need # to be copied/moved. The getFileGroup # function returns all other files that # are associated with this file (i.e. # part of the same group). # # We store the sources as separate # (base, ext) tuples, so we don't # have to re-split when creating # destination paths. # # The unambiguous flag tells getFileGroup # to raise an error if the source appears # to be part of an incopmlete file group # (e.g. file.hdr without an accompanying # file.img). copySrcs = fslpath.getFileGroup(src, fslimage.ALLOWED_EXTENSIONS, fslimage.FILE_GROUPS, fullPaths=False, unambiguous=True) copySrcs = [(srcBase, e) for e in copySrcs] # Build a list of destinations for each # copy source - we build this list in # advance, so we can fail if any of the # destinations already exist. We also # re-combine the source bases/extensions. copyDests = [destBase + e for (b, e) in copySrcs] copySrcs = [b + e for (b, e) in copySrcs] # Fail if any of the destination # paths already exist if not overwrite and any([op.exists(d) for d in copyDests]): raise fslpath.PathError('imcp error - a destination path already ' 'exists ({})'.format(', '.join(copyDests))) # Do the copy/move for src, dest in zip(copySrcs, copyDests): if move: shutil.move(src, dest) else: shutil.copy(src, dest)