diff --git a/doc/fsl.utils.colourbarbitmap.rst b/doc/fsl.utils.colourbarbitmap.rst deleted file mode 100644 index 0b1f2ab90806d31e73a33e55ea9e0312b1085300..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.colourbarbitmap.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.colourbarbitmap module -================================ - -.. automodule:: fsl.utils.colourbarbitmap - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/fsl.utils.dialog.rst b/doc/fsl.utils.dialog.rst deleted file mode 100644 index f303a8ba51502decea3e7b6cb504a46e1362bea3..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.dialog.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.dialog module -======================= - -.. automodule:: fsl.utils.dialog - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/fsl.utils.imagepanel.rst b/doc/fsl.utils.imagepanel.rst deleted file mode 100644 index 8816d5f4456d575ccc38033ffae2ac59e29aa09e..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.imagepanel.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.imagepanel module -=========================== - -.. automodule:: fsl.utils.imagepanel - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/fsl.utils.layout.rst b/doc/fsl.utils.layout.rst deleted file mode 100644 index 334875be2f483915e1062f8ebd39b367ac675f39..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.layout.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.layout module -======================= - -.. automodule:: fsl.utils.layout - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/fsl.utils.runwindow.rst b/doc/fsl.utils.runwindow.rst deleted file mode 100644 index b99c474f04750e38298d7c568805d87b8492d47d..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.runwindow.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.runwindow module -========================== - -.. automodule:: fsl.utils.runwindow - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/fsl.utils.status.rst b/doc/fsl.utils.status.rst deleted file mode 100644 index 2ab0edfd96dc4af3772c05a2f462214abc51ecad..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.status.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.status module -======================= - -.. automodule:: fsl.utils.status - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/fsl.utils.textbitmap.rst b/doc/fsl.utils.textbitmap.rst deleted file mode 100644 index 47d11d0b3262754a895539b9c021966295a9bd0b..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.textbitmap.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.textbitmap module -=========================== - -.. automodule:: fsl.utils.textbitmap - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/fsl.utils.typedict.rst b/doc/fsl.utils.typedict.rst deleted file mode 100644 index f5359c8c6bb372e220e3efd4362b5103fa6774d3..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.typedict.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.typedict module -========================= - -.. automodule:: fsl.utils.typedict - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/fsl.utils.webpage.rst b/doc/fsl.utils.webpage.rst deleted file mode 100644 index ffbc0ddb1b3bf23b229faf7c47c20d5e08acf794..0000000000000000000000000000000000000000 --- a/doc/fsl.utils.webpage.rst +++ /dev/null @@ -1,9 +0,0 @@ -:orphan: - -fsl.utils.webpage module -======================== - -.. automodule:: fsl.utils.webpage - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/images/colourbarbitmap.png b/doc/images/colourbarbitmap.png deleted file mode 100644 index a94c44e75d587321f1c585f429bf491727e17934..0000000000000000000000000000000000000000 Binary files a/doc/images/colourbarbitmap.png and /dev/null differ diff --git a/doc/images/fsldirdialog.png b/doc/images/fsldirdialog.png deleted file mode 100644 index 4641d8b30fa34feec9d45eb1a41a5fc8731c52c2..0000000000000000000000000000000000000000 Binary files a/doc/images/fsldirdialog.png and /dev/null differ diff --git a/doc/images/simplemessagedialog.png b/doc/images/simplemessagedialog.png deleted file mode 100644 index 01771580c87137b9ffa16434aa2c9ced59bd7c73..0000000000000000000000000000000000000000 Binary files a/doc/images/simplemessagedialog.png and /dev/null differ diff --git a/doc/images/texteditdialog.png b/doc/images/texteditdialog.png deleted file mode 100644 index e2001ae7f2d53abacea0d485bfeb30f3c90df221..0000000000000000000000000000000000000000 Binary files a/doc/images/texteditdialog.png and /dev/null differ diff --git a/fsl/utils/colourbarbitmap.py b/fsl/utils/colourbarbitmap.py deleted file mode 100644 index 76fb9b7a32b7b4a072c264d479310ab8ba6f907a..0000000000000000000000000000000000000000 --- a/fsl/utils/colourbarbitmap.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python -# -# colourbarbitmap.py - A function which renders a colour bar using -# matplotlib as an RGBA bitmap. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This module provides a single function, :func:`colourBarBitmap`, which uses -:mod:`matplotlib` to plot a colour bar. The colour bar is rendered off-screen -and returned as an RGBA bitmap. -""" - - -def colourBarBitmap(cmap, - width, - height, - cmapResolution=256, - negCmap=None, - invert=False, - ticks=None, - ticklabels=None, - tickalign=None, - label=None, - orientation='vertical', - labelside='top', - alpha=1.0, - fontsize=10, - bgColour=None, - textColour='#ffffff'): - """Plots a colour bar using :mod:`matplotlib`. - - - The rendered colour bar is returned as a RGBA bitmap within a - ``numpy.uint8`` array of size :math:`w \\times h \\times 4`, with the - top-left pixel located at index ``[0, 0, :]``. - - - A rendered colour bar will look something like this: - - .. image:: images/colourbarbitmap.png - :scale: 50% - :align: center - - - :arg cmap: Name of a registered :mod:`matplotlib` colour map. - - :arg width: Colour bar width in pixels. - - :arg height: Colour bar height in pixels. - - :arg negCmap: If provided, two colour maps are drawn, centered at 0. - - :arg invert: If ``True``, the colour map is inverted. - - :arg ticks: Locations of tick labels. - - :arg ticklabels: Tick labels. - - :arg tickalign: Tick alignment (one for each tick, either ``'left'`` or - ``'right'``). - - :arg label: Text label to show next to the colour bar. - - :arg orientation: Either ``vertical`` or ``horizontal``. - - :arg labelside: If ``orientation`` is ``vertical`` ``labelSide`` may - be either ``left`` or ``right``. Otherwise, if - ``orientation`` is ``horizontal``, ``labelSide`` may - be ``top`` or ``bottom``. - - :arg alpha: Colour bar transparency, in the range ``[0.0 - 1.0]``. - - :arg fontsize: Label font size in points. - - :arg bgColour: Background colour - can be any colour specification - that is accepted by :mod:`matplotlib`. - - :arg textColour: Label colour - can be any colour specification that - is accepted by :mod:`matplotlib`. - """ - - # These imports are expensive, so we're - # importing at the function level. - import numpy as np - import matplotlib.backends.backend_agg as mplagg - import matplotlib.figure as mplfig - - if orientation not in ['vertical', 'horizontal']: - raise ValueError('orientation must be vertical or horizontal') - - if orientation == 'horizontal': - if labelside not in ['top', 'bottom']: - raise ValueError('labelside must be top or bottom') - else: - if labelside not in ['left', 'right']: - raise ValueError('labelside must be left or right') - - # vertical plots are rendered horizontally, - # and then simply rotated at the end - if orientation == 'vertical': - width, height = height, width - if labelside == 'left': labelside = 'top' - else: labelside = 'bottom' - - dpi = 96.0 - ncols = cmapResolution - data = genColours(cmap, ncols, invert, alpha) - - if negCmap is not None: - ndata = genColours(negCmap, ncols, not invert, alpha) - data = np.concatenate((ndata, data), axis=1) - ncols *= 2 - - fig = mplfig.Figure(figsize=(width / dpi, height / dpi), dpi=dpi) - canvas = mplagg.FigureCanvasAgg(fig) - ax = fig.add_subplot(111) - - if bgColour is not None: - fig.patch.set_facecolor(bgColour) - else: - fig.patch.set_alpha(0) - - # draw the colour bar - ax.imshow(data, - aspect='auto', - origin='lower', - interpolation='nearest') - - ax.set_xlim((0, ncols - 1)) - - ax.set_yticks([]) - ax.tick_params(colors=textColour, labelsize=fontsize, length=0) - - if labelside == 'top': - ax.xaxis.tick_top() - ax.xaxis.set_label_position('top') - va = 'top' - else: - ax.xaxis.tick_bottom() - ax.xaxis.set_label_position('bottom') - va = 'bottom' - - if label is not None: - ax.set_xlabel(label, - fontsize=fontsize, - color=textColour, - va=va) - label = ax.xaxis.get_label() - - if ticks is None or ticklabels is None: - ax.set_xticks([]) - else: - - ax.set_xticks(np.array(ticks) * ncols) - ax.set_xticklabels(ticklabels) - ticklabels = ax.xaxis.get_ticklabels() - - try: - fig.tight_layout() - except: - pass - - # Adjust the x label after tight_layout, - # otherwise it will overlap with the tick - # labels. I don't understand why, but I - # have to set va to the opposite of what - # I would have thought. - if label is not None and ticklabels is not None: - if labelside == 'top': - label.set_va('bottom') - label.set_position((0.5, 0.97)) - else: - label.set_va('top') - label.set_position((0.5, 0.03)) - - # This must be done *after* calling - # tick_top/tick_bottom, as I think - # the label bjects get recreated. - if ticklabels is not None and tickalign is not None: - for l, a in zip(ticklabels, tickalign): - l.set_horizontalalignment(a) - - canvas.draw() - - buf = canvas.tostring_argb() - ncols, nrows = canvas.get_width_height() - - bitmap = np.fromstring(buf, dtype=np.uint8) - bitmap = bitmap.reshape(nrows, ncols, 4).transpose([1, 0, 2]) - - # the bitmap is in argb order, - # but we want it in rgba - rgb = bitmap[:, :, 1:] - a = bitmap[:, :, 0] - bitmap = np.dstack((rgb, a)) - - if orientation == 'vertical': - bitmap = np.flipud(bitmap.transpose([1, 0, 2])) - bitmap = np.rot90(bitmap, 2) - - return bitmap - - -def genColours(cmap, cmapResolution, invert, alpha): - """Generate an array containing ``cmapResolution`` colours from the given - colour map object/function. - """ - - import numpy as np - import matplotlib.cm as cm - - ncols = cmapResolution - cmap = cm.get_cmap(cmap) - data = np.linspace(0.0, 1.0, ncols) - - if invert: - data = data[::-1] - - data = np.repeat(data.reshape(ncols, 1), 2, axis=1) - data = data.transpose() - data = cmap(data) - data[:, :, 3] = alpha - - return data diff --git a/fsl/utils/dialog.py b/fsl/utils/dialog.py deleted file mode 100644 index 429531dc2a225812485287dca0ef3f33bd5bc1e9..0000000000000000000000000000000000000000 --- a/fsl/utils/dialog.py +++ /dev/null @@ -1,962 +0,0 @@ -#!/usr/bin/env python -# -# dialog.py - Miscellaneous dialogs. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This module contains a collection of basic dialog classes, available for -use throughout ``fslpy``. The available dialogs are: - -.. autosummary:: - :nosignatures: - - SimpleMessageDialog - TimeoutDialog - ProcessingDialog - TextEditDialog - FSLDirDialog -""" - - -import os -import os.path as op -import threading - -import six -import wx - -from .platform import platform as fslplatform - - -class SimpleMessageDialog(wx.Dialog): - """A simple, no-frills :class:`wx.Dialog` for displaying a message. The - message can be updated via the :meth:`SetMessage` method. As a simple - usage example:: - - import fsl.utils.dialog as fsldlg - dlg = fsldlg.SimpleMessageDialog(message='Loading data ...') - - dlg.Show() - - # load the data, like - # you said you would - - # Data is loaded, so we - # can kill the dialog - dlg.Close() - dlg.Destroy() - - - The ``SimpleMessageDialog`` class supports the following styles: - - .. autosummary:: - SMD_KEEP_CENTERED - - - a ``SimpleMessageDialog`` looks something like this: - - .. image:: images/simplemessagedialog.png - :scale: 50% - :align: center - """ - - - def __init__(self, parent=None, message='', style=None): - """Create a ``SimpleMessageDialog``. - - :arg parent: The :mod:`wx` parent object. - - :arg message: The initial message to show. - - :arg style: Only one style flag is supported, - :data:`SMD_KEEP_CENTERED`. This flag is enabled by - default. - """ - - - if style is None: - style = SMD_KEEP_CENTERED - - if parent is None: - parent = wx.GetApp().GetTopWindow() - - wx.Dialog.__init__(self, - parent, - style=wx.STAY_ON_TOP | wx.FULL_REPAINT_ON_RESIZE) - - - self.__style = style - - self.__message = wx.StaticText( - self, - style=(wx.ST_ELLIPSIZE_MIDDLE | - wx.ALIGN_CENTRE_HORIZONTAL | - wx.ALIGN_CENTRE_VERTICAL)) - - self.__sizer = wx.BoxSizer(wx.HORIZONTAL) - self.__sizer.Add(self.__message, - border=25, - proportion=1, - flag=wx.EXPAND | wx.CENTRE | wx.ALL) - - self.SetTransparent(240) - self.SetBackgroundColour((225, 225, 255)) - - self.SetSizer(self.__sizer) - - self.SetMessage(message) - - - def Show(self): - """Overrides ``wx.Dialog.Show``. Calls that method, and calls - ``wx.Yield``. - """ - wx.Dialog.Show(self) - wx.Yield() - - - def SetMessage(self, msg): - """Updates the message shown on this ``SimpleMessageDialog``. - - If the :data:`SMD_KEEP_CENTERED` style is set, the dialog is - re-centered on its parent, to account for changes in the message width. - """ - - msg = str(msg) - - self.__message.SetLabel(msg) - - # Figure out the dialog size - # required to fit the message - dc = wx.ClientDC(self.__message) - - width, height = dc.GetTextExtent(msg) - - # +50 to account for sizer borders (see __init__), - # plus a bit more for good measure. In particular, - # under GTK, the message seems to be vertically - # truncated if we don't add some extra padding - width += 60 - height += 70 - - self.SetMinClientSize((width, height)) - self.SetClientSize(( width, height)) - - self.Layout() - self.__message.Layout() - - if self.__style & SMD_KEEP_CENTERED: - self.CentreOnParent() - - # This ridiculousness seems to be - # necessary to force a repaint on - # all platforms (OSX, GTK, GTK/SSH) - wx.Yield() - self.Refresh() - self.Update() - self.__message.Refresh() - self.__message.Update() - wx.Yield() - - -# SimpleMessageDialog style flags -SMD_KEEP_CENTERED = 1 -"""If set, the dialog will be re-centred on its parent whenever its message -changes. -""" - - -class TimeoutDialog(SimpleMessageDialog): - """A :class:`SimpleMessageDialog` which automatically destroys itself - after a specified timeout period. - - .. note:: The timeout functionality will not work if you show the dialog - by any means other than the :meth:`wx.Dialog.Show` or - :meth:`wx.Dialog.ShowModal` methods ... but is there any other - way of showing a :class:`wx.Dialog`? - """ - - - def __init__(self, parent, message, timeout=1000, **kwargs): - """Create a ``TimeoutDialog``. - - :arg parent: The :mod:`wx` parent object. - - :arg message: The initial message to display. - - :arg timeout: Timeout period in milliseconds. - - :arg kwargs: Passed through to :meth:`SimpleMessageDialog.__init__`. - """ - - SimpleMessageDialog.__init__(self, parent, message, **kwargs) - self.__timeout = timeout - - - def __close(self): - """Closes and destroys this ``TimeoutDialog``. """ - self.Close() - self.Destroy() - - - def Show(self): - """Shows this ``TimeoutDialog``, and sets up a callback to - close it after the specified ``timeout``. - """ - wx.CallLater(self.__timeout, self.__close) - SimpleMessageDialog.Show(self) - - - def ShowModal(self): - """Shows this ``TimeoutDialog``, and sets up a callback to - close it after the specified ``timeout``. - """ - wx.CallLater(self.__timeout, self.__close) - SimpleMessageDialog.ShowModal(self) - - -class ProcessingDialog(SimpleMessageDialog): - """A :class:`SimpleMessageDialog` which displays a message and runs a - task in the background. User interaction is blocked while the task runs, - and the dialog closes and destroys itself automatically on task - completion. - - - The task is simply passed in as a function. If the task supports it, - the ``ProcessingDialog`` will pass it two message-updating functions, - which can be used by the task to update the message being displayed. - This functionality is controlled by the ``passFuncs``, ``messageFunc`` - and ``errorFunc`` parameters to :meth:`__init__`. - - - A ``ProcessingDialog`` must be displayed via the :meth:`Run` method, - *not* with the :meth:`wx.Dialog.Show` or :meth:`wx.Dialog.ShowModal` - methods. - """ - - def __init__(self, parent, message, task, *args, **kwargs): - """Create a ``ProcessingDialog``. - - :arg parent: The :mod:`wx` parent object. - - :arg message: Initial message to display. - - :arg task: The function to run. - - :arg args: Positional arguments passed to the ``task`` - function. - - :arg kwargs: Keyword arguments passed to the ``task`` function. - - - Some special keyword arguments are also accepted: - - =============== ================================================= - Name Description - =============== ================================================= - ``passFuncs`` If ``True``, two extra keyword arguments are - passed to the ``task`` function - ``messageFunc`` - and ``errorFunc``. - - ``messageFunc`` is a function which accepts a - single string as its argument; when it is called, - the dialog message is updated to display the - string. - - ``errorFunc`` is a function which accepts two - arguemnts - a message string and an - :exc:`Exception` instance. If the task detects - an error, it may call this function. A new - dialog is shown, containing the details of the - error, to inform the user. - ``messageFunc`` Overrides the default ``messageFunc`` described - above. - ``errorFunc`` Overrides the default ``errorFunc`` described - above. - =============== ================================================= - """ - - passFuncs = kwargs.get('passFuncs', False) - - if not passFuncs: - kwargs.pop('messageFunc', None) - kwargs.pop('errorFunc', None) - else: - kwargs['messageFunc'] = kwargs.get('messageFunc', - self.__defaultMessageFunc) - kwargs['errortFunc'] = kwargs.get('errorFunc', - self.__defaultErrorFunc) - - self.task = task - self.args = args - self.kwargs = kwargs - self.message = message - - style = kwargs.pop('style', None) - - SimpleMessageDialog.__init__(self, parent, style=style) - - - def Run(self, mainThread=False): - """Shows this ``ProcessingDialog``, and runs the ``task`` function - passed to :meth:`__init__`. When the task completes, this dialog - is closed and destroyed. - - :arg mainThread: If ``True`` the task is run in the current thread. - Otherwise, the default behaviour is to run the - task in a separate thread. - - :returns: the return value of the ``task`` function. - - .. note:: If ``mainThread=True``, the task should call - :func:`wx.Yield` periodically - under GTK, there is a - chance that this ``ProcessingDialog`` will not get drawn - before the task begins. - """ - - self.SetMessage(self.message) - wx.Dialog.Show(self) - self.SetFocus() - - self.Refresh() - self.Update() - wx.Yield() - - if mainThread: - try: - result = self.task(*self.args, **self.kwargs) - except: - self.Close() - self.Destroy() - raise - else: - returnVal = [None] - - def wrappedTask(): - returnVal[0] = self.task(*self.args, **self.kwargs) - - thread = threading.Thread(target=wrappedTask) - thread.start() - - while thread.isAlive(): - thread.join(0.2) - wx.Yield() - - result = returnVal[0] - - self.Close() - self.Destroy() - - return result - - - def Show(self): - """Raises a :exc:`NotImplementedError`.""" - raise NotImplementedError('Use the Run method') - - - def ShowModal(self): - """Raises a :exc:`NotImplementedError`.""" - raise NotImplementedError('Use the Run method') - - - def __defaultMessageFunc(self, msg): - """Default ``messageFunc``. Updates the message which is displayed - on this ``ProcessingDialog``. See :meth:`SetMessage`. - """ - self.SetMessage(msg) - - - def __defaultErrorFunc(self, msg, err): - """Default ``errorFunc``. Opens a new dialog (a :class:`wx.MessageBox`) - which contains a description of the error. - """ - err = str(err) - msg = 'An error hass occurred: {}\n\nDetails: {}'.format(msg, err) - title = 'Error' - wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK) - - -class TextEditDialog(wx.Dialog): - """A dialog which shows an editable/selectable text field. - - - ``TextEditDialog`` supports the following styles: - - .. autosummary:: - TED_READONLY - TED_MULTILINE - TED_OK - TED_CANCEL - TED_OK_CANCEL - TED_COPY - TED_COPY_MESSAGE - - A ``TextEditDialog`` looks something like this: - - .. image:: images/texteditdialog.png - :scale: 50% - :align: center - """ - - def __init__(self, - parent, - title='', - message='', - text='', - icon=None, - style=None): - """Create a ``TextEditDialog``. - - :arg parent: The :mod:`wx` parent object. - - :arg title: Dialog title. - - :arg message: Dialog message. - - :arg text: String to display in the text field. - - :arg icon: A :mod:`wx` icon identifier, such as - :data:`wx.ICON_INFORMATION` or :data:`wx.ICON_WARNING`. - - :arg style: A combination of :data:`TED_READONLY`, - :data:`TED_MULTILINE`, :data:`TED_OK`, - :data:`TED_CANCEL`, :data:`TED_OK_CANCEL`, - :data:`TED_COPY` and :data:`TED_COPY_MESSAGE` . Defaults - to :data:`TED_OK`. - """ - - if style is None: - style = TED_OK - - wx.Dialog.__init__(self, - parent, - title=title, - style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) - - textStyle = 0 - if style & TED_READONLY: textStyle |= wx.TE_READONLY - if style & TED_MULTILINE: textStyle |= wx.TE_MULTILINE - - self.__message = wx.StaticText(self) - self.__textEdit = wx.TextCtrl( self, style=textStyle) - - self.__message .SetLabel(message) - self.__textEdit.SetValue(text) - - self.__showCopyMessage = style & TED_COPY_MESSAGE - - # set the min size of the text - # ctrl so it can fit a few lines - self.__textEdit.SetMinSize((-1, 120)) - self.__textEdit.SetMaxSize((600, -1)) - - self.__ok = (-1, -1) - self.__copy = (-1, -1) - self.__cancel = (-1, -1) - self.__icon = (-1, -1) - self.__buttons = [] - - if icon is not None: - - icon = wx.ArtProvider.GetMessageBoxIcon(icon) - - if fslplatform.wxFlavour == fslplatform.WX_PHOENIX: - bmp = wx.Bitmap() - else: - bmp = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight()) - - bmp.CopyFromIcon(icon) - self.__icon = wx.StaticBitmap(self) - self.__icon.SetBitmap(bmp) - - if style & TED_OK: - self.__ok = wx.Button(self, id=wx.ID_OK) - self.__ok.Bind(wx.EVT_BUTTON, self.__onOk) - self.__buttons.append(self.__ok) - - if style & TED_CANCEL: - self.__cancel = wx.Button(self, id=wx.ID_CANCEL) - self.__cancel.Bind(wx.EVT_BUTTON, self.__onCancel) - self.__buttons.append(self.__cancel) - - if style & TED_COPY: - self.__copy = wx.Button(self, label='Copy to clipboard') - self.__copy.Bind(wx.EVT_BUTTON, self.__onCopy) - self.__buttons.append(self.__copy) - - self.__textEdit.Bind(wx.EVT_CHAR_HOOK, self.__onCharHook) - - textSizer = wx.BoxSizer(wx.VERTICAL) - iconSizer = wx.BoxSizer(wx.HORIZONTAL) - btnSizer = wx.BoxSizer(wx.HORIZONTAL) - mainSizer = wx.BoxSizer(wx.VERTICAL) - - textSizer.Add(self.__message, - flag=wx.ALL | wx.CENTRE, - border=20) - textSizer.Add(self.__textEdit, - flag=wx.ALL | wx.EXPAND, - border=20, - proportion=1) - - iconSizer.Add(self.__icon, flag=wx.ALL | wx.CENTRE, border=20) - iconSizer.Add(textSizer, flag=wx.EXPAND, proportion=1) - - btnSizer.AddStretchSpacer() - btnSizer.Add(self.__ok, - flag=wx.ALL | wx.CENTRE, - border=10) - btnSizer.Add(self.__copy, - flag=wx.ALL | wx.CENTRE, - border=10) - btnSizer.Add(self.__cancel, - flag=wx.ALL | wx.CENTRE, - border=10) - btnSizer.Add((-1, 20)) - - mainSizer.Add(iconSizer, flag=wx.EXPAND, proportion=1) - mainSizer.Add(btnSizer, flag=wx.EXPAND) - - self.SetSizer(mainSizer) - self.Fit() - - - def __onCharHook(self, ev): - """Called on ``EVT_CHAR_HOOK`` events generated by the ``TextCtrl``. - Implements tab-navigation, and makes the enter key behave as if - the user had clicked the OK button. - """ - - key = ev.GetKeyCode() - - if key not in (wx.WXK_TAB, wx.WXK_RETURN): - ev.Skip() - return - - # Dodgy, but I've had loads of trouble - # under OSX - Navigate/HandleAsNavigationKey - # do not work. - if key == wx.WXK_TAB and len(self.__buttons) > 0: - self.__buttons[0].SetFocus() - - elif key == wx.WXK_RETURN: - self.__onOk(None) - - - def __onOk(self, ev): - """Called when the *Ok* button is pressed. Ends the dialog. """ - self.EndModal(wx.ID_OK) - - - def __onCancel(self, ev): - """Called when the *Cancel* button is pressed. Ends the dialog. """ - self.EndModal(wx.ID_CANCEL) - - - def __onCopy(self, ev): - """Called when the *Copy* button is pressed. Copies the text - to the system clipboard, and pops up a :class:`TimeoutDialog` - informing the user. - """ - text = self.__textEdit.GetValue() - - cb = wx.TheClipboard - - if cb.Open(): - cb.SetData(wx.TextDataObject(text)) - cb.Close() - - if self.__showCopyMessage: - td = TimeoutDialog(self, 'Copied!', 1000) - td.Show() - - - def SetMessage(self, message): - """Set the message displayed on the dialog.""" - self.__message.SetLabel(message) - - - def SetOkLabel(self, label): - """Set the label to show on the *Ok* button.""" - self.__ok.SetLabel(label) - - - def SetCopyLabel(self, label): - """Sets the label to show on the *Copy* button.""" - self.__copy.SetLabel(label) - - - def SetCancelLabel(self, label): - """Sets the label to show on the *Cancel* button.""" - self.__cancel.SetLabel(label) - - - def SetText(self, text): - """Sets the text to show in the text field.""" - self.__textEdit.SetValue(text) - - - def GetText(self): - """Returns the text shown in the text field.""" - return self.__textEdit.GetValue() - - -# TextEditDialog style flags - - -TED_READONLY = 1 -"""If set, the user will not be able to change the text field contents.""" - - -TED_MULTILINE = 2 -"""If set, the text field will span multiple lines. """ - - -TED_OK = 4 -"""If set, an *Ok* button will be shown. """ - - -TED_CANCEL = 8 -"""If set, a *Cancel* button will be shown. """ - - -TED_OK_CANCEL = 12 -"""If set, *Ok* and *Cancel* buttons will be shown. Equivalent to -``TED_OK | TED_CANCEL``. -""" - - -TED_COPY = 16 -"""If set, a *Copy* button will be shown, allowing the use to copy -the text to the system clipboard. -""" - - -TED_COPY_MESSAGE = 32 -"""If set, and if :attr:`TED_COPY` is also set, when the user chooses -to copy the text to the system clipboard, a popup message is displayed. -""" - - -class FSLDirDialog(wx.Dialog): - """A dialog which warns the user that the ``$FSLDIR`` environment - variable is not set, and prompts them to identify the FSL - installation directory. - - If the user selects a directory, the :meth:`getFSLDir` method can be - called to retrieve their selection after the dialog has been closed. - - A ``FSLDirDialog`` looks something like this: - - .. image:: images/fsldirdialog.png - :scale: 50% - :align: center - """ - - def __init__(self, parent, toolName): - """Create a ``FSLDirDialog``. - - :arg parent: The :mod:`wx` parent object. - - :arg toolName: The name of the tool which is running. - """ - - wx.Dialog.__init__(self, parent, title='$FSLDIR is not set') - - self.__fsldir = None - self.__icon = wx.StaticBitmap(self) - self.__message = wx.StaticText( self) - self.__locate = wx.Button( self, id=wx.ID_OK) - self.__skip = wx.Button( self, id=wx.ID_CANCEL) - - icon = wx.ArtProvider.GetMessageBoxIcon(wx.ICON_EXCLAMATION) - - if fslplatform.wxFlavour == fslplatform.WX_PYTHON: - bmp = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight()) - else: - bmp = wx.Bitmap() - - bmp.CopyFromIcon(icon) - - self.__icon.SetBitmap(bmp) - self.__message.SetLabel( - 'The $FSLDIR environment variable is not set - {} ' - 'may not behave correctly.'.format(toolName)) - self.__locate .SetLabel('Locate $FSLDIR') - self.__skip .SetLabel('Skip') - - self.__skip .Bind(wx.EVT_BUTTON, self.__onSkip) - self.__locate.Bind(wx.EVT_BUTTON, self.__onLocate) - - self.__mainSizer = wx.BoxSizer(wx.HORIZONTAL) - self.__contentSizer = wx.BoxSizer(wx.VERTICAL) - self.__buttonSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.__buttonSizer.Add((1, 1), flag=wx.EXPAND, proportion=1) - self.__buttonSizer.Add(self.__locate) - self.__buttonSizer.Add((20, 1)) - self.__buttonSizer.Add(self.__skip) - - self.__contentSizer.Add(self.__message, flag=wx.EXPAND, proportion=1) - self.__contentSizer.Add((1, 20)) - self.__contentSizer.Add(self.__buttonSizer, flag=wx.EXPAND) - - # If running on OSX, add a message - # telling the user about the - # cmd+shift+g shortcut - if fslplatform.os == 'Darwin': - - self.__hint = wx.StaticText( - self, - label=six.u('Hint: Press \u2318+\u21e7+G in the file ' - 'dialog to manually type in a location.')) - - self.__hint.SetForegroundColour('#888888') - - self.__contentSizer.Insert(2, self.__hint, flag=wx.EXPAND) - self.__contentSizer.Insert(3, (1, 20)) - - else: - self.__hint = None - - self.__mainSizer.Add(self.__icon, - flag=wx.ALL | wx.CENTRE, - border=20) - self.__mainSizer.Add(self.__contentSizer, - flag=wx.EXPAND | wx.ALL, - proportion=1, - border=20) - - self.__message.Wrap(self.GetSize().GetWidth()) - - self.SetSizer(self.__mainSizer) - self.__mainSizer.Layout() - self.__mainSizer.Fit(self) - - self.CentreOnParent() - - - def GetFSLDir(self): - """If the user selected a directory, this method returns their - selection. Otherwise, it returns ``None``. - """ - return self.__fsldir - - - def __onSkip(self, ev): - """called when the *Skip* button is pushed. """ - self.EndModal(wx.ID_CANCEL) - - - def __onLocate(self, ev): - """Called when the *Locate* button is pushed. Opens a - :class:`wx.DirDialog` which allows the user to locate the - FSL installation directory. - """ - - dlg = wx.DirDialog( - self, - message='Select the directory in which FSL is installed', - defaultPath=op.join(os.sep, 'usr', 'local'), - style=wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST) - - # If the user cancels the file - # dialog, focus returns to the - # original 'Choose dir' / 'Skip' - # dialog. - if dlg.ShowModal() != wx.ID_OK: - return - - self.__fsldir = dlg.GetPath() - - self.EndModal(wx.ID_OK) - - -class CheckBoxMessageDialog(wx.Dialog): - """A ``wx.Dialog`` which displays a message, one or more ``wx.CheckBox`` - widgets, with associated messages, an *Ok* button, and (optionally) a - *Cancel* button. - """ - - - def __init__(self, - parent, - title=None, - message=None, - cbMessages=None, - cbStates=None, - yesText=None, - noText=None, - cancelText=None, - hintText=None, - focus=None, - icon=None, - style=None): - """Create a ``CheckBoxMessageDialog``. - - :arg parent: A ``wx`` parent object. - - :arg title: The dialog frame title. - - :arg message: Message to show on the dialog. - - :arg cbMessages: A list of labels, one for each ``wx.CheckBox``. - - :arg cbStates: A list of initial states (boolean values) for - each ``wx.CheckBox``. - - :arg yesText: Text to show on the *yes*/confirm button. Defaults - to *OK*. - - :arg noText: Text to show on the *no* button. If not provided, - there will be no *no* button. - - :arg cancelText: Text to show on the *cancel* button. If not - provided, there will be no cancel button. - - :arg hintText: If provided, shown as a "hint", in a slightly - faded font, between the checkboxes and the buttons. - - :arg focus: One of ``'yes'``, ``'no'```, or ``'cancel'``, - specifying which button should be given initial - focus. - - :arg icon: A ``wx`` icon identifier (e.g. - ``wx.ICON_EXCLAMATION``). - - :arg style: Passed through to the ``wx.Dialog.__init__`` - method. Defaults to ``wx.DEFAULT_DIALOG_STYLE``. - """ - - if style is None: style = wx.DEFAULT_DIALOG_STYLE - if title is None: title = '' - if message is None: message = '' - if cbMessages is None: cbMessages = [''] - if cbStates is None: cbStates = [False] * len(cbMessages) - if yesText is None: yesText = 'OK' - - wx.Dialog.__init__(self, parent, title=title, style=style) - - if icon is not None: - icon = wx.ArtProvider.GetMessageBoxIcon(icon) - self.__icon = wx.StaticBitmap(self) - - if fslplatform.wxFlavour == fslplatform.WX_PYTHON: - bmp = wx.EmptyBitmap(icon.GetWidth(), icon.GetHeight()) - else: - bmp = wx.Bitmap() - - bmp.CopyFromIcon(icon) - self.__icon.SetBitmap(bmp) - else: - self.__icon = (1, 1) - - self.__checkboxes = [] - for msg, state in zip(cbMessages, cbStates): - cb = wx.CheckBox(self, label=msg) - cb.SetValue(state) - self.__checkboxes.append(cb) - - self.__message = wx.StaticText(self, label=message) - self.__yesButton = wx.Button( self, label=yesText, id=wx.ID_YES) - - self.__yesButton.Bind(wx.EVT_BUTTON, self.__onYesButton) - - if noText is not None: - self.__noButton = wx.Button(self, label=noText, id=wx.ID_NO) - self.__noButton.Bind(wx.EVT_BUTTON, self.__onNoButton) - - else: - self.__noButton = None - - if cancelText is not None: - self.__cancelButton = wx.Button(self, - label=cancelText, - id=wx.ID_CANCEL) - self.__cancelButton.Bind(wx.EVT_BUTTON, self.__onCancelButton) - else: - self.__cancelButton = None - - if hintText is not None: - self.__hint = wx.StaticText(self, label=hintText) - self.__hint.SetForegroundColour('#888888') - else: - self.__hint = None - - self.__mainSizer = wx.BoxSizer(wx.HORIZONTAL) - self.__contentSizer = wx.BoxSizer(wx.VERTICAL) - self.__btnSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.__contentSizer.Add(self.__message, flag=wx.EXPAND, proportion=1) - self.__contentSizer.Add((1, 20), flag=wx.EXPAND) - for cb in self.__checkboxes: - self.__contentSizer.Add(cb, flag=wx.EXPAND) - - if self.__hint is not None: - self.__contentSizer.Add((1, 20), flag=wx.EXPAND) - self.__contentSizer.Add(self.__hint, flag=wx.EXPAND) - - self.__contentSizer.Add((1, 20), flag=wx.EXPAND) - self.__btnSizer.Add((1, 1), flag=wx.EXPAND, proportion=1) - - buttons = [self.__yesButton, self.__noButton, self.__cancelButton] - buttons = [b for b in buttons if b is not None] - - for i, b in enumerate(buttons): - self.__btnSizer.Add(b) - if i != len(buttons) - 1: - self.__btnSizer.Add((5, 1), flag=wx.EXPAND) - - self.__contentSizer.Add(self.__btnSizer, flag=wx.EXPAND) - - self.__mainSizer.Add(self.__icon, - flag=wx.ALL | wx.CENTRE, - border=20) - self.__mainSizer.Add(self.__contentSizer, - flag=wx.EXPAND | wx.ALL, - proportion=1, - border=20) - - self.__message.Wrap(self.GetSize().GetWidth()) - - yes = self.__yesButton - no = self.__noButton - cncl = self.__cancelButton - - if focus == 'yes': yes .SetDefault() - elif focus == 'no' and no is not None: no .SetDefault() - elif focus == 'cancel' and cncl is not None: cncl.SetDefault() - - self.SetSizer(self.__mainSizer) - self.Layout() - self.Fit() - self.CentreOnParent() - - - def CheckBoxState(self, index=0): - """After this ``CheckBoxMessageDialog`` has been closed, this method - will retrieve the state of the dialog ``CheckBox``. - """ - return self.__checkboxes[index].GetValue() - - - def __onYesButton(self, ev): - """Called when the button on this ``CheckBoxMessageDialog`` is - clicked. Closes the dialog. - """ - self.EndModal(wx.ID_YES) - - - def __onNoButton(self, ev): - """Called when the button on this ``CheckBoxMessageDialog`` is - clicked. Closes the dialog. - """ - self.EndModal(wx.ID_NO) - - - def __onCancelButton(self, ev): - """If the ``CHECKBOX_MSGDLG_CANCEL_BUTTON`` style was set, this method - is called when the cancel button is clicked. Closes the dialog. - """ - self.EndModal(wx.ID_CANCEL) diff --git a/fsl/utils/imagepanel.py b/fsl/utils/imagepanel.py deleted file mode 100644 index 5352bd100b2cb98718a74ae1858040aeab3343f5..0000000000000000000000000000000000000000 --- a/fsl/utils/imagepanel.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python -# -# imagepanel.py - A panel for displaying a wx.Image. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This module provides the :class:`ImagePanel` class, for displaying a -:class:`wx.Image`. -""" - -import logging - -import wx - -from fsl.utils.platform import platform as fslplatform - - -log = logging.getLogger(__name__) - - -if fslplatform.wxFlavour == fslplatform.WX_PHOENIX: ImagePanelBase = wx.Panel -else: ImagePanelBase = wx.PyPanel - - -class ImagePanel(ImagePanelBase): - """A :class:`wx.Panel` which may be used to display a resizeable - :class:`wx.Image`. The image is scaled to the size of the panel. - """ - - def __init__(self, parent, image=None): - """Create an ``ImagePanel``. - - If the ``image`` is not passed in here, it can be set later with the - :meth:`SetImage` method. - - :arg parent: The :mod:`wx` parent object. - - :arg image: The :class:`wx.Image` object to display. - """ - - ImagePanelBase.__init__(self, parent) - - self.Bind(wx.EVT_PAINT, self.Draw) - self.Bind(wx.EVT_SIZE, self.__onSize) - - self.SetImage(image) - - - def SetImage(self, image): - """Set the image that is displayed on this ``ImagePanel``. - - :arg image: The :class:`wx.Image` object to display. - """ - self.__image = image - - - if image is not None: self.SetMinSize(image.GetSize()) - else: self.SetMinSize((0, 0)) - - self.Refresh() - - - def __onSize(self, ev): - """Redraw this panel when it is sized, so the image is scaled - appropriately - see the :meth:`Draw` method. - """ - self.Refresh() - ev.Skip() - - - def DoGetBestSize(self): - """Returns the size of the image being displayed. - """ - - if self.__image is None: return (0, 0) - else: return self.__image.GetSize() - - - def Draw(self, ev=None): - """Draws this ``ImagePanel``. The image is scaled to the current panel - size. - """ - - self.ClearBackground() - - if self.__image is None: - return - - if ev is None: dc = wx.ClientDC(self) - else: dc = wx.PaintDC( self) - - if not dc.IsOk(): - return - - width, height = dc.GetSize().Get() - - if width == 0 or height == 0: - return - - bitmap = self.__image.Scale(width, height).ConvertToBitmap() - - dc.DrawBitmap(bitmap, 0, 0, False) diff --git a/fsl/utils/layout.py b/fsl/utils/layout.py deleted file mode 100644 index 95f1e40ccba3783712981642a34c10e936cee940..0000000000000000000000000000000000000000 --- a/fsl/utils/layout.py +++ /dev/null @@ -1,556 +0,0 @@ -#!/usr/bin/env python -# -# layout.py - Utility functions for calculating canvas sizes and laying them -# out. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""Utility functions for calculating canvas sizes and laying them out. - - -This module implements a simple layout manager, for laying out canvases and -associated orientation labels. It is used primarily by the :mod:`.render` -application, for off-screen rendering. - -You can use the following classes to define a layout: - -.. autosummary:: - :nosignatures: - - Bitmap - Space - HBox - VBox - - -And the following functions to generate layouts and bitmaps: - -.. autosummary:: - :nosignatures: - - buildOrthoLayout - buildCanvasBox - padBitmap - layoutToBitmap - - -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. The following -size calculation functions are available: - -.. autosummary:: - :nosignatures: - - calcSizes - calcGridSizes - calcHorizontalSizes - calcVerticalSizes - calcPixWidth - calcPixHeight -""" - - -import logging - -import numpy as np - - -log = logging.getLogger(__name__) - - -class Bitmap(object): - """A class which encapsulates a RGBA bitmap, assumed to be a - ``numpy.uint8`` array of shape :math:`height \\times width \\times 4`). - - .. warning:: Note the unusual array shape - height is the first axis, - and width the second! - - - A ``Bitmap`` instance has the following attributes: - - - ``bitmap``: The bitmap data - - ``width``: Bitmap width in pixels - - ``height``: Bitmap height in pixels - """ - - def __init__(self, bitmap): - """Create a ``Bitmap``. - - :arg bitmap: :mod:`numpy` array containing the bitmap data. - """ - self.bitmap = bitmap - self.width = bitmap.shape[1] - self.height = bitmap.shape[0] - - -class Space(object): - """A class which represents empty space of a specific width/height. - - - A ``Space`` instance has the following attributes: - - - ``width``: Width in pixels. - - ``height``: Height in pixels. - """ - - def __init__(self, width, height): - """Creat a ``Space``. - - :arg width: Width in pixels. - - :arg height: Height in pixels. - """ - self.width = width - self.height = height - - -class HBox(object): - """A class which contains items to be laid out horizontally. - - After creation, new items should be added via the :meth:`append` method. - - A ``HBox`` instance has the following attributes: - - - ``width``: Total width in pixels. - - ``height``: Total height in pixels. - - ``items``: List of items in this ``HBox``. - """ - - - def __init__(self, items=None): - """Create a ``HBox``. - - :arg items: List of items contained in this ``HBox``. - """ - self.width = 0 - self.height = 0 - self.items = [] - if items is not None: map(self.append, items) - - - def append(self, item): - """Append a new item to this ``HBox``. """ - self.items.append(item) - self.width = self.width + item.width - if item.height > self.height: - self.height = item.height - - -class VBox(object): - """A class which contains items to be laid out vertically. - - After creation, new items can be added via the :meth:`append` method. - - A ``VBox`` instance has the following attributes: - - - ``width``: Total width in pixels. - - ``height``: Total height in pixels. - - ``items``: List of items in this ``VBox``. - """ - - def __init__(self, items=None): - """Create a ``VBox``. - - :arg items: List of items contained in this ``VBox``. - """ - self.width = 0 - self.height = 0 - self.items = [] - if items is not None: map(self.append, items) - - - def append(self, item): - """Append a new item to this ``VBox``. """ - self.items.append(item) - self.height = self.height + item.height - if item.width > self.width: - self.width = item.width - - -def padBitmap(bitmap, width, height, vert, bgColour): - """Pads the given bitmap with zeros along the secondary axis (specified - with the ``vert`` parameter), so that it fits in the given - ``width``/``height``. - - - :arg bitmap: A ``numpy.array`` of size :math:`x \\times y \\times 4` - containing a RGBA bitmap. - - :arg width: Desired width in pixels. - - :arg height: Desired height in pixels. - - :arg vert: If ``vert`` is ``True``, the bitmap is padded - horizontally to fit ``width``. Otherwise, the - bitmap is padded vertically to fit ``height``. - - :arg bgColour: Background colour to use for padding. Must be - a ``(r, g, b, a)`` tuple with each channel in - the range ``[0 - 255]``. - """ - - iheight = bitmap.shape[0] - iwidth = bitmap.shape[1] - - if vert: - if iwidth < width: - lpad = int(np.floor((width - iwidth) / 2.0)) - rpad = int(np.ceil( (width - iwidth) / 2.0)) - lpad = np.zeros((iheight, lpad, 4), dtype=np.uint8) - rpad = np.zeros((iheight, rpad, 4), dtype=np.uint8) - lpad[:] = bgColour - rpad[:] = bgColour - bitmap = np.hstack((lpad, bitmap, rpad)) - else: - if iheight < height: - tpad = int(np.floor((height - iheight) / 2.0)) - bpad = int(np.ceil(( height - iheight) / 2.0)) - tpad = np.zeros((tpad, iwidth, 4), dtype=np.uint8) - bpad = np.zeros((bpad, iwidth, 4), dtype=np.uint8) - tpad[:] = bgColour - bpad[:] = bgColour - bitmap = np.vstack((tpad, bitmap, bpad)) - - return bitmap - - -def layoutToBitmap(layout, bgColour): - """Recursively turns the given ``layout`` object into a bitmap. - - :arg layout: A :class:`Bitmap`, :class:`Space`, :class:`HBox` or - :class:`VBox` instance. - - :arg bgColour: Background colour used to fill in empty space. Must be - a ``(r, g, b, a)`` tuple with channel values in the range - ``[0, 255]``. Defaults to transparent. - - :returns: a ``numpy.uint8`` array of size - :math:`height \\times width \\times 4`. - """ - - if bgColour is None: bgColour = [0, 0, 0, 0] - bgColour = np.array(bgColour, dtype=np.uint8) - - # Space is easy - if isinstance(layout, Space): - space = np.zeros((layout.height, layout.width, 4), dtype=np.uint8) - space[:] = bgColour - return space - - # Bitmap is easy - elif isinstance(layout, Bitmap): - return np.array(layout.bitmap, dtype=np.uint8) - - # Boxes require a bit of work - if isinstance(layout, HBox): vert = False - elif isinstance(layout, VBox): vert = True - - # Recursively bitmapify the children of the box - itemBmps = map(lambda i: layoutToBitmap(i, bgColour), layout.items) - - # Pad each of the bitmaps so they are all the same - # size along the secondary axis (which is width - # if the layout is a VBox, and height if the layout - # is a HBox). - width = layout.width - height = layout.height - itemBmps = map(lambda bmp: padBitmap(bmp, width, height, vert, bgColour), - itemBmps) - - if vert: return np.vstack(itemBmps) - else: return np.hstack(itemBmps) - - -def buildCanvasBox(canvasBmp, labelBmps, showLabels, labelSize): - """Builds a layout containing the given canvas bitmap, and orientation - labels (if ``showLabels`` is ``True``). - - - :arg canvasBmp: A ``numpy.uint8`` array containing a bitmap. - - :arg labelBmps: Only used if ``showLabels`` is ``True``. ``numpy.uint8`` - arrays containing label bitmaps. Must be a - dictionary of ``{side : numpy.uint8}`` mappings, - and must have keys ``top``, ``bottom``, ``left`` and - ``right``. - - :arg showLabels: If ``True``, the orientation labels provided in - ``labelBmps`` are added to the layout. - - :arg labelSize: Label sizes - the ``left``/``right`` label widths, - and ``top``/``bottom`` label heights are padded to this - size using ``Space`` objects. - - :returns: A :class:`Bitmap` or :class:`VBox` instance. - """ - - if not showLabels: return Bitmap(canvasBmp) - - row1Box = HBox([Space(labelSize, labelSize), - Bitmap(labelBmps['top']), - Space(labelSize, labelSize)]) - - row2Box = HBox([Bitmap(labelBmps['left']), - Bitmap(canvasBmp), - Bitmap(labelBmps['right'])]) - - row3Box = HBox([Space(labelSize, labelSize), - Bitmap(labelBmps['bottom']), - Space(labelSize, labelSize)]) - - return VBox((row1Box, row2Box, row3Box)) - - -def buildOrthoLayout(canvasBmps, - labelBmps, - layout, - showLabels, - labelSize): - """Builds a layout containing the given canvas bitmaps, label bitmaps, and - colour bar bitmap. - - - :arg canvasBmps: A list of ``numpy.uint8`` arrays containing the canvas - bitmaps to be laid out. - - :arg layout: One of ``'horizontal'``, ``'vertical'``, or ``'grid'``. - - See the :func:`buildCanvasBox` for details on the other parameters. - - - :returns: A :class:`HBox` or :class:`VBox` describing the layout. - """ - - if labelBmps is None: - labelBmps = [None] * len(canvasBmps) - showLabels = False - - canvasBoxes = map(lambda cbmp, lbmps: buildCanvasBox(cbmp, - lbmps, - showLabels, - labelSize), - canvasBmps, - labelBmps) - - if layout == 'horizontal': canvasBox = HBox(canvasBoxes) - elif layout == 'vertical': canvasBox = VBox(canvasBoxes) - elif layout == 'grid': - row1Box = HBox([canvasBoxes[0], canvasBoxes[1]]) - row2Box = HBox([canvasBoxes[2], Space(canvasBoxes[1].width, - canvasBoxes[2].height)]) - canvasBox = VBox((row1Box, row2Box)) - - return canvasBox - - -def calcSizes(layout, canvasaxes, bounds, width, height): - """Convenience function which, based upon whether the `layout` argument - is ``'horizontal'``, ``'vertical'``, or ``'grid'``, respectively calls - one of: - - - :func:`calcHorizontalSizes` - - :func:`calcVerticalSizes` - - :func:`calcGridSizes` - - :arg layout: String specifying the layout type. - - :arg canvsaxes: A list of tuples, one for each canvas to be laid out. - Each tuple contains two values, ``(i, j)``, where ``i`` - is an index, into ``bounds``, specifying the canvas - width, and ``j`` is an index into ``bounds``, specifying - the canvas height, in the display coordinate system. - - :arg bounds: A list of three values specifying the size of the display - space. - - :arg width: Maximum width in pixels. - - :arg height: Maximum height in pixels. - - :returns: A list of ``(width, height)`` tuples, one for each canvas, - each specifying the canvas width and height in pixels. - """ - - layout = layout.lower() - func = None - - if layout == 'horizontal': func = calcHorizontalSizes - elif layout == 'vertical': func = calcVerticalSizes - elif layout == 'grid': func = calcGridSizes - - # a bad value for layout - # will result in an error - sizes = func(canvasaxes, bounds, width, height) - - log.debug('For space ({}, {}) and {} layout, pixel ' - 'sizes for canvases {} ({}) are: {}'.format( - width, height, layout, canvasaxes, bounds, sizes)) - - return sizes - - -def calcGridSizes(canvasaxes, bounds, width, height): - """Calculates the size of three canvases so that they are laid - out in a grid, i.e.: - - 0 1 - - 2 - - - .. note:: If less than three canvases are specified, they are passed to - the :func:`calcHorizontalLayout` function. - - See :func:`calcSizes` for details on the arguments. - """ - - if len(canvasaxes) < 3: - return calcHorizontalSizes(canvasaxes, bounds, width, height) - - canvasWidths = [bounds[c[0]] for c in canvasaxes] - canvasHeights = [bounds[c[1]] for c in canvasaxes] - - ttlWidth = float(canvasWidths[ 0] + canvasWidths[ 1]) - ttlHeight = float(canvasHeights[0] + canvasHeights[2]) - - sizes = [] - - for i in range(len(canvasaxes)): - - cw = width * (canvasWidths[ i] / ttlWidth) - ch = height * (canvasHeights[i] / ttlHeight) - - acw, ach = _adjustPixelSize(canvasWidths[ i], - canvasHeights[i], - cw, - ch) - - if (float(cw) / ch) > (float(acw) / ach): cw, ch = cw, ach - else: cw, ch = acw, ch - - sizes.append((cw, ch)) - - return sizes - - -def calcVerticalSizes(canvasaxes, bounds, width, height): - """Calculates the size of up to three canvases so they are laid out - vertically. - - See :func:`calcSizes` for details on the arguments. - """ - return _calcFlatSizes(canvasaxes, bounds, width, height, True) - - -def calcHorizontalSizes(canvasaxes, bounds, width, height): - """Calculates the size of up to three canvases so they are laid out - horizontally. - - See :func:`calcSizes` for details on the arguments. - """ - return _calcFlatSizes(canvasaxes, bounds, width, height, False) - - -def _calcFlatSizes(canvasaxes, bounds, width, height, vert=True): - """Used by :func:`calcVerticalSizes` and :func:`calcHorizontalSizes`. - - Calculates the width and height, in pixels, of each canvas. - - :arg vert: If ``True`` the sizes are calculated for a vertical layout; - otherwise they are calculated for a horizontal layout. - - See :func:`calcSizes` for details on the other arguments. - - :returns: A list of ``(width, height)`` tuples, one for each canvas, - each specifying the canvas width and height in pixels. - """ - - # Get the canvas dimensions in world space - canvasWidths = [bounds[c[0]] for c in canvasaxes] - canvasHeights = [bounds[c[1]] for c in canvasaxes] - - maxWidth = float(max(canvasWidths)) - maxHeight = float(max(canvasHeights)) - ttlWidth = float(sum(canvasWidths)) - ttlHeight = float(sum(canvasHeights)) - - if vert: ttlWidth = maxWidth - else: ttlHeight = maxHeight - - sizes = [] - for i in range(len(canvasaxes)): - - if ttlWidth == 0: cw = 0 - else: cw = width * (canvasWidths[ i] / ttlWidth) - - if ttlHeight == 0: ch = 0 - else: ch = height * (canvasHeights[i] / ttlHeight) - - sizes.append((cw, ch)) - - return sizes - - -def calcPixWidth(wldWidth, wldHeight, pixHeight): - """Given the dimensions of a space to be displayed, and the available - height in pixels, calculates the required pixel width. - - :arg wldWidth: Width of the display coordinate system - - :arg wldHeight: Height of the display coordinate system - - :arg pixHeight: Available height in pixels. - - :returns: The required width in pixels. - """ - return _adjustPixelSize(wldWidth, - wldHeight, - pixHeight * (2 ** 32), - pixHeight)[0] - - -def calcPixHeight(wldWidth, wldHeight, pixWidth): - """Given the dimensions of a space to be displayed, and the available - width in pixels, calculates the required pixel height. - - :arg wldWidth: Width of the display coordinate system - - :arg wldHeight: Height of the display coordinate system - - :arg pixWidth: Available width in pixels. - - :returns: The required height in pixels. - """ - return _adjustPixelSize(wldWidth, - wldHeight, - pixWidth, - pixWidth * (2 ** 32))[1] - - - -def _adjustPixelSize(wldWidth, wldHeight, pixWidth, pixHeight): - """Used by :func:`calcPixelWidth` and :func:`calcPixelHeight`. - - Potentially reduces the given pixel width/height such that the - display space aspect ratio is maintained. - """ - - if any((pixWidth == 0, - pixHeight == 0, - wldWidth == 0, - wldHeight == 0)): - return 0, 0 - - pixRatio = float(pixWidth) / pixHeight - wldRatio = float(wldWidth) / wldHeight - - if pixRatio > wldRatio: - pixWidth = wldWidth * (pixHeight / wldHeight) - - elif pixRatio < wldRatio: - pixHeight = wldHeight * (pixWidth / wldWidth) - - return pixWidth, pixHeight diff --git a/fsl/utils/runwindow.py b/fsl/utils/runwindow.py deleted file mode 100644 index a26969e9f3d06b753c9d8975f8730b30a8a7a12a..0000000000000000000000000000000000000000 --- a/fsl/utils/runwindow.py +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/env python -# -# runwindow.py - Run a process, display its output in a wx window. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This module provides classes and functions for running a non-interactive -process, and displaying its output. - - -This module provides the :class:`RunPanel` and :class:`ProcessManager` -classes, and a couple of associated convenience functions. - -.. autosummary:: - :nosignatures: - - RunPanel - ProcessManager - run - checkAndRun -""" - -import os -import signal -import logging - -import subprocess as subp -import threading as thread - -try: import queue -except: import Queue as queue - -import wx - - -log = logging.getLogger(__name__) - - -class RunPanel(wx.Panel): - """A panel which displays a multiline text control, and a couple of - buttons along the bottom. ``RunPanel`` instances are created by the - :func:`run` function, and used/controlled by the :class:`ProcessManager`. - - - One of the buttons is intended to closes the window in which this panel - is contained. The second button is intended to terminate the running - process. Both buttons are unbound by default, so must be manually - configured by the creator. - - - The text panel and buttons are available as the following attributes: - - - ``text``: The text panel. - - ``closeButton``: The `Close window` button. - - ``killButton``: The `Terminate process` button. - """ - - def __init__(self, parent): - """Create a ``RunPanel``. - - :arg parent: The :mod:`wx` parent object. - """ - wx.Panel.__init__(self, parent) - - self.sizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(self.sizer) - - # Horizontal scrolling no work in OSX - # mavericks. I think it's a wxwidgets bug. - self.text = wx.TextCtrl(self, - style=wx.TE_MULTILINE | - wx.TE_READONLY | - wx.TE_DONTWRAP | - wx.HSCROLL) - - self.sizer.Add(self.text, flag=wx.EXPAND, proportion=1) - - self.btnPanel = wx.Panel(self) - self.btnSizer = wx.BoxSizer(wx.HORIZONTAL) - self.btnPanel.SetSizer(self.btnSizer) - - self.sizer.Add(self.btnPanel, flag=wx.EXPAND) - - self.killButton = wx.Button(self.btnPanel, label='Terminate process') - self.closeButton = wx.Button(self.btnPanel, label='Close window') - - self.btnSizer.Add(self.killButton, flag=wx.EXPAND, proportion=1) - self.btnSizer.Add(self.closeButton, flag=wx.EXPAND, proportion=1) - - -class ProcessManager(thread.Thread): - """A thread which manages the execution of a child process, and capture - of its output. - - The process output is displayed in a :class:`RunPanel` which must be - passed to the ``ProcessManager`` on creation. - - The :meth:`termProc` method can be used to terminate the child process - before it has completed. - """ - - def __init__(self, cmd, parent, runPanel, onFinish): - """Create a ``ProcessManager``. - - :arg cmd: String or list of strings, the command to be - executed. - - :arg parent: :mod:`wx` parent object. - - :arg runPanel: A :class:`RunPanel` instance , for displaying the - child process output. - - :arg onFinish: Callback function to be called when the process - finishes. May be ``None``. Must accept two parameters, - the GUI ``parent`` object, and the process return code. - """ - thread.Thread.__init__(self, name=cmd[0]) - - self.cmd = cmd - self.parent = parent - self.runPanel = runPanel - self.onFinish = onFinish - - # Handle to the Popen object which represents - # the child process. Created in run(). - self.proc = None - - # A queue for sharing data between the thread which - # is blocking on process output (this thread object), - # and the wx main thread which writes that output to - # the runPanel - self.outq = queue.Queue() - - # Put the command string at the top of the text control - self.outq.put(' '.join(self.cmd) + '\n\n') - wx.CallAfter(self.__writeToPanel) - - - def __writeToPanel(self): - """Reads a string from the output queue, and appends it - to the :class:`RunPanel`. This method is intended to be - executed via :func:`wx.CallAfter`. - """ - - try: output = self.outq.get_nowait() - except queue.Empty: output = None - - if output is None: return - - # ignore errors - the user may have closed the - # runPanel window before the process has completed - try: self.runPanel.text.WriteText(output) - except: pass - - - def run(self): - """Starts the process, then reads its output line by line, writing - each line asynchronously to the :class:`RunPanel`. When the - process ends, the ``onFinish`` method (if there is one) is called. - If the process finishes abnormally (with a non-0 exit code) a warning - dialog is displayed. - """ - - # Run the command. The preexec_fn parameter creates - # a process group, so we are able to kill the child - # process, and all of its children, if necessary. - log.debug('Running process: "{}"'.format(' '.join(self.cmd))) - self.proc = subp.Popen(self.cmd, - stdout=subp.PIPE, - bufsize=1, - stderr=subp.STDOUT, - preexec_fn=os.setsid) - - # read process output, line by line, pushing - # each line onto the output queue and - # asynchronously writing it to the runPanel - for line in self.proc.stdout: - - log.debug('Process output: {}'.format(line.strip())) - self.outq.put(line) - wx.CallAfter(self.__writeToPanel) - - # When the above for loop ends, it means that the stdout - # pipe has been broken. But it doesn't mean that the - # subprocess is finished. So here, we wait until the - # subprocess terminates, before continuing, - self.proc.wait() - - retcode = self.proc.returncode - - log.debug( 'Process finished with return code {}'.format(retcode)) - self.outq.put('Process finished with return code {}'.format(retcode)) - - wx.CallAfter(self.__writeToPanel) - - # Disable the 'terminate' button on the run panel - def updateKillButton(): - - # ignore errors - see __writeToPanel - try: self.runPanel.killButton.Enable(False) - except: pass - - wx.CallAfter(updateKillButton) - - # Run the onFinish handler, if there is one - if self.onFinish is not None: - wx.CallAfter(self.onFinish, self.parent, retcode) - - - def termProc(self): - """Attempts to kill the running child process.""" - - log.debug('Attempting to send SIGTERM to ' - 'process group with pid {}'.format(self.proc.pid)) - os.killpg(self.proc.pid, signal.SIGTERM) - - # put a message on the runPanel - self.outq.put('\nSIGTERM sent to process\n\n') - wx.CallAfter(self.__writeToPanel) - - -def run(name, cmd, parent, onFinish=None, modal=True): - """Runs the given command, displaying the output in a :class:`RunPanel`. - - :arg name: Name of the tool to be run, used in the window title. - - :arg cmd: String or list of strings, specifying the command to be - executed. - - :arg parent: :mod:`wx` parent object. - - :arg modal: If ``True``, the frame which contains the ``RunPanel`` - will be modal. - - :arg onFinish: Function to be called when the process ends. Must - accept two parameters - a reference to the :mod:`wx` - frame/dialog displaying the process output, and - the exit code of the application. - """ - - # Create the GUI - if modal, the easiest - # approach is to use a wx.Dialog - if modal: - frame = wx.Dialog( - parent, - title=name, - style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) - else: - frame = wx.Frame(parent, title=name) - - panel = RunPanel(frame) - - # Create the thread which runs the child process - mgr = ProcessManager(cmd, parent, panel, onFinish) - - # Bind the panel control buttons so they do stuff - panel.closeButton.Bind(wx.EVT_BUTTON, lambda e: frame.Close()) - panel.killButton .Bind(wx.EVT_BUTTON, lambda e: mgr.termProc()) - - # Run the thread which runs the child process - mgr.start() - - # layout and show the window - frame.Layout() - if modal: frame.ShowModal() - else: frame.Show() - - -def checkAndRun(name, - opts, - parent, - cmdFunc, - optLabels={}, - modal=True, - onFinish=None): - """Validates the given options. If invalid, a dialog is shown, - informing the user about the errors. Otherwise, the tool is - executed, and its output shown in a dialog window. Parameters: - - - :arg opts: A :class:`props.HasProperties` object to be - validated. - - :arg cmdFunc: Function which takes a :class:`props.HasProperties` - object, and returns a command to be executed (as a - list of strings), which will be passed to the :func:`run` - function. - - :arg optLabels: Dictionary containing property ``{name : label}`` mappings. - Used in the error dialog, if any options are invalid. - - See :func:`run` for details on the other arguments. - """ - - errors = opts.validateAll() - - if len(errors) > 0: - - msg = 'There are numerous errors which need '\ - 'to be fixed before {} can be run:\n'.format(name) - - for opt, error in errors: - - if opt in optLabels: name = optLabels[opt] - msg = msg + '\n - {}: {}'.format(opt, error) - - wx.MessageDialog( - parent, - message=msg, - style=wx.OK | wx.ICON_ERROR).ShowModal() - - else: - cmd = cmdFunc(opts) - run(name, cmd, parent, onFinish, modal) diff --git a/fsl/utils/status.py b/fsl/utils/status.py deleted file mode 100644 index ee40e787a529f86040129c2e398e80b72112d300..0000000000000000000000000000000000000000 --- a/fsl/utils/status.py +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env python -# -# status.py - A simple interface for displaying messages. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This is a little module which provides an interface for displaying a -message, or status update, to the user. The ``status`` module provides the -following functions: - - .. autosummary:: - :nosignatures: - - setTarget - update - clearStatus - -A couple of other functions are also provided, for reporting error messages -to the user: - - .. autosummary:: - :nosignatures: - - reportError - reportIfError - reportErrorDecorator - - -The :func:`update` function may be used to display a message. By default, the -message is simply logged (via the ``logging`` module). However, if a status -target has been set via the :func:`setTarget` function, the message is also -passed to this target. - - -.. warning:: If the status update target is a ``wx`` GUI object, you must - make sure that it is updated asynchronously (e.g. via - ``wx.CallAfter``). -""" - - -import threading -import contextlib -import logging -import inspect -import os.path as op - - -log = logging.getLogger(__name__) - - -_statusUpdateTarget = None -"""A reference to the status update target - this is ``None`` by default, and -can be set via :func:`setTarget`. -""" - - -_clearThread = None -"""Reference to a :class:`ClearThread`, which is a daemon thread that clears -the status after the timeout passed to the :func:`update` function. -""" - - -def setTarget(target): - """Set a target function to receive status updates. The ``target`` must - be a function which accepts a string as its sole parameter. - """ - global _statusUpdateTarget - _statusUpdateTarget = target - - -def update(message, timeout=1.0): - """Display a status update to the user. The message is logged and, - if a status update target has been set, passed to the target. - - :arg timeout: Timeout (in seconds) after which the status will be - cleared (via the :class:`ClearThread`). Pass in ``None`` - to disable this behaviour. - - - .. note:: The ``timeout`` method only makes sense to use if the status - target is a GUI widget of some sort. - """ - - global _clearThread - global _statusUpdateTarget - - if log.getEffectiveLevel() == logging.DEBUG: - - frame = inspect.stack()[1] - module = frame[1] - linenum = frame[2] - module = op.basename(module) - - log.debug('[{}:{}] {}'.format(module, linenum, message)) - - if _statusUpdateTarget is None: - return - - _statusUpdateTarget(message) - - if timeout is not None: - log.debug('timeout is not None - starting clear thread') - - if _clearThread is None: - _clearThread = ClearThread() - _clearThread.start() - - _clearThread.clear(timeout) - else: - if _clearThread is not None: - _clearThread.veto() - log.debug('No timeout - vetoing clear thread') - - -def clearStatus(): - """Clear the status. If a status update target has been set, it is passed - the empty string. - """ - if _statusUpdateTarget is None: - return - - _statusUpdateTarget('') - - -def reportError(title, msg, err): - """Reports an error to the user in a generic manner. If a GUI is available, - (see the :meth.`.Platform.haveGui` attribute), a ``wx.MessageBox`` is - shown. Otherwise a log message is generated. - """ - - from .platform import platform as fslplatform - from . import async - - if fslplatform.haveGui: - msg = '{}\n\nDetails: {}'.format(msg, str(err)) - - import wx - async.idle(wx.MessageBox, msg, title, wx.ICON_ERROR | wx.OK) - - -@contextlib.contextmanager -def reportIfError(title, msg, raiseError=True, report=True): - """A context manager which calls :func:`reportError` if the enclosed code - raises an ``Exception``. - - :arg raiseError: If ``True``, the ``Exception`` which was raised is - propagated upwards. - - :arg report: Defaults to ``True``. If ``False``, an error message - is logged, but :func:`reportError` is not called. - """ - try: - yield - - except Exception as e: - - log.error('{}: {}'.format(title, msg), exc_info=True) - - if report: - reportError(title, msg, e) - - if raiseError: - raise - - -def reportErrorDecorator(*args, **kwargs): - """A decorator which wraps the decorated function with - :func:`reportIfError`. - """ - - def decorator(func): - def wrapper(*wargs, **wkwargs): - with reportIfError(*args, **kwargs): - func(*wargs, **wkwargs) - - return wrapper - - return decorator - - -class ClearThread(threading.Thread): - """The ``ClearThread`` is a daemon thread used by the :func:`update` - function. Only one ``ClearThread`` is ever started - it is started on the - first call to ``update`` when a timeout is specified. - - The ``ClearThread`` waits until the :meth:`clear` method is called. - It then waits for the specified timeout and, unless another call to - :meth:`clear`, or a call to :meth:`veto` has been made, clears the - status via a call to :func:`clearStatus`. - """ - - - def __init__(self): - """Create a ``ClearThread``. """ - - threading.Thread.__init__(self) - - self.daemon = True - self.__clearEvent = threading.Event() - self.__vetoEvent = threading.Event() - self.__timeout = None - - - def clear(self, timeout): - """Clear the status after the specified timeout (in seconds). """ - - self.__timeout = timeout - self.__vetoEvent .clear() - self.__clearEvent.set() - - - def veto(self): - """If this ``ClearThread`` is waiting on a timeout to clear - the status, a call to ``veto`` will prevent it from doing so. - """ - self.__vetoEvent.set() - - - def run(self): - """The ``ClearThread`` function. Infinite loop which waits until - the :meth:`clear` method is called, and then clears the status - (via a call to :func:`clearStatus`). - """ - - while True: - - self.__vetoEvent .clear() - self.__clearEvent.wait() - self.__clearEvent.clear() - - # http://bugs.python.org/issue14623 - # - # When the main thread exits, daemon threads will - # continue to run after the threading module is - # destroyed. Calls to the Event methods can thus - # result in errors. - try: - if not self.__clearEvent.wait(self.__timeout) and \ - not self.__vetoEvent.isSet(): - - log.debug('Timeout - clearing status') - clearStatus() - - except TypeError: - return diff --git a/fsl/utils/textbitmap.py b/fsl/utils/textbitmap.py deleted file mode 100644 index 3320284b075849e9e8845b707049f1577fe547a8..0000000000000000000000000000000000000000 --- a/fsl/utils/textbitmap.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python -# -# textbitmap.py - A function which renders some text using matplotlib, and -# returns it as an RGBA bitmap. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This module provides a single function, :func:`textBitmap`, which renders -some text off-screen using :mod:`matplotlib`, and returns it as an RGBA bitmap. -""" - - -def textBitmap(text, - width, - height, - fontSize, - fgColour, - bgColour, - alpha=1.0): - """Draw some text using :mod:`matplotlib`. - - - The rendered text is returned as a RGBA bitmap within a ``numpy.uint8`` - array of size :math:`w \\times h \\times 4`, with the top-left pixel - located at index ``[0, 0, :]``. - - :arg text: Text to render. - - :arg width: Width in pixels. - - :arg height: Height in pixels. - - :arg fontSize: Font size in points. - - :arg fgColour: Foreground (text) colour - can be any colour specification - that is accepted by :mod:`matplotlib`. - - :arg bgColour: Background colour - can be any colour specification that - is accepted by :mod:`matplotlib`.. - - :arg alpha: Text transparency, in the range ``[0.0 - 1.0]``. - """ - - # Imports are expensive - import numpy as np - import matplotlib.backends.backend_agg as mplagg - import matplotlib.figure as mplfig - - dpi = 96.0 - fig = mplfig.Figure(figsize=(width / dpi, height / dpi), - dpi=dpi) - canvas = mplagg.FigureCanvasAgg(fig) - ax = fig.add_axes([0, 0, 1, 1]) - ax.axis('off') - - if bgColour is not None: fig.patch.set_facecolor(bgColour) - else: fig.patch.set_alpha(0) - - ax.set_xticks([]) - ax.set_yticks([]) - - ax.text(0.5, - 0.5, - text, - fontsize=fontSize, - verticalalignment='center', - horizontalalignment='center', - transform=ax.transAxes, - color=fgColour, - alpha=alpha) - - try: fig.tight_layout() - except: pass - - canvas.draw() - buf = canvas.tostring_argb() - ncols, nrows = canvas.get_width_height() - - bitmap = np.fromstring(buf, dtype=np.uint8) - bitmap = bitmap.reshape(nrows, ncols, 4) - - rgb = bitmap[:, :, 1:] - a = bitmap[:, :, 0] - bitmap = np.dstack((rgb, a)) - - return bitmap diff --git a/fsl/utils/typedict.py b/fsl/utils/typedict.py deleted file mode 100644 index 2c587f44b8fc6a52a8c1b42af7967a005a1dbb26..0000000000000000000000000000000000000000 --- a/fsl/utils/typedict.py +++ /dev/null @@ -1,334 +0,0 @@ -#!/usr/bin/env python -# -# typedict.py - Provides the TypeDict class. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This module provides the :class:`TypeDict` class, a type-aware dictionary. -""" - - -import six - -import collections - - -class TypeDict(object): - """A type-aware dictionary. - - - The purpose of the ``TypeDict`` is to allow value lookup using either - classes or instances as keys. The ``TypeDict`` can be used in the same way - that you would use a regular ``dict``, but the ``get`` and ``__getitem__`` - methods have some extra functionality. - - - **Easy to understand example** - - - Let's say we have a class with some properties:: - - import fsl.utils.typedict as td - - class Animal(object): - isMammal = True - numLegs = 4 - - - And we want to associate some tooltips with those properties:: - - tooltips = td.TypeDict({ - - 'Animal.isMammal' : 'Set this to True for mammals, ' - 'False for reptiles.', - 'Animal.numLegs' : 'The nuber of legs on this animal.' - }) - - Because we used a ``TypeDict``, we can now look up those tooltips - in a number of ways:: - - a = Animal() - - # Lookup by string (equivalent to a normal dict lookup) - tt = tooltips['Animal.isMammal'] - - # Lookup by class - tt = tooltips[Animal, 'isMammal'] - - # Lookup by instance - tt = tooltips[a, 'isMammal'] - - - This functionality also works across class hierarchies:: - - class Cat(Animal): - numYoutubeHits = 10 - - tooltips = td.TypeDict({ - - 'Animal.isMammal' : 'Set this to True for mammals, ' - 'False for reptiles.', - 'Animal.numLegs' : 'The nuber of legs on this animal.', - 'Cat.numYoutubeHits' : 'Number of youtube videos this cat ' - 'has starred in.' - }) - - c = Cat() - - isMammalTooltip = tooltips[Cat, 'isMammal'] - numLegsTooltip = tooltips[c, 'numLegs'] - youtubeHitsTooltip = tooltips[c, 'numYoutubeHits'] - - # Class-hierachy-aware TypeDict lookups only - # work when you pass in an instance/class as - # the key - the following will result in a - # KeyError: - t = tooltips['Cat.numLegs'] - - - The :meth:`get` method has some extra functionality for working with - class hierarchies:: - - - tooltips = td.TypeDict({ - - 'Animal.isMammal' : 'Set this to True for mammals, ' - 'False for reptiles.', - - 'Animal.numLegs' : 'The nuber of legs on this animal.', - - 'Cat.numLegs' : 'This will be equal to four for all cats, ' - 'but could be less for disabled cats, ' - 'or more for lucky cats.', - - 'Cat.numYoutubeHits' : 'Number of youtube videos this cat ' - 'has starred in.' - }) - - print tooltips.get((c, 'numLegs')) - # 'This will be equal to four for all cats, but could ' - # 'be less for disabled cats, or more for lucky cats.' - - print tooltips.get((c, 'numLegs'), allhits=True) - # ['This will be equal to four for all cats, but could ' - # 'be less for disabled cats, or more for lucky cats.', - # 'The nuber of legs on this animal.'] - - print tooltips.get((c, 'numLegs'), allhits=True, bykey=True) - # {('Animal', 'numLegs'): 'The nuber of legs on this animal.', - # ('Cat', 'numLegs'): 'This will be equal to four for all cats, ' - # 'but could be less for disabled cats, or ' - # 'more for lucky cats.'} - - - **Boring technical description** - - - The ``TypeDict`` is a custom dictionary which allows classes or class - instances to be used as keys for value lookups, but internally transforms - any class/instance keys into strings. Tuple keys are supported. Value - assignment with class/instance keys is not supported. All keys are - transformed via the :meth:`tokenifyKey` method before being internally - used and/or stored. - - If a class/instance is passed in as a key, and there is no value - associated with that class, a search is performed on all of the base - classes of that class to see if any values are present for them. - """ - - - def __init__(self, initial=None): - """Create a ``TypeDict``. - - :arg initial: Dictionary containing initial values. - """ - - if initial is None: - initial = {} - - self.__dict = {} - - for k, v in initial.items(): - self[k] = v - - - def __str__(self): - return self.__dict.__str__() - - - def __repr__(self): - return self.__dict.__repr__() - - - def __len__(self): - return len(self.__dict) - - - def keys(self): - return self.__dict.keys() - - - def values(self): - return self.__dict.values() - - - def items(self): - return self.__dict.items() - - - def __setitem__(self, key, value): - self.__dict[self.tokenifyKey(key)] = value - - - def tokenifyKey(self, key): - """Turns a dictionary key, which may have been specified as a - string, or a combination of strings and types, into a tuple. - """ - - if isinstance(key, six.string_types): - if '.' in key: return tuple(key.split('.')) - else: return key - - if isinstance(key, collections.Sequence): - - tKeys = map(self.tokenifyKey, key) - key = [] - - for tk in tKeys: - if isinstance(tk, six.string_types): key.append(tk) - elif isinstance(tk, collections.Sequence): key += list(tk) - else: key.append(tk) - - return tuple(key) - - return key - - - def get(self, key, default=None, allhits=False, bykey=False): - """Retrieve the value associated with the given key. If - no value is present, return the specified ``default`` value, - which itself defaults to ``None``. - - If the specified key contains a class or instance, and the - ``allhits`` argument evaluates to ``True``, the entire class - hierarchy is searched, and all values present for the class, - and any base class, are returned as a sequence. - - If ``allhits`` is ``True`` and the ``bykey`` parameter is also - set to ``True``, a dictionary is returned rather than a sequence, - where the dictionary contents are the subset of this dictionary, - containing the keys which equated to the given key, and their - corresponding values. - """ - - try: return self.__getitem__(key, allhits, bykey) - except KeyError: return default - - - def __getitem__(self, key, allhits=False, bykey=False): - - origKey = key - key = self.tokenifyKey(key) - bases = [] - - # Make the code a bit easier by - # treating non-tuple keys as tuples - if not isinstance(key, tuple): - key = tuple([key]) - - newKey = [] - - # Transform any class/instance elements into - # their string representation (the class name) - for elem in key: - - if isinstance(elem, type): - newKey.append(elem.__name__) - bases .append(elem.__bases__) - - elif not isinstance(elem, (str, int)): - newKey.append(elem.__class__.__name__) - bases .append(elem.__class__.__bases__) - - else: - newKey.append(elem) - bases .append(None) - - key = newKey - - keys = [] - hits = [] - - while True: - - # If the key was not a tuple turn - # it back into a single element key - # for the lookup - if len(key) == 1: lKey = key[0] - else: lKey = tuple(key) - - val = self.__dict.get(lKey, None) - - # We've found a value for the key - if val is not None: - - # If allhits is false, just return the value - if not allhits: return val - - # Otherwise, accumulate the value, and keep - # searching - else: - hits.append(val) - if bykey: - keys.append(lKey) - - # No more base classes to search for - there - # really is no value associated with this key - elif all([b is None for b in bases]): - raise KeyError(key) - - # Search through the base classes to see - # if a value is present for one of them - for i, (elem, elemBases) in enumerate(zip(key, bases)): - if elemBases is None: - continue - - # test each of the base classes - # of the current tuple element - for elemBase in elemBases: - - newKey = list(key) - newKey[i] = elemBase - - if len(newKey) == 1: newKey = newKey[0] - else: newKey = tuple(newKey) - - try: - newVal = self.__getitem__(newKey, allhits, bykey) - except KeyError: - continue - - if not allhits: - return newVal - else: - if bykey: - newKeys, newVals = zip(*newVal.items()) - keys.extend(newKeys) - hits.extend(newVals) - else: - hits.extend(newVal) - - # No value for any base classes either - if len(hits) == 0: - raise KeyError(origKey) - - # if bykey is true, return a dict - # containing the values and their - # corresponding keys - if bykey: - return dict(zip(keys, hits)) - - # otherwise just return the - # list of matched values - else: - return hits diff --git a/fsl/utils/webpage.py b/fsl/utils/webpage.py deleted file mode 100644 index 962c6444fb39beab8168f5716fe10da3a8ab7ae6..0000000000000000000000000000000000000000 --- a/fsl/utils/webpage.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python -# -# webpage.py - Convenience functions for opening a URL in a web browser. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This module provides convenience functions for opening a URL in a web -browser. - -The following functions are provided: - -.. autosummary:: - :nosignatures: - - fileToUrl - openPage - openFile - localHelpUrl - openLocalHelp -""" - -import os -import os.path as op -import webbrowser - - -def fileToUrl(fileName): - """Converts a file path to a URL. """ - - import urlparse - import urllib - return urlparse.urljoin( - 'file:', urllib.pathname2url(fileName)) - - - -def openPage(url): - """Opens the given URL in the system-default web browser.""" - webbrowser.open(url) - - -def openFile(fileName): - """Opens the given file in the system-default web browser.""" - openPage(fileToUrl(fileName)) - - -def localHelpUrl(toolName): - """Checks the ``$FSLDIR`` to see if a local help page exists for the - FSL tool with the specified name. - """ - fsldir = os.environ.get('FSLDIR', None) - - if fsldir is None: - return None - - toolName = toolName.lower() - localUrl = op.join(fsldir, 'doc', 'redirects', '{}.html'.format(toolName)) - - if op.exists(localUrl): - return fileToUrl(localUrl) - - return None - - -def openLocalHelp(toolName): - """Attempts to open the locally hosted FSL help documentation - for the given FSL tool. If there is no help page for the - given tool, attempts to open the FSL wiki. - """ - - localUrl = localHelpUrl(toolName) - - if localUrl is None: - localUrl = "http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/" - - openPage(localUrl) diff --git a/tests/test_typedict.py b/tests/test_typedict.py deleted file mode 100644 index fe19bcf2b869fe3aa43f5359aa6e16db642044e1..0000000000000000000000000000000000000000 --- a/tests/test_typedict.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python -# -# test_typedict.py - -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# - -import itertools as it -import pytest - -import fsl.utils.typedict as typedict - - -def test_create(): - - td = typedict.TypeDict() - - - assert len(td) == 0 - assert len(td.keys()) == 0 - assert len(td.values()) == 0 - assert len(td.items()) == 0 - - keys = [0, - 1, - 2, - 'a', - 'b', - 'c', - 'a.b', - 'a.b.c', - ('a', 'c'), - ('a', 0)] - values = list(range(len(keys))) - - td = typedict.TypeDict( dict(zip(keys, values))) - td = typedict.TypeDict(initial=dict(zip(keys, values))) - - print( td) - print(repr(td)) - - tknkeys = [td.tokenifyKey(k) for k in keys] - - assert len(td) == len(keys) - assert list(sorted(td.keys())) == list(sorted(tknkeys)) - assert list(sorted(td.values())) == list(sorted(values)) - assert list(sorted(td.items())) == list(sorted(zip(tknkeys, values))) - - for k, v in zip(keys, values): - assert td[k] == v - - with pytest.raises(KeyError): - td['non-existent'] - - assert td.get('non-existent') is None - assert td.get('non-existent', 'default') == 'default' - - -def test_class_keys(): - - class A(object): - pass - - class B(object): - pass - - class C(object): - pass - - td = typedict.TypeDict() - - a = A() - b = B() - c = C() - - # TypeDict [currently] does not allow value - # assignment with classes/instances as keys - - # we must use the class name, encoded as a - # string, when assigning. - keycomponents = ['A', 'B', 'C', 'att1', 'att2', 'att3'] - keys = list(it.chain(it.permutations(keycomponents, 1), - it.permutations(keycomponents, 2), - it.permutations(keycomponents, 3), - it.permutations(keycomponents, 4), - it.permutations(keycomponents, 5), - it.permutations(keycomponents, 6))) - - for val, key in enumerate(keys): - if len(key) == 1: td[key[0]] = val - else: td[key] = val - - for val, key in enumerate(keys): - - # Keys can be passed as tuples - assert td[key] == val - - # Or as dot-separated strings - assert td['.'.join(key)] == val - - # But when accessing items, - # we can use either class names, - # classes, or instances. - for toReplace in it.chain(it.combinations(['A', 'B', 'C'], 1), - it.combinations(['A', 'B', 'C'], 2), - it.combinations(['A', 'B', 'C'], 3)): - - clsrep = {'A' : A, 'B' : B, 'C' : C} - instrep = {'A' : a, 'B' : b, 'C' : c} - - # use class instead of name - repkey = [clsrep[k] if k in toReplace else k for k in key] - assert td[repkey] == val - - # use instance instead of name - repkey = [instrep[k] if k in toReplace else k for k in key] - assert td[repkey] == val - - -def test_class_hierarchy(): - - class A(object): - pass - - class B(A): - pass - - td = typedict.TypeDict() - td['A.a'] = 'A.a' - td['A.b'] = 'A.b' - td['A.c'] = 'A.c' - td['A.d'] = 'A.d' - td['B.a'] = 'B.a' - td['B.b'] = 'B.b' - td['B.1'] = 'B.1' - td['B.2'] = 'B.2' - - a = A() - b = B() - - assert td[A, 'a'] == 'A.a' - assert td[A, 'b'] == 'A.b' - assert td[A, 'c'] == 'A.c' - assert td[A, 'd'] == 'A.d' - - assert td['A.a'] == 'A.a' - assert td['A.b'] == 'A.b' - assert td['A.c'] == 'A.c' - assert td['A.d'] == 'A.d' - - assert td.get((A, 'a')) == 'A.a' - assert td.get((A, 'b')) == 'A.b' - assert td.get((A, 'c')) == 'A.c' - assert td.get((A, 'd')) == 'A.d' - - assert td.get((A, 'a'), allhits=True) == ['A.a'] - assert td.get((A, 'b'), allhits=True) == ['A.b'] - assert td.get((A, 'c'), allhits=True) == ['A.c'] - assert td.get((A, 'd'), allhits=True) == ['A.d'] - - assert td.get((a, 'a'), allhits=True) == ['A.a'] - assert td.get((a, 'b'), allhits=True) == ['A.b'] - assert td.get((a, 'c'), allhits=True) == ['A.c'] - assert td.get((a, 'd'), allhits=True) == ['A.d'] - - assert td[B, 'a'] == 'B.a' - assert td[B, 'b'] == 'B.b' - assert td[B, '1'] == 'B.1' - assert td[B, '2'] == 'B.2' - - assert td['B.a'] == 'B.a' - assert td['B.b'] == 'B.b' - assert td['B.1'] == 'B.1' - assert td['B.2'] == 'B.2' - - assert td.get('B.a') == 'B.a' - assert td.get('B.b') == 'B.b' - assert td.get('B.1') == 'B.1' - assert td.get('B.2') == 'B.2' - - assert td.get((B, 'a')) == 'B.a' - assert td.get((B, 'b')) == 'B.b' - assert td.get((B, '1')) == 'B.1' - assert td.get((B, '2')) == 'B.2' - assert td.get((b, 'a')) == 'B.a' - assert td.get((b, 'b')) == 'B.b' - assert td.get((b, '1')) == 'B.1' - assert td.get((b, '2')) == 'B.2' - - with pytest.raises(KeyError): - td['B.c'] - with pytest.raises(KeyError): - td['B.d'] - - assert td[B, 'c'] == 'A.c' - assert td[B, 'd'] == 'A.d' - assert td[b, 'c'] == 'A.c' - assert td[b, 'd'] == 'A.d' - - assert td.get('B.a', allhits=True) == ['B.a'] - assert td.get('B.b', allhits=True) == ['B.b'] - assert td.get('B.1', allhits=True) == ['B.1'] - assert td.get('B.2', allhits=True) == ['B.2'] - assert td.get((B, 'a'), allhits=True) == ['B.a', 'A.a'] - assert td.get((B, 'b'), allhits=True) == ['B.b', 'A.b'] - assert td.get((B, '1'), allhits=True) == ['B.1'] - assert td.get((B, '2'), allhits=True) == ['B.2'] - assert td.get((B, 'c'), allhits=True) == ['A.c'] - assert td.get((B, 'd'), allhits=True) == ['A.d'] - - assert td.get((b, 'a'), allhits=True) == ['B.a', 'A.a'] - assert td.get((b, 'b'), allhits=True) == ['B.b', 'A.b'] - assert td.get((b, '1'), allhits=True) == ['B.1'] - assert td.get((b, '2'), allhits=True) == ['B.2'] - assert td.get((b, 'c'), allhits=True) == ['A.c'] - - assert td.get((B, 'a'), allhits=False, bykey=True) == 'B.a' - assert td.get((B, 'b'), allhits=False, bykey=True) == 'B.b' - assert td.get((B, '1'), allhits=False, bykey=True) == 'B.1' - assert td.get((B, '2'), allhits=False, bykey=True) == 'B.2' - - assert td.get((B, 'a'), allhits=True, bykey=True) == {('A', 'a') : 'A.a', - ('B', 'a') : 'B.a'} - assert td.get((B, 'b'), allhits=True, bykey=True) == {('A', 'b') : 'A.b', - ('B', 'b') : 'B.b'} - assert td.get((B, '1'), allhits=True, bykey=True) == {('B', '1') : 'B.1'} - assert td.get((B, '2'), allhits=True, bykey=True) == {('B', '2') : 'B.2'} - assert td.get((B, 'c'), allhits=True, bykey=True) == {('A', 'c') : 'A.c'} - assert td.get((B, 'd'), allhits=True, bykey=True) == {('A', 'd') : 'A.d'} - - assert td.get((b, 'a'), allhits=True, bykey=True) == {('A', 'a') : 'A.a', - ('B', 'a') : 'B.a'} - assert td.get((b, 'b'), allhits=True, bykey=True) == {('A', 'b') : 'A.b', - ('B', 'b') : 'B.b'} - assert td.get((b, '1'), allhits=True, bykey=True) == {('B', '1') : 'B.1'} - assert td.get((b, '2'), allhits=True, bykey=True) == {('B', '2') : 'B.2'} - assert td.get((b, 'c'), allhits=True, bykey=True) == {('A', 'c') : 'A.c'} - assert td.get((b, 'd'), allhits=True, bykey=True) == {('A', 'd') : 'A.d'} - - assert td.get((A, 'a'), allhits=True, bykey=True) == {('A', 'a') : 'A.a'} - assert td.get((A, 'b'), allhits=True, bykey=True) == {('A', 'b') : 'A.b'} - assert td.get((A, 'c'), allhits=True, bykey=True) == {('A', 'c') : 'A.c'} - assert td.get((A, 'd'), allhits=True, bykey=True) == {('A', 'd') : 'A.d'}