[AG-TECH] shared image

Dave Semeraro semeraro at ncsa.uiuc.edu
Tue Sep 21 10:49:32 CDT 2004


Hi Techies,

I finally got around to finishing the new shared image viewer. The new 
viewer .py and .app files
can be found at the same web location as the old application. Be aware of 
the name
change however. There are several changes in the new application. Of 
particular note is it is not
backward compatible with the older version. The markup is stored in the 
venue in a different way.
Look and feel are the same however and there are a couple of added 
features. Read the brief
notes on the web site 
http://cherokee.ncsa.uiuc.edu/~semeraro/Stupid_AG_Tricks/Collaboration_via_AG.htm

I have attached the .py and .app file to this mail for those of you who 
dont want to get them from
the web. Let me know of any problems or issues.

Enjoy,

Dave


Dave Semeraro Ph.D.
Visualization and Virtual Environments Group
NCSA University of Illinois
605 E. Springfield Ave.
Champaign, IL 61820
Semeraro at ncsa.uiuc.edu
(217) 244-1852
-------------- next part --------------
"""

A shared image viewer application for the Access Grid.

This application allows the user to view and annotate image data in a
collaborative environment provided by the Access Grid Toolkit. This is
a major revision of the original AGBasicImage application. The
modifications made bring the application into conformance with the
AG2.3 environment. This new version of the application is modeled after
the sharedbrowser.py application distributed with the AG2.3 software.

AG Version - 2.3 

Dave Semeraro
NCSA - UIUC
2004

"""

#       Import the usual suspects
import os
import sys
import logging
import getopt
import string
from locale import atoi
#       Import the wxPython stuff
import wx
from wx.lib.imagebrowser import *
#       Import the AG stuff
from AccessGrid.SharedAppClient import SharedAppClient
from AccessGrid.Platform.Config import UserConfig
from AccessGrid.ClientProfile import ClientProfile
from AccessGrid import icons
from AccessGrid.Toolkit import WXGUIApplication
from AccessGrid.Toolkit import CmdlineApplication
from AccessGrid.DataStoreClient import GetVenueDataStore
from AccessGrid import Platform
#
#       grab some event ID's
#
ID_OPEN = wx.NewId()
ID_OPEN_VENUE = wx.NewId()
ID_CLEAR = wx.NewId()
ID_RELOAD = wx.NewId()
ID_EXIT = wx.NewId()
ID_ERASEVENUEMARKUP = wx.NewId()
ID_ERASEMARKUP = wx.NewId()
#
wildcard = "JPEG Files (*.JPG)|*.jpg|"\
           "Gif Files (*.GIF)|*.gif|"\
           "All Files (*.*)|*.*"

def _ConvertMarkupToString(markup):
        """
        Convert the markup data to a string for storage in the venue application.
        The markup exists locally as a list of tuples. The contents of this list is
        converted to a string of space separated entries.
        """
        tmpstrlist = []
        tmpstrlist.append(str(len(markup)))
        for marks in markup:
                tmpstrlist.append(marks[0])
                tmpstrlist.append(str(marks[1]))
                coordstrings = _CoordListToStringList(marks[2])
                tmpstrlist.append(str(len(coordstrings)))
                for item in coordstrings:
                        tmpstrlist.append(item)
                marksstring = string.join(tmpstrlist,' ')
        #print marksstring
        return marksstring

def _ConvertStringToMarkup(mstring):
        markslist = mstring.split()
        mark = 0
        i = 1
        markuplist = []
        while mark < atoi(markslist[0]):
                color = markslist[i]
                thickness = atoi(markslist[i+1])
                numcoords = atoi(markslist[i+2])
                i = i + 3
                tmpcoordstringlist = []
                for j in xrange(numcoords):
                        tmpcoordstringlist.append(markslist[i+j])        
                markuplist.append((color,thickness,_StringListToCoordList(tmpcoordstringlist)))
                i = i + numcoords
                mark = mark + 1
        #print markuplist
        return markuplist


def _CoordListToStringList(coordlist):
        """
        change a list of coordinates to a string. The input list contains
        tuples of coordinate pairs that constitute the end points of line
        segments. This list of tuples of tuples is changed to a space
        separated string of characters representing the coordinate values.
        for example [((624, 478),(625,477)), ((336,389),(367,389))] is
        mapped to ['624','478','625','477','336','389','367','389']
        """
        tmplist = []
        for segment in coordlist:
                for point in segment:
                        tmplist.append(str(point[0]))
                        tmplist.append(str(point[1]))
        return tmplist

