From 6685fea385aa836996a6ac9b044686b757686ee8 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Sun, 3 Apr 2016 14:39:46 +0100 Subject: [PATCH] Removed FSLeyes references, removed all code from fsl/__init__, removed log.memory .. all the __init__ stuff will make its way over to FSLeyes. --- fsl/__init__.py | 673 +--------------------------------------- fsl/data/constants.py | 2 +- fsl/data/image.py | 8 - fsl/data/model.py | 8 - fsl/data/tensorimage.py | 7 - fsl/utils/layout.py | 7 +- fsl/utils/platform.py | 5 +- 7 files changed, 10 insertions(+), 700 deletions(-) diff --git a/fsl/__init__.py b/fsl/__init__.py index 2db3ef495..31faab1ed 100644 --- a/fsl/__init__.py +++ b/fsl/__init__.py @@ -5,65 +5,20 @@ # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # -"""The :mod:`fsl` package contains front ends to various FSL tools, including -*FSLeyes*, the Python based image viewer. The following top-level functions -are provided: +"""The :mod:`fsl` package is a library which contains convenience classes +and functions for use by FSL python tools. It is broadly split into two +sub-packages: .. autosummary:: - :nosignatures: - - main - runTool - - -The conventional way to run ``fslpy`` is as follows:: - - import fsl - - # If no arguments are provided, the - # main() function will use sys.argv - fsl.main() - - # Or, you can pass in arguments - # programmatically - fsl.main(['fsleyes', 'MNI152_T1_1mm.nii.gz', '--cmap', 'hot']) - - # You can ask for top level help - fsl.main(['help']) - - # Or you can ask for help on a specific tool - fsl.main(['help', 'render']) - - -.. note:: When the ``fsl`` package is imported, it configures a new - ``logging`` level for tracking memory usage. The log level is - attached to the ``logging`` module as ``logging.MEMORY``, and - an associated log function is attached as ``logging.memory``. + fsl.data + fsl.utils .. note:: The ``fslpy`` version number (currently |version|) is set in a single place - the :mod:`fsl.version` module. """ -from __future__ import print_function - -import logging -import pkgutil -import warnings - -import os -import sys -import time -import argparse -import importlib -import threading -import subprocess - -import fsl.tools as tools -import fsl.utils.settings as fslsettings -import fsl.utils.platform as fslplatform - import fsl.version @@ -71,621 +26,3 @@ __version__ = fsl.version.__version__ """The current ``fslpy`` version number. This information is stored in the :mod:`fsl.version` module. """ - - -def main(args=None): - """``fslpy`` entry point. - - Parses command line arguments, loads the appropriate tool module, builds - and displays a GUI (if the tool has one), and/or executes the tool. - - :arg args: Command line arguments. If not provided, ``sys.argv`` is used. - """ - - if args is None: - args = sys.argv[1:] - - # Get a list of all available tools, - # and parse the top-level arguments - allTools = _getFSLToolNames() - fslTool, namespace, toolArgv = _parseTopLevelArgs(args, allTools) - - # GUI or command-line tool? - if fslTool.interface is not None: _runGUITool(fslTool, namespace, toolArgv) - else: _runCLITool(fslTool, namespace, toolArgv) - - -def _runGUITool(fslTool, topLevelArgs, toolArgv): - """Runs the given ``FSLTool``, which is assumed to be a GUI tool. - - :arg fslTool: The ``FSLTool`` to run - see the :func:`_loadFSLTool` - function. - - :arg topLevelArgs: An ``argparse.Namespace`` object containing parsed - top-level arguments. - - :arg toolArgv: Unparsed tool-specific command line arguments. - """ - import wx - - # Create the wx.App object befor fslTool.init, - # in case it does GUI stuff. Also create a dummy - # frame - if we don't create a dummy frame, the - # wx.MainLoop call below will just return - # immediately. - # - # The buildGUI function below will kill the dummy - # frame when it has created the real interface. - app = wx.App() - dummyFrame = wx.Frame(None) - - # Call the tool's init - # function if there is one - if fslTool.init is not None: initVal = fslTool.init() - else: initVal = None - - # We are going do all processing on the - # wx.MainLoop, so the GUI can be shown - # as soon as possible, and because it is - # difficult to force immediate GUI - # refreshes when not running on the main - # loop - this is important for, e.g. - # FSLEyes, which displays status updates - # to the user while it is loading overlays - # and setting up the interface. - # - # To make this work, this buildGUI - # function is called on a separate thread - # (so it is executed after wx.MainLoop - # has been called), but it schedules its - # work to be done on the wx.MainLoop. - def buildGUI(): - def realBuild(): - - # Parse the tool-specific - # command line arguments - toolNamespace = _parseToolArgs(fslTool, toolArgv) - - # Call the tool context function - if fslTool.context is not None: - ctx = fslTool.context(toolNamespace, initVal) - else: - ctx = None - - # Build the GUI - frame = _buildGUI(toolNamespace, fslTool, ctx) - frame.Show() - - # See comment about the - # dummy frame below - dummyFrame.Destroy() - - # Sleep a bit so the main thread (on - # which wx.MainLoop is running) can - # start. - time.sleep(0.01) - - if not topLevelArgs.skipfslcheck: - wx.CallAfter(_fslDirWarning, - None, - fslTool.toolName, - 'FSLDIR' in os.environ) - - wx.CallAfter(realBuild) - - threading.Thread(target=buildGUI).start() - app.MainLoop() - - -def _runCLITool(fslTool, topLevelArgs, toolArgv): - """Runs the given ``FSLTool``, which is assumed to be a command-line (i.e. - non-GUI) tool. - - :arg fslTool: The ``FSLTool`` to run - see the :func:`_loadFSLTool` - function. - - :arg topLevelArgs: An ``argparse.Namespace`` object containing parsed - top-level arguments. - - :arg toolArgv: Unparsed tool-specific command line arguments. - """ - - if fslTool.execute is None: - return - - # Call the tool's init - # function if there is one - if fslTool.init is not None: initVal = fslTool.init() - else: initVal = None - - # Parse the tool-specific - # command line arguments - namespace = _parseToolArgs(fslTool, toolArgv) - - initVal = None - ctx = None - - if fslTool.init is not None: initVal = fslTool.init() - if fslTool.context is not None: ctx = fslTool.context(namespace, - initVal) - - if not topLevelArgs.skipfslcheck: - _fslDirWarning(None, fslTool.toolName, 'FSLDIR' in os.environ) - - fslTool.execute(namespace, ctx) - - -def runTool(toolName, args, **kwargs): - """Runs the tool with the specified name, with the specified arguments, - in a separate process. Returns the process exit code. - - :arg toolName: Name of the FSL tool to run. - - :arg args: Arguments to pass to the FSL tool. - - :arg kwargs: Passed through to the ``subprocess.call`` function. - """ - - args = [toolName] + args - - if log.getEffectiveLevel() == logging.DEBUG: - args = ['-vvv'] + args - - # If we are running from a compiled fsleyes - # executable, we need to prepend command line - # arguments with 'cmd' - see the wrapper script - # created by: - # - # https://git.fmrib.ox.ac.uk/paulmc/fslpy_build/\ - # blob/master/build_osx_app.sh - if getattr(sys, 'frozen', False): - args = [sys.executable, 'cmd'] + args - - # Otherwise we are running - # through a python interpreter - else: - args = [sys.executable, '-c', 'import fsl; fsl.main()'] + args - - log.debug('Executing {}'.format(' '.join(args))) - - return subprocess.call(args, **kwargs) - - -def _getFSLToolNames(): - """Returns the name of every tool in the :mod:`fsl.tools` package. """ - - # Under linux/Pyinstaller 3.1, iter_modules - # doesn't seem to work, even if i include - # the source code in the frozen app directory. - # So i'm hard coding the names of the tool - # modules. - # - # A workaround would be to manually glob the - # fsl/tools/ directory which, if I continue - # to add the source code to the frozen app - # directory, should work for both frozen and - # unfrozen apps. - if getattr(sys, 'frozen', False): - allTools = ['fsleyes', 'render'] - else: - allTools = [mod for _, mod, _ in pkgutil.iter_modules(tools.__path__)] - - return allTools - - -def _loadFSLTool(moduleName): - """Inspects the given module to see if it looks like a valid FSL tool. - If it is not, a :exc:`TypeError` is raised. If it is, a container - object, a ``FSLTool`` is created and returned, containing all of the - elements of the tool. - - The returned ``FSLTool`` instance contains the following attributes: - - ============== ======================================== - ``module`` The tool module. - ``moduleName`` The tool module name. - ``toolName`` The tool name. - ``helpPage`` The tool help page URL. - ``init`` An initialisation function. - ``parseArgs`` A function to parse tool arguments. - ``context`` A function to generate the tool context. - ``interface`` A function to create the tool interface. - ``execute`` A function to run the tool. - ``actions`` A list of tool actions. - ============== ======================================== - - See the :mod:`fsl.tools` documentation for more details. - """ - - module = importlib.import_module('fsl.tools.{}'.format(moduleName)) - - # Each FSL tool module may specify several things - toolName = getattr(module, 'FSL_TOOLNAME', None) - helpPage = getattr(module, 'FSL_HELPPAGE', None) - init = getattr(module, 'FSL_INIT', None) - parseArgs = getattr(module, 'FSL_PARSEARGS', None) - context = getattr(module, 'FSL_CONTEXT', None) - interface = getattr(module, 'FSL_INTERFACE', None) - execute = getattr(module, 'FSL_EXECUTE', None) - actions = getattr(module, 'FSL_ACTIONS', []) - - # But at the very least, must specify a name, and - # either a function which will create an interface, - # or a function which can be called to do some work - if not all((toolName, any((interface, execute)))): - raise TypeError('"{}" does not appear to be a valid FSL tool'.format( - moduleName)) - - # The tool must either provide an interface, - # or do some non-interactive work. - if interface and execute: - raise TypeError('"{}" does not appear to be a valid FSL tool'.format( - moduleName)) - - class FSLTool(object): - pass - - fsltool = FSLTool() - - fsltool.module = module - fsltool.moduleName = moduleName - fsltool.toolName = toolName - fsltool.helpPage = helpPage - fsltool.init = init - fsltool.parseArgs = parseArgs - fsltool.context = context - fsltool.interface = interface - fsltool.execute = execute - fsltool.actions = actions - - return fsltool - - -def _parseTopLevelArgs(argv, allTools): - """Parses top-level command line arguments. This involves parsing arguments - which are shared across all tools. Also identifies the tool to be invoked - and configures logging verbosity. Returns a tuple containing the - following: - - - The ``FSLTool`` instance (see :func:`_loadFSLTool`). - - - The :class:`argparse.Namespace` instance containing parsed arguments. - - - All remaining unparsed command line arguments (to be passed to the - :func:`_parseToolArgs` function). - """ - - epilog = 'Type fslpy help <tool> for program-specific help. ' \ - 'Available programs:\n {}'.format('\n '.join(allTools)) - - parser = argparse.ArgumentParser( - prog='fslpy', - description='Run a FSL program', - epilog=epilog, - formatter_class=argparse.RawDescriptionHelpFormatter) - - parser.add_argument( - '-v', '--verbose', action='count', - help='Verbose output (can be used up to 3 times)') - - parser.add_argument( - '-V', '--version', action='store_true', - help='Print the current fslpy version and exit') - - parser.add_argument( - '-s', '--skipfslcheck', action='store_true', - help='Skip $FSLDIR check/warning') - - parser.add_argument( - '-n', '--noisy', metavar='MODULE', action='append', - help='Make the specified module noisy') - - parser.add_argument( - '-m', '--memory', action='store_true', - help='Output memory events (implied if -v is set)') - - parser.add_argument('tool', help='FSL program to run', nargs='?') - - # No arguments at all? - # I'm not a mind-reader - if len(argv) == 0: - parser.print_help() - sys.exit(1) - - # find the index of the first positional - # argument, i.e. the tool name - i = -1 - while True: - - i = i + 1 - - if i >= len(argv): - break - - if argv[i].startswith('-'): - continue - - if i > 0 and argv[i - 1] in ('-n', '--noisy'): - continue - break - - firstPos = i - - # Separate the top level arguments - # from the tool arguments, and parse - # the top level args - fslArgv = argv[:firstPos + 1] - toolArgv = argv[ firstPos + 1:] - namespace = parser.parse_args(fslArgv) - - # Version number - if namespace.version: - print('fslpy version: {}'.format(__version__)) - sys.exit(0) - - if namespace.noisy is None: - namespace.noisy = [] - - # if the specified tool is 'help', it should be followed by - # one more argument, the name of the tool to print help for. - # - # FSLeyes prints basic usage when passed '-h'/'--help'; - # to get all options, we need to pass '-fh'/'--fullhelp'. - if namespace.tool in ('help', 'fullhelp'): - - # no tool name supplied - if len(toolArgv) == 0: - parser.print_help() - sys.exit(1) - - # unknown tool name supplied - if toolArgv[0] not in allTools: - print('\nUnknown FSL tool: {}\n'.format(toolArgv[0])) - parser.print_help() - sys.exit(1) - - # Only fsleyes/render support full help - if namespace.tool == 'fullhelp' and \ - toolArgv[0] not in ('fsleyes', 'render'): - print('\n{} does not support fullhelp\n'.format(toolArgv[0])) - parser.print_help() - sys.exit(1) - - fslTool = _loadFSLTool(toolArgv[0]) - - # no tool specific argument parser - if fslTool.parseArgs is None: - print('No help for {}'.format(toolArgv[0])) - - # Otherwise, get help from the tool. We assume that - # all the argument parser for every tool will interpret - # '--help' (and fsleyes will interpret '--fullhelp'), - # and will print some help. - else: - fslTool.parseArgs(['--{}'.format(namespace.tool)]) - sys.exit(0) - - # Unknown tool name supplied - elif namespace.tool not in allTools: - print('\nUnknown FSL tool: {}\n'.format(namespace.tool)) - parser.print_help() - sys.exit(1) - - # Configure any logging verbosity - # settings specified by the user - if namespace.verbose is None: - if namespace.memory: - class MemFilter(object): - def filter(self, record): - if record.name in namespace.noisy: return 1 - elif record.levelno == logging.MEMORY: return 1 - else: return 0 - - log.setLevel(logging.MEMORY) - log.handlers[0].addFilter(MemFilter()) - log.memory('Added filter for MEMORY messages') - logging.getLogger('props') .setLevel(logging.WARNING) - logging.getLogger('pwidgets').setLevel(logging.WARNING) - - if namespace.verbose == 1: - log.setLevel(logging.DEBUG) - - # make some noisy things quiet - logging.getLogger('fsl.fsleyes.gl') .setLevel(logging.MEMORY) - logging.getLogger('fsl.fsleyes.views').setLevel(logging.MEMORY) - logging.getLogger('props') .setLevel(logging.WARNING) - logging.getLogger('pwidgets') .setLevel(logging.WARNING) - elif namespace.verbose == 2: - log.setLevel(logging.DEBUG) - logging.getLogger('props') .setLevel(logging.WARNING) - logging.getLogger('pwidgets').setLevel(logging.WARNING) - elif namespace.verbose == 3: - log.setLevel(logging.DEBUG) - logging.getLogger('props') .setLevel(logging.DEBUG) - logging.getLogger('pwidgets').setLevel(logging.DEBUG) - - for mod in namespace.noisy: - logging.getLogger(mod).setLevel(logging.DEBUG) - - # The trace module monkey-patches some - # things if its logging level has been - # set to DEBUG, so we import it now so - # it can set itself up. - traceLogger = logging.getLogger('props.trace') - if traceLogger.getEffectiveLevel() <= logging.DEBUG: - import props.trace - - fslTool = _loadFSLTool(namespace.tool) - - return fslTool, namespace, toolArgv - - -def _parseToolArgs(tool, argv): - """Parses tool-specific command-line arguments. Returns the result of - calling the ``FSL_PARSEARGS`` attribute of the given tool, or ``None`` - if the tool does not have the function. - - :arg tool: The ``FSLTool`` to be invoked. - :arg argv: Command line arguments to be parsed. - """ - - if tool.parseArgs is not None: toolNamespace = tool.parseArgs(argv) - else: toolNamespace = None - - return toolNamespace - - -def _fslDirWarning(parent, toolName, fslEnvActive): - """If ``fslEnvActive`` is False, displays a warning that the ``$FSLDIR`` - environment variable is not set. The warning is displayed either on - stdout, or via a GUI dialog (if ``wx`` is available). - - :arg parent: A ``wx`` parent for the :class:`.FSLDirDialog`, if it - is displayed. - - :arg toolName: Name of the tool that is being executed. - - :arg fslEnvActive: Set to ``True`` if ``$FSLDIR`` is set, ``False`` - otherwise. - """ - - if fslEnvActive: return - - haveGui = False - try: - import wx - if wx.GetApp() is not None: - haveGui = True - except: - pass - - warnmsg = 'The FSLDIR environment variable is not set - '\ - '{} may not behave correctly.'.format(toolName) - - # Check fslpy settings before - # prompting the user - fsldir = fslsettings.read('fsldir') - - if fsldir is not None: - os.environ['FSLDIR'] = fsldir - fslplatform.platform.fsldir = fsldir - return - - if haveGui: - import wx - from fsl.utils.dialog import FSLDirDialog - - def warn(): - dlg = FSLDirDialog(parent, toolName) - - if dlg.ShowModal() == wx.ID_OK: - fsldir = dlg.GetFSLDir() - log.debug('Setting $FSLDIR to {} (specified ' - 'by user)'.format(fsldir)) - - fslplatform.platform.fsldir = fsldir - os.environ[ 'FSLDIR'] = fsldir - fslsettings.write( 'fsldir', fsldir) - - wx.CallLater(500, warn) - - else: - log.warn(warnmsg) - - -def _buildGUI(args, fslTool, toolCtx): - """Builds a :mod:`wx` GUI for the tool. - - :arg fslTool: The ``FSLTool`` instance (see :func:`_loadFSLTool`). - - :arg toolCtx: The tool context, as returned by the - ``FSLTool.context`` function. - """ - - import wx - import fsl.utils.webpage as webpage - - frame = fslTool.interface(None, args, toolCtx) - - menuBar = frame.GetMenuBar() - - if menuBar is None: - menuBar = wx.MenuBar() - frame.SetMenuBar(menuBar) - - fslMenu = wx.Menu() - menuBar.Insert(0, fslMenu, 'FSL') - - actions = [] - - def help(ev): - if fslTool.helpPage is not None: - webpage.openPage(fslTool.helpPage) - else: - webpage.openLocalHelp(fslTool.toolName) - - actions.append(( - wx.ID_HELP, - '{} Help'.format(fslTool.toolName), - help)) - - for (name, func) in fslTool.actions: - actions.append((wx.ID_ANY, name, lambda ev, f=func: f(frame, toolCtx))) - - actions.append(( - wx.ID_EXIT, - 'Exit', - lambda *ev: frame.Close())) - - for wxId, name, func in actions: - menuItem = fslMenu.Append(wxId, name) - frame.Bind(wx.EVT_MENU, func, menuItem) - - return frame - - -################################# -# My own custom logging level for -# tracing memory related events -################################# - -logging.MEMORY = 15 -def _logmemory(self, message, *args, **kwargs): - """Log function for my custom ``logging.MEMORY`` logging level. """ - if self.isEnabledFor(logging.MEMORY): - self._log(logging.MEMORY, message, args, **kwargs) - -logging.Logger.memory = _logmemory -logging.addLevelName(logging.MEMORY, 'MEMORY') - - - -######################### -# Fix some logging quirks -######################### - - -# make numpy/matplotlib quiet -warnings.filterwarnings('ignore', module='matplotlib') -warnings.filterwarnings('ignore', module='mpl_toolkits') -warnings.filterwarnings('ignore', module='numpy') - - -# There's a bug in OpenGL.GL.shaders (PyOpenGL 3.1.0. - it's been -# fixed in the development version) - it calls logging.basicConfig(), -# and thus screws up our own logging. We overcome this by configuring -# the root logger before OpenGL.GL.shaders is imported (which -# occurs when fsl.fsleyes.gl.slicecanvas.SliceCanvas is imported). - -logFormatter = logging.Formatter('%(levelname)8.8s ' - '%(filename)20.20s ' - '%(lineno)4d: ' - '%(funcName)-15.15s - ' - '%(message)s') -logHandler = logging.StreamHandler() -logHandler.setFormatter(logFormatter) - -# We want the root logger -log = logging.getLogger() - -log.addHandler(logHandler) diff --git a/fsl/data/constants.py b/fsl/data/constants.py index 52a2f8200..127ec638e 100644 --- a/fsl/data/constants.py +++ b/fsl/data/constants.py @@ -4,7 +4,7 @@ # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # -"""This module defines some constant values used throughout ``fsleyes``. +"""This module defines some constant values used throughout ``fslpy``. The following constants relate to the orientation of an axis, in either diff --git a/fsl/data/image.py b/fsl/data/image.py index 6887806c0..cc8ab09f8 100644 --- a/fsl/data/image.py +++ b/fsl/data/image.py @@ -392,14 +392,6 @@ class Image(Nifti1, props.HasProperties): else: self.name = 'Nibabel image' - log.memory('{}.init ({})'.format(type(self).__name__, id(self))) - - - def __del__(self): - """Prints a log message. """ - if log: - log.memory('{}.del ({})'.format(type(self).__name__, id(self))) - def __hash__(self): """Returns a number which uniquely idenfities this ``Image`` instance diff --git a/fsl/data/model.py b/fsl/data/model.py index dcbc29b97..3def2dff3 100644 --- a/fsl/data/model.py +++ b/fsl/data/model.py @@ -66,14 +66,6 @@ class Model(object): self.__loBounds = self.vertices.min(axis=0) self.__hiBounds = self.vertices.max(axis=0) - log.memory('{}.init ({})'.format(type(self).__name__, id(self))) - - - def __del__(self): - """Prints a log message.""" - if log: - log.memory('{}.del ({})'.format(type(self).__name__, id(self))) - def __repr__(self): """Rewturns a string representation of this ``Model`` instance. """ diff --git a/fsl/data/tensorimage.py b/fsl/data/tensorimage.py index 0bc0627ac..4d194b480 100644 --- a/fsl/data/tensorimage.py +++ b/fsl/data/tensorimage.py @@ -137,13 +137,6 @@ class TensorImage(fslimage.Nifti1): self.dataSource = l1dir self.name = '{}[tensor]'.format(op.basename(l1dir)) - log.memory('{}.init({})'.format(type(self).__name__, id(self))) - - - def __del__(self): - if log: - log.memory('{}.del({})'.format(type(self).__name__, id(self))) - def V1(self): return self.__v1 def V2(self): return self.__v2 diff --git a/fsl/utils/layout.py b/fsl/utils/layout.py index 146134600..00897df13 100644 --- a/fsl/utils/layout.py +++ b/fsl/utils/layout.py @@ -38,11 +38,8 @@ A few functions are also provided for calculating the display size, in pixels, of one or more canvases which are displaying a defined coordinate system. The canvas sizes are calculated so that their aspect ratio, relative to the respective horizontal/vertical display axes, are maintained, and that the -canvases are sized proportionally with respect to each other. These functions -are used both by :mod:`.render`, and also by the :class:`.OrthoPanel` and -:class:`.LightBoxPanel`, for calculating canvas sizes when they are displayed -in :mod:`~.tools.fsleyes`. The following size calculation functions are -available: +canvases are sized proportionally with respect to each other. The following +size calculation functions are available: .. autosummary:: :nosignatures: diff --git a/fsl/utils/platform.py b/fsl/utils/platform.py index b35c5a1a7..2510f8830 100644 --- a/fsl/utils/platform.py +++ b/fsl/utils/platform.py @@ -62,9 +62,8 @@ class Platform(notifier.Notifier): .. note:: The values of the :attr:`glVersion` and :attr:`glRenderer` properties are not automatically set - they will only contain - a value if one is assigned to them. The - :func:`fsl.fsleyes.gl.bootstrap` function does this when it is - first called. + a value if one is assigned to them. *FSLeyes* does this during + startup, in the :func:`fsleyes.gl.bootstrap` function. .. autosummary:: -- GitLab