From 5bf216bc8bc5f4508486896540235109ad124951 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Thu, 17 Nov 2016 16:14:47 +0000 Subject: [PATCH] imcp/immv moved into their own module. They now have an option to honour the default file extensions (i.e. the FSLOUTPUTTYPE) - this is used by the fslpy_immv/imcp command line scripts. --- bin/fslpy_imcp | 37 +++------ bin/fslpy_immv | 43 ++++------- fsl/data/image.py | 45 +++++------ fsl/utils/imcp.py | 193 ++++++++++++++++++++++++++++++++++++++++++++++ fsl/utils/path.py | 143 ++++------------------------------ 5 files changed, 256 insertions(+), 205 deletions(-) create mode 100644 fsl/utils/imcp.py diff --git a/bin/fslpy_imcp b/bin/fslpy_imcp index 85fa9a561..eb4441bdc 100755 --- a/bin/fslpy_imcp +++ b/bin/fslpy_imcp @@ -11,20 +11,8 @@ from __future__ import print_function import os.path as op import sys import fsl.utils.path as fslpath - - -SUPPORTED_EXTENSIONS = [ - '.nii', '.nii.gz', - '.hdr', '.hdr.gz', - '.img', '.img.gz', - '.mnc', '.mnc.gz' -] - - -FILE_GROUPS = [ - ('.img', '.hdr'), - ('.img.gz', '.hdr.gz') -] +import fsl.utils.imcp as imcp +import fsl.data.image as fslimage usage = """Usage: @@ -37,35 +25,32 @@ Copy images from <file1> to <file2>, or copy all <file>s to <directory> NB: filenames can be basenames or include an extension. Recognised file extensions: {} -""".format(', '.join(SUPPORTED_EXTENSIONS)) +""".format(', '.join(fslimage.ALLOWED_EXTENSIONS)) -def main(): +def main(argv=None): """Parses CLI arguments (see the usage string), and calls the - fsl.utils.path.imcp function on each input. + :func:`fsl.utils.imcp.imcp` function on each input. """ - if len(sys.argv) < 2: + if len(argv) < 2: print(usage) sys.exit(1) - srcs = sys.argv[1:-1] - dest = sys.argv[ -1] + srcs = argv[:-1] + dest = argv[ -1] if len(srcs) > 1 and not op.isdir(dest): print(usage) sys.exit(1) srcs = fslpath.removeDuplicates(srcs, - allowedExts=SUPPORTED_EXTENSIONS, - fileGroups=FILE_GROUPS) + allowedExts=fslimage.ALLOWED_EXTENSIONS, + fileGroups=fslimage.FILE_GROUPS) for src in srcs: try: - fslpath.imcp(src, - dest, - allowedExts=SUPPORTED_EXTENSIONS, - fileGroups=FILE_GROUPS) + imcp.imcp(src, dest, useDefaultExt=True) except Exception as e: print(e) diff --git a/bin/fslpy_immv b/bin/fslpy_immv index f1f1baf08..0b5a5ae54 100755 --- a/bin/fslpy_immv +++ b/bin/fslpy_immv @@ -11,21 +11,8 @@ from __future__ import print_function import os.path as op import sys import fsl.utils.path as fslpath - - -SUPPORTED_EXTENSIONS = [ - '.nii', '.nii.gz', - '.hdr', '.hdr.gz', - '.img', '.img.gz', - '.mnc', '.mnc.gz' -] - - - -FILE_GROUPS = [ - ('.img', '.hdr'), - ('.img.gz', '.hdr.gz') -] +import fsl.utils.imcp as imcp +import fsl.data.image as fslimage usage = """Usage: @@ -38,35 +25,35 @@ Moves images from <file1> to <file2>, or move all <file>s to <directory> NB: filenames can be basenames or include an extension. Recognised file extensions: {} -""".format(', '.join(SUPPORTED_EXTENSIONS)) +""".format(', '.join(fslimage.ALLOWED_EXTENSIONS)) -def main(): +def main(argv=None): """Parses CLI arguments (see the usage string), and calls the - fsl.utils.path.immv function on each input. - """ + fsl.utils.imcp.immv function on each input. + """ + + if argv is None: + argv = sys.argv[1:] - if len(sys.argv) < 2: + if len(argv) < 2: print(usage) sys.exit(1) - srcs = sys.argv[1:-1] - dest = sys.argv[ -1] + srcs = argv[:-1] + dest = argv[ -1] if len(srcs) > 1 and not op.isdir(dest): print(usage) sys.exit(1) srcs = fslpath.removeDuplicates(srcs, - allowedExts=SUPPORTED_EXTENSIONS, - fileGroups=FILE_GROUPS) + allowedExts=fslimage.ALLOWED_EXTENSIONS, + fileGroups=fslimage.FILE_GROUPS) for src in srcs: try: - fslpath.immv(src, - dest, - allowedExts=SUPPORTED_EXTENSIONS, - fileGroups=FILE_GROUPS) + imcp.immv(src, dest, useDefaultExt=True, move=True) except Exception as e: print(e) diff --git a/fsl/data/image.py b/fsl/data/image.py index c81acf89f..1ff4ee748 100644 --- a/fsl/data/image.py +++ b/fsl/data/image.py @@ -25,9 +25,10 @@ and file names: :nosignatures: looksLikeImage - removeExt + addExt splitExt - addExt + getExt + removeExt defaultExt loadIndexedImageFile """ @@ -789,36 +790,36 @@ def looksLikeImage(filename, allowedExts=None): return any([filename.endswith(ext) for ext in allowedExts]) -def removeExt(filename): - """Removes the extension from the given file name. Returns the filename - unmodified if it does not have a supported extension. - - See :func:`~fsl.utils.path.removeExt`. - - :arg filename: The file name to strip. +def addExt(prefix, mustExist=True): + """Adds a file extension to the given file ``prefix``. See + :func:`~fsl.utils.path.addExt`. """ - return fslpath.removeExt(filename, ALLOWED_EXTENSIONS) + return fslpath.addExt(prefix, + ALLOWED_EXTENSIONS, + mustExist, + defaultExt(), + fileGroups=FILE_GROUPS) def splitExt(filename): - """Splits the base name and extension for the given ``filename``. - - See :func:`~fsl.utils.path.splitExt`. + """Splits the base name and extension for the given ``filename``. See + :func:`~fsl.utils.path.splitExt`. """ - return fslpath.splitExt(filename, ALLOWED_EXTENSIONS) -def addExt(prefix, mustExist=True): - """Adds a file extension to the given file ``prefix``. +def getExt(filename): + """Gets the extension for the given file name. See + :func:`~fsl.utils.path.getExt`. + """ + return fslpath.getExt(filename, ALLOWED_EXTENSIONS) + - See :func:`~fsl.utils.path.addExt`. +def removeExt(filename): + """Removes the extension from the given file name. See + :func:`~fsl.utils.path.removeExt`. """ - return fslpath.addExt(prefix, - ALLOWED_EXTENSIONS, - mustExist, - defaultExt(), - fileGroups=FILE_GROUPS) + return fslpath.removeExt(filename, ALLOWED_EXTENSIONS) def defaultExt(): diff --git a/fsl/utils/imcp.py b/fsl/utils/imcp.py new file mode 100644 index 000000000..47a509025 --- /dev/null +++ b/fsl/utils/imcp.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# +# imcp.py - Functions for moving/copying NIFTI image files. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +""" + +.. autosummary:: + :nosignatures: + + imcp + immv +""" + + +import os +import os.path as op +import shutil + +import fsl.utils.path as fslpath +import fsl.data.image as fslimage + + +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`. + + :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) + + # 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 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 + # conveting 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. + copySrcs = fslpath.getFileGroup(src, + fslimage.ALLOWED_EXTENSIONS, + fslimage.FILE_GROUPS, + fullPaths=False) + + copySrcs = [(srcBase, e) for e in copySrcs] + + # Note that these additional files + # do not have to exist, e.g. + # imcp('blah.img', ...) will still + # work if there is no blah.hdr + copySrcs = [(b, e) for (b, e) in copySrcs if op.exists(b + e)] + + # 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) + + +def immv(src, + dest, + overwrite=False, + useDefaultExt=True): + """Move the specified ``src`` to the specified ``dest``. See :func:`imcp`. + """ + imcp(src, + dest, + overwrite=overwrite, + useDefaultExt=useDefaultExt, + move=True) diff --git a/fsl/utils/path.py b/fsl/utils/path.py index 464c0b892..cbd9fd8bd 100644 --- a/fsl/utils/path.py +++ b/fsl/utils/path.py @@ -18,13 +18,10 @@ paths. getExt splitExt getFileGroup - imcp - immv """ import os.path as op -import shutil class PathError(Exception): @@ -231,23 +228,23 @@ def removeDuplicates(paths, allowedExts=None, fileGroups=None): 001.img 002.hdr 002.img - 003.img + 003.hdr 003.img And you call ``removeDuplicates`` like so:: - paths = ['001.img', '001.hdr', - '002.img', '002.hdr', - '003.img', '003.hdr'] + paths = ['001.img', '001.hdr', + '002.img', '002.hdr', + '003.img', '003.hdr'] - allowedExts = ['.img', '.hdr'] - fileGroups = [('.img', '.hdr')] + allowedExts = ['.img', '.hdr'] + fileGroups = [('.img', '.hdr')] removeDuplicates(paths, allowedExts, fileGroups) The returned list will be:: - ['001.img', '003.img', '003.img'] + ['001.img', '002.img', '003.img'] :arg paths: List of paths to reduce. @@ -290,16 +287,17 @@ def getFileGroup(path, allowedExts=None, fileGroups=None, fullPaths=True): return ``file.img`` (i.e. the file which matches the first extension in the group). - Similarly, if you call the :func:`imcp` or :func:`immv` functions with the - above parameters, both ``file.img`` and ``file.hdr`` will be moved. + Similarly, if you call the :func:`.imcp.imcp` or :func:`.imcp.immv` + functions with the above parameters, both ``file.img`` and ``file.hdr`` + will be moved. .. note:: The primary use-case of file groups is to resolve ambiguity with respect to NIFTI and ANALYSE75 image pairs. By specifying ``fileGroups=[('.img', '.hdr'), ('.img.gz', '.hdr.gz')]``, the - :func:`addExt`, :func:`immv` and :func:`imcp` functions are able - to figure out what you mean when you specify ``file``, and both - ``file.hdr`` and ``file.img`` (or ``file.hdr.gz`` and - ``file.img.gz``) exist. + :func:`addExt`, :func:`.imcp.immv` and :func:`.imcp.imcp` + functions are able to figure out what you mean when you specify + ``file``, and both ``file.hdr`` and ``file.img`` (or + ``file.hdr.gz`` and ``file.img.gz``) exist. :arg path: Path to the file. Must contain the file extension. @@ -342,116 +340,3 @@ def getFileGroup(path, allowedExts=None, fileGroups=None, fullPaths=True): else: if fullPaths: return matchedGroupFiles[0] else: return matchedGroups[ 0] - - -def imcp(src, - dest, - allowedExts=None, - fileGroups=None, - overwrite=False, - move=False): - """Copy the given ``src`` file to destination ``dest``. - - :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 allowedExts: Allowed/recognised file extensions. - - :arg fileGroups: Recognised file groups - see the :func:`getFileGroup` - documentation. - - :arg overwrite: If ``True`` this function will overwrite files that - already exist. Defaults to ``False``. - - :arg move: If ``True``, the files are moved, instead of being - copied. - """ - - base, ext = splitExt(src, allowedExts) - destIsDir = op.isdir(dest) - - # If dest has been specified - # as a file name, we don't - # care about its extension. - if not destIsDir: - dest = removeExt(dest, allowedExts) - - # src was specified without an - # extension, or the specitifed - # src does not have an allowed - # extension. - if ext == '': - - # 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 = addExt(src, - allowedExts, - mustExist=True, - fileGroups=fileGroups) - - # We've resolved src to a - # full filename - split it - # again to get its extension - base, ext = splitExt(src, allowedExts) - - # If the source is part of a file group, - # e.g. src.img/src.hdr, we want 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. - copySrcs = getFileGroup(src, allowedExts, fileGroups, fullPaths=False) - copySrcs = [(base, e) for e in copySrcs] - - # Note that these additional files - # do not have to exist, e.g. - # imcp('blah.img', ...) will still - # work if there is no blah.hdr - copySrcs = [(b, e) for (b, e) in copySrcs if op.exists(b + e)] - - # 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. - copyDests = [] - for i, (base, ext) in enumerate(copySrcs): - - # We'll also take this opportunity - # to re-combine the source paths - copySrcs[i] = base + ext - - basename = op.basename(base) - - if destIsDir: copyDests.append(op.join(dest, basename + ext)) - else: copyDests.append(dest + ext) - - # Fail if any of the destination - # paths already exist - if not overwrite and any([op.exists(d) for d in copyDests]): - raise 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) - - -def immv(src, dest, allowedExts=None, fileGroups=None, overwrite=False): - """Move the specified ``src`` to the specified ``dest``. See :func:`imcp`. - """ - imcp(src, dest, allowedExts, fileGroups, overwrite, move=True) -- GitLab