def _StringListToCoordList(stringlist):
        """
        reverse the conversion from the coords to string
        """
        tmp = []
        for i in xrange(0,len(stringlist),4):
                x1 = atoi(stringlist[i])
                y1 = atoi(stringlist[i+1])
                x2 = atoi(stringlist[i+2])
                y2 = atoi(stringlist[i+3])
                tmp.append(((x1,y1),(x2,y2)))
        return tmp



class BIFileDropTarget(wx.FileDropTarget):
        def __init__(self,viewer):
                wx.FileDropTarget.__init__(self)
                self.viewer = viewer
        def OnDropFiles(self,x,y,filenames):
                self.viewer.log.info("loading image files %s " %filenames)
                for localImageFile in filenames:
                        self.viewer.window.LoadImageFromFilename(localImageFile)
                        venueImageFile =self.viewer.UploadFile(localImageFile)
                        file = os.path.split(localImageFile)[-1]
                        self.viewer.sharedAppClient.SetData(self.viewer.imagedataname,file)
                        self.viewer.sharedAppClient.SendEvent("NewImage",(self.viewer.PublicId,file))
                        self.viewer.frame.SetTitle(file)
                        self.viewer.frame.Refresh(True)


class ViewerFrame(wx.Frame):
        menuColours = { 200 : 'Black',
                        201 : 'Yellow',
                        202 : 'Red',
                        203 : 'Green',
                        204 : 'Blue',
                        205 : 'Purple',
                        206 : 'Brown'
                        }
