Skip to content
Snippets Groups Projects
Commit 1c791814 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Working on support for dynamic adding of images at runtime

parent bf8d2514
No related branches found
No related tags found
No related merge requests found
...@@ -15,8 +15,9 @@ import nibabel as nib ...@@ -15,8 +15,9 @@ import nibabel as nib
import matplotlib.cm as mplcm import matplotlib.cm as mplcm
import matplotlib.colors as mplcolors import matplotlib.colors as mplcolors
import fsl.props as props import fsl.props as props
import fsl.data.imagefile as imagefile import fsl.data.imagefile as imagefile
import fsl.utils.notifylist as notifylist
class Image(object): class Image(object):
...@@ -31,7 +32,7 @@ class Image(object): ...@@ -31,7 +32,7 @@ class Image(object):
""" """
# The image parameter may be the name of an image file # The image parameter may be the name of an image file
if isinstance(image, str): if isinstance(image, basestring):
image = nib.load(imagefile.addExt(image)) image = nib.load(imagefile.addExt(image))
# Or a numpy array - we wrap it in a nibabel image, # Or a numpy array - we wrap it in a nibabel image,
...@@ -147,7 +148,7 @@ class ImageDisplay(props.HasProperties): ...@@ -147,7 +148,7 @@ class ImageDisplay(props.HasProperties):
self.displayMax = self.dataMax self.displayMax = self.dataMax
class ImageList(object): class ImageList(notifylist.NotifyList):
""" """
Class representing a collection of images to be displayed together. Class representing a collection of images to be displayed together.
Provides basic list-like functionality, and a listener interface Provides basic list-like functionality, and a listener interface
...@@ -165,42 +166,8 @@ class ImageList(object): ...@@ -165,42 +166,8 @@ class ImageList(object):
if not isinstance(images, collections.Iterable): if not isinstance(images, collections.Iterable):
raise TypeError('images must be a sequence of images') raise TypeError('images must be a sequence of images')
if not all(map(lambda img: isinstance(img, Image), images)): def validate(img):
raise TypeError('images must be a sequence of images') if not isinstance(img, Image):
raise TypeError('images must be a sequence of images')
self._images = images
self._listeners = []
def __len__ (self): return self._images.__len__()
def __getitem__ (self, key): return self._images.__getitem__(key)
def __iter__ (self): return self._images.__iter__()
def __contains__(self, image): return self._images.__contains__(image)
def append(self, image):
self._images.append(image)
self.notify()
def pop(self, index=-1):
popped = self._images.pop(index)
self.notify()
return popped
def insert(self, index, image):
self._images.insert(index, image)
self.notify()
def extend(self, images):
self._images.extend(images)
self.notify()
def addListener (self, listener): self._listeners.append(listener) notifylist.NotifyList.__init__(self, images, validate)
def removeListener(self, listener): self._listeners.remove(listener)
def notify (self):
for listener in self._listeners:
listener(self)
...@@ -14,11 +14,37 @@ import os.path as op ...@@ -14,11 +14,37 @@ import os.path as op
# to any of the functions in this module. # to any of the functions in this module.
_allowedExts = ['.nii', '.img', '.hdr', '.nii.gz', '.img.gz'] _allowedExts = ['.nii', '.img', '.hdr', '.nii.gz', '.img.gz']
_descriptions = ['NIFTI1 images',
'ANALYZE75 images',
'NIFTI1/ANALYZE75 headers',
'Compressed NIFTI1 images',
'Compressed ANALYZE75/NIFTI1 images']
# The default file extension (TODO read this from $FSLOUTPUTTYPE) # The default file extension (TODO read this from $FSLOUTPUTTYPE)
_defaultExt = '.nii.gz' _defaultExt = '.nii.gz'
def wildcard(allowedExts=None):
"""
"""
if allowedExts is None:
allowedExts = _allowedExts
descs = _descriptions
else:
descs = allowedExts
exts = ['*{}'.format(ext) for ext in allowedExts]
wcParts = ['|'.join((desc,ext)) for (desc,ext) in zip(descs, exts)]
print '|'.join(wcParts)
return '|'.join(wcParts)
def isSupported(filename, allowedExts=None): def isSupported(filename, allowedExts=None):
""" """
Returns True if the given file has a supported extension, False Returns True if the given file has a supported extension, False
......
#!/usr/bin/env python
#
# fslview.py -
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import sys
import wx
import fsl.fslview.slicecanvas as slicecanvas
import fsl.fslview.orthopanel as orthopanel
import fsl.fslview.imagelistpanel as imagelistpanel
import fsl.data.fslimage as fslimage
class FslViewFrame(wx.Frame):
def __init__(self, imageList, title=''):
wx.Frame.__init__(self, None, title=title)
self.imageList = imageList
self.orthoPanel = orthopanel .OrthoPanel( self, imageList)
self.listPanel = imagelistpanel.ImageListPanel(self, imageList)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer)
self.sizer.Add(self.orthoPanel, flag=wx.EXPAND, proportion=1)
self.sizer.Add(self.listPanel, flag=wx.EXPAND)
self.Layout()
if __name__ == '__main__':
app = wx.App()
images = map(fslimage.Image, sys.argv[1:])
imageList = fslimage.ImageList(images)
frame = FslViewFrame(imageList)
frame.Show()
app.MainLoop()
#!/usr/bin/env python
#
# imagelistpanel.py - A panel which displays an image list, and a 'console'
# allowing the display properties of each image to be changed, and images
# to be added/removed from the list.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import os
import wx
import fsl.data.fslimage as fslimage
import fsl.data.imagefile as imagefile
import fsl.utils.elistbox as elistbox
import fsl.props as props
class ImageListPanel(wx.Panel):
"""
"""
def __init__(self, parent, imageList):
"""
"""
wx.Panel.__init__(self, parent)
self.imageList = imageList
imageNames = [img.name for img in imageList]
# list box containing the list of images
self.listBox = elistbox.EditableListBox(
self, imageNames, imageList, style=elistbox.ELB_REVERSE)
self.listBox.Bind(elistbox.EVT_ELB_SELECT_EVENT, self._imageSelected)
self.listBox.Bind(elistbox.EVT_ELB_MOVE_EVENT, self._imageMoved)
self.listBox.Bind(elistbox.EVT_ELB_REMOVE_EVENT, self._imageRemoved)
self.listBox.Bind(elistbox.EVT_ELB_ADD_EVENT, self._addImage)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.SetSizer(self.sizer)
self.sizer.Add(self.listBox, flag=wx.EXPAND, proportion=1)
# a panel for each image, containing widgets
# allowing the image display properties to be
# changed
for i,image in enumerate(imageList):
displayPanel = self._makeDisplayPanel(image)
displayPanel.Show(i == 0)
self.Layout()
def _makeDisplayPanel(self, image):
"""
"""
displayPanel = props.buildGUI(self, image.display)
self.sizer.Add(displayPanel, flag=wx.EXPAND, proportion=2)
image.setAttribute(
'displayPanel_{}'.format(id(self)), displayPanel)
return displayPanel
def _imageMoved(self, ev):
"""
Called when an image name is moved in the ListBox. Reorders the
ImageList to reflect the change.
"""
self.imageList.move(ev.oldIdx, ev.newIdx)
self.Refresh()
def _imageSelected(self, ev):
"""
Called when an image is selected in the ListBox. Displays the
corresponding image display configuration panel.
"""
for i,image in enumerate(self.imageList):
displayPanel = image.getAttribute(
'displayPanel_{}'.format(id(self)))
displayPanel.Show(i == ev.idx)
self.Layout()
def _addImage(self, ev):
try: lastDir = self._lastDir
except: lastDir = os.getcwd()
wildcard = imagefile.wildcard()
dlg = wx.FileDialog(self.GetParent(),
message='Open image file',
defaultDir=lastDir,
wildcard=wildcard,
style=wx.FD_OPEN)
if dlg.ShowModal() != wx.ID_OK: return
image = fslimage.Image(dlg.GetPath())
self.imageList.insert(0, image)
self._makeDisplayPanel(image)
self.listBox.Insert(0, image.name, image)
def _imageRemoved(self, ev):
"""
"""
image = self.imageList.pop(ev.idx)
displayPanel = image.getAttribute('displayPanel_{}'.format(id(self)))
displayPanel.Destroy()
...@@ -298,11 +298,6 @@ class SliceCanvas(wxgl.GLCanvas): ...@@ -298,11 +298,6 @@ class SliceCanvas(wxgl.GLCanvas):
glImageData = self._initGLImageData(image) glImageData = self._initGLImageData(image)
image.setAttribute('glImageData_{}'.format(id(self)), glImageData)
self._configDisplayListeners(image)
self.updateColourBuffer(image)
self.glReady = True self.glReady = True
...@@ -340,6 +335,10 @@ class SliceCanvas(wxgl.GLCanvas): ...@@ -340,6 +335,10 @@ class SliceCanvas(wxgl.GLCanvas):
glImageData = GLImageData( glImageData = GLImageData(
image, imageBuffer, colourBuffer, positionBuffer, geomBuffer) image, imageBuffer, colourBuffer, positionBuffer, geomBuffer)
image.setAttribute('glImageData_{}'.format(id(self)), glImageData)
self._configDisplayListeners(image)
self.updateColourBuffer(image)
return glImageData return glImageData
...@@ -350,7 +349,8 @@ class SliceCanvas(wxgl.GLCanvas): ...@@ -350,7 +349,8 @@ class SliceCanvas(wxgl.GLCanvas):
image buffer is used instead. image buffer is used instead.
""" """
imageBuffer = image.getAttribute('glBuffer') try: imageBuffer = image.getAttribute('glBuffer')
except: imageBuffer = None
if imageBuffer is not None: if imageBuffer is not None:
return imageBuffer return imageBuffer
...@@ -518,7 +518,14 @@ class SliceCanvas(wxgl.GLCanvas): ...@@ -518,7 +518,14 @@ class SliceCanvas(wxgl.GLCanvas):
image = self.imageList[i] image = self.imageList[i]
glImageData = image.getAttribute( glImageData = image.getAttribute(
'glImageData_{}'.format(id(self))) 'glImageData_{}'.format(id(self)))
# try:
# glImageData = image.getAttribute(
# 'glImageData_{}'.format(id(self)))
# except:
# wx.CallAfter(lambda : self._initGLImageData(image))
# return
imageDisplay = image.display imageDisplay = image.display
geomBuffer = glImageData.geomBuffer geomBuffer = glImageData.geomBuffer
......
#!/usr/bin/env python
#
# notifylist.py -
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import collections
import logging
import unittest
log = logging.getLogger(__name__)
class NotifyList(object):
def __init__(self, items=None, validateFunc=None):
if items is None: items = []
if validateFunc is None: validateFunc = lambda v: v
if not isinstance(items, collections.Iterable):
raise TypeError('items must be a sequence')
map(validateFunc, items)
self._validate = validateFunc
self._items = items
self._listeners = []
def __len__ (self): return self._items.__len__()
def __getitem__ (self, key): return self._items.__getitem__(key)
def __iter__ (self): return self._items.__iter__()
def __contains__(self, item): return self._items.__contains__(item)
def __eq__ (self, other): return self._items.__eq__(other)
def __str__ (self): return self._items.__str__()
def __repr__ (self): return self._items.__repr__()
def append(self, item):
self._validate(item)
self._items.append(item)
self._notify()
def pop(self, index=-1):
popped = self._items.pop(index)
self._notify()
return popped
def insert(self, index, item):
self._validate(item)
self._items.insert(index, item)
self._notify()
def extend(self, items):
map(self._validate, items)
self._items.extend(items)
self._notify()
def move(self, from_, to):
"""
Move the item from 'from_' to 'to'.
"""
item = self._items.pop(from_)
self._items.insert(to, item)
self._notify()
def addListener (self, listener): self._listeners.append(listener)
def removeListener(self, listener): self._listeners.remove(listener)
def _notify (self):
for listener in self._listeners:
try:
listener(self)
except e:
log.debug('Listener raised exception: {}'.format(e.message))
class TestNotifyList(unittest.TestCase):
def setUp(self):
self.listlen = 5
self.thelist = NotifyList(range(self.listlen))
def test_move(self):
for i in range(self.listlen):
for j in range(self.listlen):
self.setUp()
self.thelist.move(i, j)
demo = range(self.listlen)
val = demo.pop(i)
demo.insert(j, val)
print '{} -> {}: {} <-> {}'.format(i, j, self.thelist, demo)
self.assertEqual(self.thelist, demo)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment