Hi Eric,

On 30/12/06, Eric Emsellem <[EMAIL PROTECTED]> wrote:
Hi,

I am writing a small module to easily load images and interact with
them.

Sorry for getting to this thread late - back from a quick holiday now.
I have written an image browser module that does what it sounds like
you're trying to do (and works with 3-D image stacks as well). It's
still quite rudimentary, and I hadn't really planned on sharing it in
its current state, but it may be that you can find some useful ideas
in the code. The image browser is a matplotlib canvas embedded in a wx
window, and I use the WxAgg backend - I've no idea how that will
change things from your setup. I run it interactively from ipython
-pylab as shown below.

Usage is pretty simple:

In [1]: import pyvis
In [2]: pv = pyvis.pyvis()
In [3]: pv.AddImg(arange(10000).reshape(100,100), 'my gradient')

then play around with the menus. More than one image can be loaded at
a time. I haven't had a close look at the memory usage, but it has
been working adequately for quite large image stacks
(1024x1024x250x8-bit). Feel free to use the code as you like.

Angus.
--
AJC McMorland, PhD Student
Physiology, University of Auckland
import numpy
import wx
import matplotlib

matplotlib.interactive(False)
matplotlib.use("WXAgg")
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg, \
     NavigationToolbar2WxAgg, FigureManager
from matplotlib.figure import Figure
from matplotlib.axes import Subplot

ID_FILE_EXIT         =   101
ID_WINDOW_VIEWS      =   102
ID_WINDOW_DATA       =   103
ID_TOOLS_MAXPROJ     =   104
ID_TOOLS_DELITEM     =   105
ID_TOOLS_LOSEFOCUS   =   106
ID_CMAP_GRAY         =   107
ID_CMAP_JET          =   108


version = 0.100

class MyNavToolbar2WxAgg(NavigationToolbar2WxAgg):
    def __init__(self, canvas, parent):
        self.parent = parent
        return NavigationToolbar2WxAgg.__init__(self, canvas)
    
    def home(self, *args):
        self.parent.ResetLims()
        NavigationToolbar2WxAgg.home(self, *args)
        
#     '''rendered obsolete by use of toolbar.mode (which I didn''t
#     realise existed).'''
#     def __init__(self, canvas):
#         self.zoomode = False
#         self.panmode = False
#         return NavigationToolbar2WxAgg.__init__(self, canvas)
    
#     def zoom(self, *args):
#         self.zoomode = not self.zoomode
#         if self.panmode:
#             self.panmode = False
#         NavigationToolbar2WxAgg.zoom(self, *args)

#     def pan(self, *args):
#         self.panmode = not self.panmode
#         if self.zoomode:
#             self.zoomode = False
#         NavigationToolbar2WxAgg.pan(self, *args)


# ---------------------------------------------------------------
class ViewsFrame(wx.Frame):
    '''
    frame for Views controls'''


    def __init__(self,parent):
        wx.Frame.__init__(self,None,-1,"Views")
        self.parent = parent
        wx.EVT_CLOSE(self, self.OnCloseViews)

        self.sizerouter = wx.BoxSizer(wx.VERTICAL)
        self.sizerinner = wx.BoxSizer(wx.HORIZONTAL)
        self.sizerinner.Add((10,1))
        self.zslider = \
                         wx.Slider( self, -1, 0, 0, 100, \
                                    wx.DefaultPosition, wx.DefaultSize, \
                                    wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | \
                                    wx.SL_LABELS )
        self.sizerinner.Add( wx.StaticText( self, -1, 'z'  ), 1, \
                             wx.ALIGN_CENTER )
        self.sizerinner.Add( self.zslider, 9, wx.EXPAND )
        self.sizerouter.Add( self.sizerinner, 1, wx.EXPAND )
        self.Bind(wx.EVT_SLIDER, self.SliderUpdate)
        self.SetSizer(self.sizerouter)
        self.SetAutoLayout(1)


    def OnCloseViews(self,evt):
        if not evt.CanVeto():
            self.Destroy()
        else:
            evt.Veto()
            self.parent.mainframe.windowmenu.Check(ID_WINDOW_VIEWS, False)
            self.Show(False)


    def SliderUpdate(self,evt):
        if self.zslider.GetValue() != self.parent.zpos:
            self.parent.zpos = self.zslider.GetValue()
            self.parent.UpdateImage(keepaxes=True)


# ---------------------------------------------------------------
class DataFrame(wx.Frame):
    '''
    frame for Data list'''


    def __init__(self,parent):
        wx.Frame.__init__(self,None,-1,"Data")
        self.parent = parent
        wx.EVT_CLOSE(self, self.OnCloseViews)

        id = wx.NewId()
        self.list = wx.ListCtrl(self,id,style=wx.LC_REPORT | wx.LC_SINGLE_SEL)
        self.list.InsertColumn(0, "name")
        self.list.InsertColumn(1, "shape")
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.list,1,wx.EXPAND)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectItem)
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)


    def OnCloseViews(self,evt):
        if not evt.CanVeto():
            self.Destroy()
        else:
            evt.Veto()
            self.parent.mainframe.windowmenu.Check(ID_WINDOW_DATA, False)
            self.Show(False)


    def ShapeString(self, id):
        ndims = numpy.rank( self.parent.ims[id] )
        str = ''
        for i in range( ndims ):
            dimsize = self.parent.ims[id].shape[i]
            if dimsize > 1:
                if i > 0:
                    str = str + ' x '
                str = str + "%d" % dimsize
        return str


    def AddItem(self, name):
        id = self.list.GetItemCount()
        self.list.InsertStringItem(id, name)
        self.list.SetStringItem(id, 1, self.ShapeString(name) )
    

    def DelItem(self, name):
        id = self.list.FindItem(-1, name)
        self.list.DeleteItem(id)
        # select last remaining item in list
        self.list.Select(self.list.GetItemCount() - 1)
        

    def OnSelectItem(self, evt):
        self.parent._pdbg(3, '->dataframe.OnSelectItem')
        if self.parent.curname != evt.GetText():
            self.parent.SwitchToData(evt.GetText())


    def SelectItem(self, name):
        self.parent._pdbg(3, '->dataframe.SelectItem')
        id = self.list.FindItem(-1, name)
        self.list.Select(id)


    def GetCurrentItemText(self):
        sid = self.list.GetFirstSelected()
        if sid > 0:
            return self.list.GetItemText(sid)
        else:
            return None


# ---------------------------------------------------------------
class PlotFigure(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__(self, None, -1, "PyVis")

        self.parent = parent
        self.fig = Figure((5,4), 75)

        # canvas stuff
        self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
        self.canvas.mpl_connect('motion_notify_event', self.SetMouseStatus)

        
        self.toolbar = MyNavToolbar2WxAgg(self.canvas, self)
        #self.toolbar = NavigationToolbar2WxAgg(self.canvas)
        self.toolbar.Realize()
        self.CreateStatusBar()
        self.cmap = None

        # file menu
        filemenu = wx.Menu()
        filemenu.Append(ID_FILE_EXIT, "E&xit", "Terminate the program")
        self.Connect(ID_FILE_EXIT, -1, wx.wxEVT_COMMAND_MENU_SELECTED, \
                     self.OnMenuExit)

        # window menu
        self.windowmenu = wx.Menu()
        self.windowmenu.AppendCheckItem(ID_WINDOW_DATA, "&Data",
                                        "Open/close Data window")
        self.windowmenu.AppendCheckItem(ID_WINDOW_VIEWS, "&Views",
                                        "Toggle open state of Views window")
        self.Connect(ID_WINDOW_VIEWS, -1, wx.wxEVT_COMMAND_MENU_SELECTED, \
                     self.ShowHideViews)

        # tools menu
        toolsmenu = wx.Menu()
        toolsmenu.Append(ID_TOOLS_MAXPROJ, "&Maximum projection", \
                        "Show maximum projection over selected axes" )
        toolsmenu.Append(ID_TOOLS_DELITEM, "&Delete", \
                         "Delete the currently selected item" )
        self.Connect(ID_TOOLS_MAXPROJ, -1,  wx.wxEVT_COMMAND_MENU_SELECTED, \
                     self.ShowMaxProj)
        self.Connect(ID_TOOLS_DELITEM, -1, wx.wxEVT_COMMAND_MENU_SELECTED, \
                     self.DelCurItem)

        # colourmap menu
        self.cmapmenu = wx.Menu()
        self.cmapmenu.AppendCheckItem(ID_CMAP_GRAY, "&Gray", \
                        "Use Gray colour map")
        self.cmapmenu.AppendCheckItem(ID_CMAP_JET, "&Jet", \
                        "Use Jet colour map")
        self.Connect(ID_CMAP_GRAY, -1, wx.wxEVT_COMMAND_MENU_SELECTED, \
                    self.CMapGray)
        self.Connect(ID_CMAP_JET, -1, wx.wxEVT_COMMAND_MENU_SELECTED, \
                    self.CMapJet)
        
        
        # menu bar
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, "&File")
        menuBar.Append(self.windowmenu, "&Window")
        menuBar.Append(toolsmenu, "&Tools")
        menuBar.Append(self.cmapmenu, "&Colourmaps")
        self.SetMenuBar(menuBar)

        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        wx.EVT_MENU(self, ID_WINDOW_DATA, self.ShowHideData)

        self.figmgr = FigureManager(self.canvas, 1, self)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas, 1, wx.LEFT|wx.TOP|wx.GROW)
        sizer.Add(self.toolbar, 0, wx.GROW)
        self.SetSizer(sizer)
        self.Fit()


    def plot_data(self, array, vmin=None, vmax=None, keepaxes=False):
        a = self.fig.add_subplot(111)
        if keepaxes:
            xlims = a.get_xlim()
            ylims = a.get_ylim()
        a.imshow(array, interpolation='nearest', vmin=vmin, vmax=vmax, cmap=self.cmap)
        if keepaxes:
            a.set_xlim(xlims)
            a.set_ylim(ylims)
        self.toolbar.update()
        self.Refresh()


    def GetLims(self):
        a = self.fig.add_subplot(111)
        xlims = a.get_xlim()
        ylims = a.get_ylim()
        return xlims, ylims


    def ResetLims(self):
        a = self.fig.add_subplot(111)
        maxes = self.parent.ims[self.parent.curname].shape
        a.set_xlim((0,maxes[0]))
        a.set_ylim((0,maxes[1]))
        self.Refresh()

    def GetToolbar(self):
        return self.toolbar


    def OnCloseWindow(self, evt):
        if not self.IsBeingDeleted():
            self.parent.viewsframe.Close(True)
            self.parent.dataframe.Close(True)
            self.Destroy()


    def OnMenuExit(self, evt):
        self.parent.viewsframe.Close(True)
        self.parent.dataframe.Close(True)
        self.Destroy()


    def ShowHideData(self, evt):
        self.parent.dataframe.Show(not self.parent.dataframe.IsShown())

       
    def ShowHideViews(self, evt):
        self.parent.viewsframe.Show(not self.parent.viewsframe.IsShown())


    def SetMouseStatus(self, evt):
        a = self.fig.gca()
        xpos, ypos = evt.xdata, evt.ydata
        if xpos != None and ypos != None:
            zpos = self.parent.zpos
            # should probably do proper check that exact upper value isn't met
            try:
                val = self.parent.im[numpy.floor(xpos), numpy.floor(ypos)]
                status_str = "x: %4.2f  y: %4.2f  z: %4.2f  I:%4.2f" \
                             % (xpos, ypos, zpos, val)
                self.SetStatusText(status_str)
            except:
                pass


    def ShowMaxProj(self, evt):
        self.parent.MaxProj(self.parent.curname)

        
    def DelCurItem(self, evt):
        self.parent.DelItem( self.parent.curname )


    def CMapGray(self, evt):
        self.cmap = matplotlib.cm.gray
        self.cmapmenu.Check(ID_CMAP_GRAY, True)
        self.cmapmenu.Check(ID_CMAP_JET, False)
        self.parent.UpdateImage(keepaxes=True)


    def CMapJet(self, evt):
        self.cmap = matplotlib.cm.jet
        self.cmapmenu.Check(ID_CMAP_GRAY, False)
        self.cmapmenu.Check(ID_CMAP_JET, True)
        self.parent.UpdateImage(keepaxes=True)
        
        