# this doesnt do much but set up the menu. The events are mapped to another
# class
        def __init__(self,parent,ID):
                wx.Frame.__init__(self,parent,ID,"SharedImageViewer: no image loaded",size=(800,600),style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
                menu = wx.Menu()
                menu.Append(ID_OPEN,"&Open...","Open an image file")
                menu.Append(ID_OPEN_VENUE,"&Open from venue...","Open a venue image file")
                menu.Append(ID_CLEAR,"&Clear Image","Erase screen image and clear global image data")
                menu.Append(ID_RELOAD,"&Reload Image","Get the current image and markup from the venue")
                menu.AppendSeparator()
                menu.Append(ID_EXIT,"&Exit","Terminate with extreme prejudice")
                colormenu = wx.Menu()
                keys = self.menuColours.keys()
                keys.sort()
                for k in keys:
                       text = self.menuColours[k]
                       colormenu.AppendRadioItem(k,text)
                menubar = wx.MenuBar()
                menubar.Append(menu,"&File")
                menubar.Append(colormenu,"&Colors")
                settingmenu = wx.Menu()
                settingmenu.AppendCheckItem(ID_ERASEMARKUP,"Erase Local Markup","Erase local markup on loading image")
                settingmenu.AppendCheckItem(ID_ERASEVENUEMARKUP,"Erase Venue Markup","Erase global markup on loading image")
                menubar.Append(settingmenu,"&Settings")
                self.SetMenuBar(menubar)
                
#
#       Define the viewerwindow class here
#
class ViewerWindow(wx.Window):
        def __init__(self,parent,ID):
                wx.Window.__init__(self,parent,ID,style=wx.NO_FULL_REPAINT_ON_RESIZE)
                self.imagefile = None
                self.parentframe = parent
                self.image = None
                self.lines = []
                self.thickness = 1
                self.SetColour("Black")
                self.SetBackgroundColour("WHITE")
                self.InitBuffer()
                # bind some events some window events are bound in another place
                self.Bind(wx.EVT_IDLE, self.OnIdle)
                self.Bind(wx.EVT_SIZE, self.OnSize)
                self.Bind(wx.EVT_PAINT, self.OnPaint)
                self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
                self.Bind(wx.EVT_MOTION, self.OnMotion)

        def ClearWindow(self,event):
                self.lines = []
                self.image = None
                self.reInitBuffer = True
                self.Refresh(True)

        def InitBuffer(self):
                size = self.GetClientSize()
                if self.image == None:
                        self.buffer = wx.EmptyBitmap(size.width,size.height)
                        dc = wx.BufferedDC(None,self.buffer)
                        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
                        dc.Clear()
                else:
                        #self.Refresh(True)
                        self.buffer = self.image.ConvertToBitmap()
                        dc = wx.BufferedDC(None, self.buffer)
                        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
                self.DrawLines(dc)
                self.reInitBuffer = False
                self.Refresh(True)

        def LoadImageFromFilename(self,imagefilename):
                self.imagefile = imagefilename
                self.image = wx.Image(self.imagefile)
                self.reInitBuffer = True
                self.Refresh(True)

        def SetColour(self,colour):
                self.colour = colour
                self.pen = wx.Pen(wx.NamedColour(self.colour),self.thickness,wx.SOLID)

        def SetThickness(self,num):
                self.thickness = num
                self.pen = wx.Pen(wx.NamedColour(self.colour),self.thickness,wx.SOLID)

#        def LoadImage(self,animage):
#                imgdat = base64.decodestring(animage.data)
#                self.image = wx.EmptyImage(string.atoi(animage.width),string.atoi(animage.height))
#                self.image.SetData(imgdat)
#                self.reInitBuffer = True

        def OnIdle(self,event):
                if self.reInitBuffer:
                        self.InitBuffer()
                        self.Refresh(True)

        def OnSize(self,event):
                self.reInitBuffer = True

        def OnPaint(self,event):
                dc = wx.BufferedPaintDC(self,self.buffer)

        def OnLeftDown(self,event):
                self.curLine = []
                self.x, self.y = event.GetPositionTuple()
                self.CaptureMouse()

        def OnLeftUp(self):
                if self.HasCapture():
                        self.lines.append((self.colour, self.thickness, self.curLine))
                        self.ReleaseMouse()
                        #print len(self.lines),self.lines[0:]

        def GetCurrentMarks(self):
                return (self.colour, self.thickness, self.curLine)

        def DrawLines(self,dc):
                dc.BeginDrawing()
                for colour, thickness, line in self.lines:
                        pen = wx.Pen(wx.NamedColour(colour),thickness,wx.SOLID)
                        dc.SetPen(pen)
                        for coords in line:
                                apply(dc.DrawLine, coords)
                dc.EndDrawing()
        def OnMotion(self,event):
                if event.Dragging() and event.LeftIsDown():
                        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
                        dc.BeginDrawing()
                        dc.SetPen(self.pen)
                        pos = event.GetPositionTuple()
                        coords = ((self.x,self.y), pos)
                        self.curLine.append(coords)
                        dc.DrawLine((self.x,self.y),pos)
                        self.x,self.y = pos
                        dc.EndDrawing()

#
#       Define the shared image viewer class here
#
class SharedImageViewer( wx.App ):
        def OnInit(self):
                return 1

        def OnExit(self,event):
                self.sharedAppClient.Shutdown()
                self.frame.Close(True)
                os._exit(1)

        def __init__(self, appUrl, venueUrl, name):
                wx.App.__init__(self, False)
                self.sharedAppClient = SharedAppClient(name)
                self.log = self.sharedAppClient.InitLogging()
                self.markupdataname = "MarkupData"
                self.imagedataname = "ImageFile"
                self.EraseLocalMarkupOnLoad = False
                self.EraseVenueMarkupOnLoad = False
                # Get a Client Profile
                try:
                        clientProfileFile = os.path.join(UserConfig.instance().GetConfigDir(), "profile")
                        clientProfile = ClientProfile(clientProfileFile)
                except:
                        self.log.info("SharedAppClient.Connect: Could not load client profile, set clientProfile = None")
                        clientProfile = None

                self.sharedAppClient.Join(appUrl,clientProfile)
                self.PublicId = self.sharedAppClient.GetPublicId()
                try:
                        self.dataStoreClient = GetVenueDataStore(venueUrl)
                except:
                        print "Exception:", sys.exc_type, sys.exc_value
                        import traceback
                        traceback.print_stack()

                self.sharedAppClient.RegisterEventCallback("NewImage",self.HandleNewImage)
                self.sharedAppClient.RegisterEventCallback("NewMarkup",self.HandleNewMarkup)
                self.frame = ViewerFrame(None,-1)
                self.window = ViewerWindow(self.frame, -1)
                self.window.Bind(wx.EVT_LEFT_UP,self.ShareMarkup)
                self.frame.Bind(wx.EVT_MENU,self.On_Open,id=ID_OPEN)
                self.frame.Bind(wx.EVT_MENU,self.On_OpenVenue,id=ID_OPEN_VENUE)
                self.frame.Bind(wx.EVT_MENU,self.OnExit,id=ID_EXIT)
                self.frame.Bind(wx.EVT_MENU,self.window.ClearWindow,id=ID_CLEAR)
                self.frame.Bind(wx.EVT_MENU,self.Reload,id=ID_RELOAD)
                self.frame.Bind(wx.EVT_MENU,self.SetEraseLocalMarkup,id=ID_ERASEMARKUP)
                #self.frame.Bind(wx.EVT_MENU,self.SetEraseVenueMarkup,id=ID_ERASEVENUEMARKUP)
                keys = self.frame.menuColours.keys()
                for k in keys:
                        self.frame.Bind(wx.EVT_MENU,self.OnMenuSetColour,id=k)
                dt = BIFileDropTarget(self)
                self.window.SetDropTarget(dt)
                # see if there is an image in the venue and load it up
                animage = self.sharedAppClient.GetData(self.imagedataname)
                if animage == "":
                        self.log.info("SharedAppViewer: no image in venue")
                else:
                        self.log.info("SharedAppViewer: initial image found")
                        localImageFile = self.DownloadFile(animage)
                        self.window.LoadImageFromFilename(localImageFile)
                        title = os.path.split(localImageFile)[-1]
                        self.frame.SetTitle(title)
                # see if there is markup in the venue and load it up
                scribble = self.sharedAppClient.GetData(self.markupdataname)
                if scribble == "":
                        self.log.info("SharedAppViewer: no markup in venue")
                else:
                        self.window.lines = _ConvertStringToMarkup(scribble)
                self.frame.Show(1)
                self.SetTopWindow(self.frame)

        def SetEraseLocalMarkup(self,event):
                if event.IsChecked():
                        self.EraseLocalMarkupOnLoad = True
                else:
                        self.EraseLocalMarkupOnLoad = False

        def SetEraseVenueMarkup(self,event):
                if event.IsChecked():
                        self.EraseVenueMarkupOnLoad = True
                else:
                        self.EraseVenueMarkupOnLoad = False

        def Reload(self,event):
                self.sharedAppClient.UpdateDataCache()
                animage = self.sharedAppClient.GetData(self.imagedataname)
                if animage == "":
                        self.log.info("SharedAppViewer: no image in venue")
                else:
                        self.log.info("SharedAppViewer: reloading image %s" %animage)
                        localImageFile = self.DownloadFile(animage)
                        self.window.LoadImageFromFilename(localImageFile)
                        title = os.path.split(localImageFile)[-1]
                        self.frame.SetTitle(title)
                # see if there is markup in the venue and load it up
                scribble = self.sharedAppClient.GetData(self.markupdataname)
                if scribble == "":
                        self.log.info("SharedAppViewer: no markup in venue")
                else:
                        self.window.lines = _ConvertStringToMarkup(scribble)
                        self.window.Refresh(True)
                                        

        def On_Open(self,event):
                dir = os.getcwd()
                dlg = ImageDialog(self.frame,dir)
                dlg.Centre()
                if dlg.ShowModal() == wx.ID_OK:
                        if self.EraseLocalMarkupOnLoad: self.window.lines = []
                        localImageFile = dlg.GetFile()
                        self.window.LoadImageFromFilename(localImageFile)
                        venueImageFile =self.UploadFile(localImageFile)
                        file = os.path.split(localImageFile)[-1]
                        self.sharedAppClient.SetData(self.imagedataname,file)
                        self.sharedAppClient.SendEvent("NewImage",(self.PublicId,file))
                        self.frame.SetTitle(file)
                        self.frame.Refresh(True)

        def On_OpenVenue(self,event):
                self.dataStoreClient.LoadData()
                dlg = wx.SingleChoiceDialog(self.frame,"Select an image file to load", "Load Venue Image Dialog",
                                                self.dataStoreClient.dataIndex.keys())

                if dlg.ShowModal() == wx.ID_OK:
                        if self.EraseLocalMarkupOnLoad: self.window.lines = []
                        venueImageFile = dlg.GetStringSelection()
                        localImageFile = self.DownloadFile(venueImageFile)
                        self.sharedAppClient.SetData(self.imagedataname, venueImageFile)
                        file = os.path.split(localImageFile)[-1]
                        self.sharedAppClient.SendEvent("NewImage",(self.PublicId,file))
                        self.window.LoadImageFromFilename(localImageFile)
                        title = os.path.split(localImageFile)[-1]
                        self.frame.SetTitle(title)

        def UploadFile(self,localFile):
                # first see if a file of the same name is already there
                filename = ""
                filename = self.dataStoreClient.QueryMatchingFiles(localFile)
                if len(filename) == 0:
                        try:
                                self.dataStoreClient.Upload(localFile)
                        except:
                                self.log.exception("failed to upload %s" %localFile)
                        file = os.path.split(localFile) [-1]
                        venueDataUrl = os.path.join(self.dataStoreClient.uploadURL,file)
                        return venueDataUrl
                else:
                        self.log.info("file %s is already in the venue" %localFile)

        def DownloadFile(self,venueDataUrl):
                file = os.path.split(venueDataUrl) [-1]
                self.dataStoreClient.LoadData()
                self.dataStoreClient.Download(venueDataUrl,file)
                return file

        def HandleNewImage(self,eventdata):
                (senderId,venueImageFile) = eventdata.data
                if senderId != self.sharedAppClient.GetPublicId():
                        try:
                                localImageFile = self.DownloadFile(venueImageFile)
                                self.window.LoadImageFromFilename(localImageFile)
                        except:
                                self.log.exception("SharedImageViewer: could not load image file from venue")

        def ShareMarkup(self,eventdata):
                """
                Share the markup with the other instances. This is done
                in two ways. First the total markup store in the venue is
                updated and then the markup is sent in the event data for
                all to enjoy. The venue based markup is only accessed when
                an application first joins an ongoing session.
                """
                self.window.OnLeftUp()
                # refresh the data cache
                self.sharedAppClient.UpdateDataCache(self.markupdataname)
                # retrieve the markup that is stored in the venue
                scribble = self.sharedAppClient.GetData(self.markupdataname)
                if scribble == "":
                        self.log.info("SharedImageViewer: no markup in venue")
                        markup = []
                else:
                        markup = _ConvertStringToMarkup(scribble)
                markup.append(self.window.GetCurrentMarks())
                self.sharedAppClient.SetData(self.markupdataname,_ConvertMarkupToString(markup))
                self.sharedAppClient.SendEvent("NewMarkup",(self.PublicId,self.window.GetCurrentMarks()))
                
        def OnMenuSetColour(self,eventdata):
                self.window.colour = self.frame.menuColours[eventdata.GetId()]
                self.window.pen = wx.Pen(wx.NamedColour(self.window.colour),self.window.thickness,wx.SOLID)


        def HandleNewMarkup(self,eventdata):
                (senderId,markslist) = eventdata.data
                if senderId != self.sharedAppClient.GetPublicId():
                        try:
                                self.window.lines.append((markslist[0],markslist[1],markslist[2]))
                                self.window.reInitBuffer = True
                                self.window.Refresh(True)
                        except:
                                self.log.exception("SharedImageViewer: whacked markup import")

class ArgumentManager:
        """
        stolen from the shared browser app and modified slightly for our use
        """
        def __init__(self):
                self.arguments = {}
                self.arguments['applicationUrl'] = None
                self.arguments['venueUrl'] = None
                self.arguments['debug'] = 0

        def GetArguments(self):
                return self.arguments

        def Usage(self):
                print "%s:" % sys.argv[0]
                print "     -a|--applicationURL : <url to application in venue>"
                print "     -v|--venueURL : <url to venue>"
                print "     -d|--debug : <enables debug output>"
                print "     -h|--help : <print usage>"

        def ProcessArgs(self):
                try:
                        opts,args = getopt.getopt(sys.argv[1:], "a:v:d:h",
                                 ["applicationURL=","venueURL=","debug","help"])
                except getopt.GetoptError:
                        self.Usage()
                        sys.exit(2)
                for o, a in opts:
                        if o in ("-a", "--applicationURL"):
                                self.arguments["applicationUrl"] = a
                        elif o in ("-v", "--venueURL"):
                                self.arguments["venueUrl"] = a
                        elif o in ("-d", "--debug"):
                                self.arguments["debug"] = 1
                        elif o in ("-h", "--help"):
                                self.Usage()
                                self.exit(0)

if __name__ == "__main__":
        app = WXGUIApplication()
        #app = CmdlineApplication.instance()
        name = "SharedImageViewer"
        am = ArgumentManager()
        am.ProcessArgs()
        aDict = am.GetArguments()
        appUrl = aDict['applicationUrl']
        venueUrl = aDict['venueUrl']
        debugMode = aDict['debug']
        init_args = []
        if "--debug" in sys.argv or "-d" in sys.argv:
                init_args.append("--debug")
        #app.Initialize(name)
        app.Initialize(name,args=init_args)
        if not appUrl:
                am.Usage()
        elif not venueUrl:
                am.Usage()
        else:
        #appUrl = "https://voyager.ncsa.uiuc.edu:9000/Venues/default/apps/000000ff1ca76bc4008d008e0040000b437"
        #venueUrl = "https://voyager.ncsa.uiuc.edu:9000/Venues/default"
                wx.InitAllImageHandlers()
                iv = SharedImageViewer(appUrl,venueUrl,name)
                iv.MainLoop()

-------------- next part --------------
[application]
name = Shared Image Viewer
mimetype = application/x-ag-shared-image-viewer
extension = sharedimageviewer
files = SharedImageViewer.py

[commands]
Open = %(python)s SharedImageViewer.py  -a %(appUrl)s -v %(venueUrl)s


More information about the ag-tech mailing list