# ---------------------------------------------------------------
class pyvis(wx.App):

    verbosity = 0

    def OnInit(self):
        '''Usage pv = pyvis.pyvis()
        pv.AddImage(arr, 'arname')

        PyVis array visualization tool, based loosely after the
        excellent KVis by Richard Gooch, which connects to pdl'''

        self._pdbg(3, '->init')
        
        self.nextnum = 0
        self.zpos = 0
        self.ims = {}
        self.mainframe = PlotFigure(self)
        self.mainframe.Show(True)
        self.dataframe = DataFrame(self)
        self.viewsframe = ViewsFrame(self)
        self.SetTopWindow(self.mainframe)
        self.cid = []
        self.curname = None
        return True


    def _pdbg(self, level, str):
        if self.verbosity > level:
            print str


    def AddImg(self, stack, name=None):
        '''Usage:  AddImg(data, name=name)
        Add an image (2 or 3-D) to PyVis stack, with an optional
        name for reference.'''

        self._pdbg(3, '->AddImg')
        # set appropriate data name
        if name == None:
            name = "image %d" % self.nextnum
            self.nextnum += 1
        if name in self.ims.keys():
            # ensure unique image name
            name = name + '%d' % self.nextnum
            self.nextnum += 1

        # load data into memory
        self.ims[name] = stack
        self.dataframe.AddItem(name)
        self.SwitchToData(name)


    def UpdateImage(self, keepaxes=False):
        '''Internal usage mainly. Used to redraw the current image
        (2-D). Keepaxes is used when switching z-position to maintain
        the axis position (zoom level).'''

        self._pdbg(3, '->UpdateImage')

        # for coding convenience
        data = self.ims[self.curname]

        # load data (plane if 3-D) into image
        if numpy.rank(data) == 2:
            self.im = data
        elif numpy.rank(data) == 3:
            self.im = data[:,:,self.zpos]
        else:
            raise TypeError("Data has too many dimensions")

        # display image
        self.mainframe.plot_data(self.im[:,::-1].transpose(), \
                                 vmin=self.imin, vmax=self.imax, \
                                 keepaxes=keepaxes)


    def SwitchToData(self, name):
        '''Usage:  SwitchToData(name)
        
        Switch to viewing the data named <name>.'''

        self._pdbg(3, '->SwitchToData')

        if name == None:
            self.mainframe.fig.clf()
            self.curname == None
        else:
            
            # working out if should keep same x-y position/zoom
            # as long as some old image is present...
            if not self.curname is None:
                old_lims = self.mainframe.GetLims()
                self._pdbg( 3, "in SwitchToData: old lims" + str( old_lims ) )
            else:
                self._pdbg(3, "in SwitchToData: no old lims")
                old_lims = None

            # for coding convenience
            data = self.ims[name]

            # for colour-table preservation throughout stack
            self.imin = data.min()
            self.imax = data.max()

            # if 2-D set maximum z to be 0
            if numpy.rank(data) == 2:
                zmax = 0
                # if 3-D go to same (current) plane or max available
            elif numpy.rank(data) == 3:
                if self.zpos > data.shape[2]:
                    self.zpos = data.shape[2] - 1
                zmax = data.shape[2] - 1
        
            # for later reference
            self.curname = name

            # make sure current item selected in Data window is correct
            if self.dataframe.GetCurrentItemText() != name:
                self.dataframe.SelectItem(name)

            # update maximum for z-slider in Views window
            self.viewsframe.zslider.SetMax( zmax )

            # continue working out if old x-y position
            # is appropriate for new data
            keepaxes = False
            if not old_lims is None:
                if numpy.all( numpy.array(old_lims).max(1) < data.shape[0:2] ):
                    keepaxes = True

            # update the screen image
            self.UpdateImage(keepaxes)


    def DelItem(self, name):
        '''Usage:  DelItem(name)
        
        Deletes item <name> from stack (and switches to last item if
        deleted item was the current one).'''
        if self.curname == name:
            lastname = self.ims.keys()[-1]
            if lastname != name:
                # if we're not deleting last (most recent) item
                # - switch to that one
                nextname = lastname
            else:
                # we are deleting last item
                if len( self.ims ) > 1:
                    # there is another one to switch to
                    nextname = self.ims.keys()[-2]
                else:
                    # we're deleting the only item
                    nextname = None
            self.SwitchToData( nextname  )
        self.dataframe.DelItem(name)
        del self.ims[name]
        

    def MaxProj(self, name):
        '''Usage:  MaxProj(name)

        Calculates a maximum z-projection of the data <name>
        and switches to make it the current data.'''
        maxprojx = self.ims[name].max(2)
        self.AddImg(maxprojx, name + ' (max projection)')


    def ClickHandler(self, evt):
        #if not (self.mainframe.toolbar.panmode or \
        #        self.mainframe.toolbar.zoomode):
        if self.mainframe.toolbar.mode == '':
            x = evt.xdata
            y = evt.ydata
            z = self.zpos
            if not self.clickcallback is None:
                print "To callback."
                self.clickcallback(x,y,z, *self.clickcallbackargs)
        
        
    def ConnectClickCallback(self, func, *args):
        '''Usage:  ConnectClickCallback(func, *args)

        Connects function <func> to respond to non-zoom, non-pan click
        events in the current canvas. func must have the signature:

        def <callbackfunc>(x,y,z, *args):
            ...

        where x,y,z are the click co-ordinates when inside the
        axes and None (x and y anyway) when click is outside and
        args are any other parameters the callback needs.'''
        print "Connecting", func.__name__
        self.clickcallback = func
        self.clickcallbackargs = args
        self.cid.append( self.mainframe.canvas.mpl_connect(\
            'button_press_event', self.ClickHandler) )


    def DisconnectClickCallback(self):
        '''Usage: DisconnectClickCallback()

        Disconnects all connected callback routines from click event.'''
        while len(self.cid) > 0:
            i = self.cid.pop()
            self.mainframe.canvas.mpl_disconnect(i)
                
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys - and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Reply via email to