[AG-TECH] New Shared Image tool

Dave Semeraro semeraro at ncsa.uiuc.edu
Mon Apr 12 10:18:40 CDT 2004


Hello folks,

I finally finished adding markup capability to the shared image viewer. 
That means that once
an image is loaded you can use the mouse and left mouse button to draw on 
the image
with a pen. Click the left mouse button and hold it down while you draw on 
the image. When
you release the left button the coordinates of what you drew are uploaded 
to the venue and
all the other image viewers are updated. You will just see the image change 
to include the
marks made by the user. The marks do not show up incrementally. Hey it was 
the easiest
implementation.

I also added the capability to change pen color. This is handy when others 
are marking on
the image also. There is a pull down menu on the menu bar for this. You can 
change colors
at any time. I also added a different image loading dialog for local 
images. This one gives
a preview of the image before you select it. The viewer also stores the 
current cumulative
markup state in the venue data object. So when users arrive late they get 
the current markup
state also. There has been no attempt to handle race conditions so what 
happens when two
people change the image markup at exactly the same time is not determined. 
Some of the
markup may be lost. I have not tried it so I dont know what will happen.

The new viewer is backward compatible with the old viewer but old viewers 
will not see the
markup data. At least that is how I think it will work. I have tested the 
viewer in windows and
Linux (redhat 7.3 with kde desktop) and it works cross platform.

I have attached a copy of the new application to this mail. To update your 
existing viewer simply
replace the existing AGBasicImage.py with the one I hvae attached. 
Otherwise follow the
installation instructions in the installation and use section of my AG webpage
http://cherokee.ncsa.uiuc.edu/~semeraro/Stupid_AG_Tricks/default.htm

I will be updating the use part of those pages in a couple of days. The 
installation will remain
identical since I am calling the application the same thing. I have not 
updated the link to the
software on the web so if you download it from there today you will get the 
old version. Have
fun and let me know if things dont work for you.

Regards,
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 --------------
"""
This is the Access Grid Enabled version of the Basic Image viewer. It does the same
thing as the Basic Image ( load an image into a window ) but will share the image over
the Access Grid. Again there is no way to pan or scroll the window. This is just an
attempt to get the BasicImage viewer to work over the AG. The scheme is that the script is executed 
and given an application URL... like this AGBasicImage appurl. There is no error handling in
this version.

Dave Semeraro
NCSA-UIUC
2003
"""

"""
Modifications:
-   Change state from image data stored in the appobject to the name of a 
    venue-resident image file
-   If a local file is opened, it is transferred to the venue
-   add drag and drop
-   add markup
-   add color menu selection and new local image browser
"""

# generic and gui related imports
import os
import sys
from wxPython.wx import *
from wxPython.lib.imagebrowser import *
import base64
import cPickle
import string

# AG related imports
import logging
from AccessGrid.hosting.pyGlobus import Client
from AccessGrid import Events
from AccessGrid import EventClient
from AccessGrid import Platform
from AccessGrid.DataStoreClient import GetVenueDataStore


ID_OPEN = wxNewId()
ID_OPEN_VENUE = wxNewId()
ID_EXIT = wxNewId()

wildcard = "JPEG Files (*.JPG)|*.jpg|"\
           "Gif Files (*.GIF)|*.gif|"\
           "All Files (*.*)|*.*"
class BIFileDropTarget(wxFileDropTarget):
        def __init__(self,window):
                wxFileDropTarget.__init__(self)
                self.window = window
        def OnDropFiles(self,x,y,filenames):
                for file in filenames:
                        self.window.wind.LoadImageFromFilename(file)
                        # load up the dropped file into venue
                        self.window.AG.UploadFile(file)
                        self.window.AG.PutData(self.window.imagedataname,os.path.split(file)[1])
                        # fire event telling all there is new data
                        self.window.AG.SendEvent("NewImage",os.path.split(file)[1])

# this is an image holder object.. guess what it does. 
class ImageHolder:
    def __init__(self,image,name):
        self.imagename = name
        self.width = str(image.GetWidth())
        self.height = str(image.GetHeight())
        self.data = base64.encodestring(image.GetData())

# A helper class to hide all the AG ugly 
class AGSharedObject:
    def __init__(self,url,venueUrl):
        self.logname = "AGBasicImagelog"
        self.appUrl = None
        self.appName        = "Basic Image"
        self.appDescription = "Access Grid Shared Image"
        self.appMimetype    = "application/x-ag-basic-image"
        self.init_logging(self.logname)

        self.log.debug("AGSharedObject: initialize with url = %s ", url)
        self.appUrl = url
        self.log.debug("AGSharedObject: Getting application proxy")
        self.appProxy = Client.Handle(self.appUrl).GetProxy()
        self.log.debug("AGSharedObject: join application")
        (self.puid,self.prid) = self.appProxy.Join()
        self.log.debug("AGSharedObject: get data channel")
        (self.chanid,self.esl) = self.appProxy.GetDataChannel(self.prid)
        self.eventClient = EventClient.EventClient(self.prid,self.esl,self.chanid)
        self.eventClient.Start()
        self.eventClient.Send(Events.ConnectEvent(self.chanid,self.prid))
        self.log.debug("AGSharedObject: connected and event channel started")
        
        self.dataStoreClient = GetVenueDataStore(venueUrl)

    def init_logging(self,logname):
        logFormat = "%(name)-17s %(asctime)s %(levelname)-5s %(message)s"
        self.log = logging.getLogger(logname)
        self.log.setLevel(logging.DEBUG)
        self.log.addHandler(logging.StreamHandler())

    def GetData(self,dataname):
        self.log.debug("looking for data: %s", dataname)
        tim = self.appProxy.GetData(self.prid,dataname)
        if len(tim) > 0:
            return tim
        else:
            return None

    def PutData(self,dataname,data):
        self.log.debug("loading data named: %s into server",dataname)
        self.appProxy.SetData(self.prid,dataname,data)

    def RegisterEvent(self,eventname,callback):
        self.log.debug("Registering event %s :",eventname)
        self.eventClient.RegisterCallback(eventname,callback)

    def SendEvent(self,eventname,eventdata):
        self.eventClient.Send(Events.Event(eventname,self.chanid,(self.puid,eventdata)))
        
    def UploadFile(self,localFile):

        # Upload the file
        self.dataStoreClient.Upload(localFile)
        
        # Construct the venue data url
        file = os.path.split(localFile)[-1]
        venueDataUrl = os.path.join(self.dataStoreClient.uploadURL, file)
        
        return venueDataUrl
        
    def DownloadFile(self,venueDataUrl):
        # Construct the local filename
        file = os.path.split(venueDataUrl)[-1]
        path = os.path.join(Platform.GetTempDir(),file)
        
        # Update the local store of venue data
        self.dataStoreClient.LoadData()
        
        # Download the file
        self.dataStoreClient.Download(venueDataUrl,file)
        
        return file

class ImageWindow(wxWindow):
    def __init__(self,parent,ID):
        wxWindow.__init__(self,parent,ID,style=wxNO_FULL_REPAINT_ON_RESIZE)
        self.imagefile = None
        self.parentframe = parent
        self.image = None
        self.lines = []
        self.thickness = 1
        self.SetColour("Red")
        self.SetBackgroundColour("WHITE")
        self.InitBuffer()
        EVT_IDLE(self,self.OnIdle)
        EVT_SIZE(self,self.OnSize)
        EVT_PAINT(self,self.OnPaint)
        # mouse event hooks
        EVT_LEFT_DOWN(self, self.OnLeftDown)
        EVT_LEFT_UP(self, self.OnLeftUp)
        EVT_MOTION(self, self.OnMotion)
        
    def InitBuffer(self):
        size = self.GetClientSize()
        if self.image == None:
            self.buffer = wxEmptyBitmap(size.width,size.height)
            dc = wxBufferedDC(None,self.buffer)
            dc.SetBackground(wxBrush(self.GetBackgroundColour()))
            dc.Clear()
        else:
            self.Clear()
            self.buffer = self.image.ConvertToBitmap()
            dc = wxBufferedDC(None,self.buffer)
            dc.SetBackground(wxBrush(self.GetBackgroundColour()))
        self.DrawLines(dc)
        self.reInitBuffer = false

    def LoadImageFromFilename(self,imagefilename):
        self.lines = []
        self.ClearVenueMarkup()
        self.imagefile = imagefilename
        self.image = wxImage(self.imagefile)
        self.reInitBuffer = true
        self.Refresh(true)

    def SetColour(self,colour):
        self.colour = colour
        self.pen = wxPen(wxNamedColour(self.colour),self.thickness,wxSOLID)

    def SetThickness(self,num):
        self.thickness = num
        self.pen = wxPen(wxNamedColour(self.colour),self.thickness,wxSOLID)

    def LoadImage(self,animage):
        imgdat = base64.decodestring(animage.data)
        self.image = wxEmptyImage(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(FALSE)

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

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

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

    def OnLeftUp(self,event):
        if self.HasCapture():
                self.parentframe.AG.SendEvent("NewMarks",(self.colour,self.thickness,self.curLine))
                self.lines.append((self.colour, self.thickness, self.curLine))
                self.UploadMarks((self.colour,self.thickness,self.curLine))
                self.ReleaseMouse()

    def UploadMarks(self,marks):
        scribble = self.parentframe.AG.GetData(self.parentframe.markupdataname)
        if scribble == None:
                markup = []
        else:
                markup = cPickle.loads(scribble)
        markup.append(marks)
        self.parentframe.AG.PutData(self.parentframe.markupdataname,cPickle.dumps(markup,0))

    def ClearVenueMarkup(self):
        markup = []
        self.parentframe.AG.PutData(self.parentframe.markupdataname,cPickle.dumps(markup,0))

    def LoadMarkup(self,data):
        self.lines = cPickle.loads(data)

    def OnMotion(self,event):
        if event.Dragging() and event.LeftIsDown():
                dc = wxBufferedDC(wxClientDC(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[0],pos[1])
                self.x, self.y = pos
                dc.EndDrawing()

    def GetLinesData(self):
        return self.lines[:]

    def SetLinesData(self,lines):
        self.lines = lines[:]
        self.InitBuffer()
        self.Refresh()

    def DrawLines(self,dc):
        dc.BeginDrawing()
        for colour, thickness, line in self.lines:
                pen = wxPen(wxNamedColour(colour),thickness,wxSOLID)
                dc.SetPen(pen)
                for coords in line:
                        apply(dc.DrawLine, coords)
        dc.EndDrawing()
        
class ImageFrame(wxFrame):
    menuColours = { 200 : 'Black',
                    201 : 'Yellow',
                    202 : 'Red',
                    203 : 'Green',
                    204 : 'Blue',
                    205 : 'Purple',
                    206 : 'Brown'
                    }
    def __init__(self,parent,ID):
        wxFrame.__init__(self,parent,ID,"AGImage: no image loaded",size=(800,600),
                         style=wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE)
        menu = wxMenu()
        menu.Append(ID_OPEN,"&Open...","Open an image file")
        menu.Append(ID_OPEN_VENUE,"&Open from venue...","Open a venue image file")
        menu.AppendSeparator()
        menu.Append(ID_EXIT,"&Exit","Terminate with extreme prejudice")
        colormenu = wxMenu()
        keys = self.menuColours.keys()
        keys.sort()
        for k in keys:
                text = self.menuColours[k]
                colormenu.AppendRadioItem(k,text)
                EVT_MENU(self,k,self.OnMenuSetColour)
        menubar = wxMenuBar()
        menubar.Append(menu,"&File")
        menubar.Append(colormenu,"&Colors")
        self.SetMenuBar(menubar)

        EVT_MENU(self,ID_OPEN, self.On_Open)
        EVT_MENU(self,ID_OPEN_VENUE, self.On_OpenVenue)
        EVT_MENU(self,ID_EXIT, self.On_Exit)

        # start the ag stuff and see if there is an image already there
        self.imagedataname = "ImageFile"
        self.markupdataname = "MarkupData"
        dt=BIFileDropTarget(self)
        self.SetDropTarget(dt)
        self.AG = AGSharedObject(sys.argv[1], sys.argv[2])
        self.AG.RegisterEvent("NewImage",self.HandleNewImage)
        self.AG.RegisterEvent("NewMarks",self.HandleNewMarks)
        self.wind = ImageWindow(self,-1)
        animage = self.AG.GetData(self.imagedataname)
        scribble = self.AG.GetData(self.markupdataname)
        if animage == None:
            print "No image found in server"
        else:
            print "Loading image: ", animage
            localImageFile = self.AG.DownloadFile(animage)
            self.LoadImageFromFilename(localImageFile)
        if scribble == None:
            print "No markup found in server"
        else:
            print "Loading markup"
            self.wind.LoadMarkup(scribble)

    def OnMenuSetColour(self,event):
        self.wind.SetColour(self.menuColours[event.GetId()])
    def On_Open(self,event):
        dir = os.getcwd()
#        dlg = wxFileDialog(self,"Select An Image", os.getcwd(), "",wildcard,wxOPEN)
        dlg = ImageDialog(self,dir)
        if dlg.ShowModal() == wxID_OK:
        
            # Load the image locally
#            localImageFile = dlg.GetPath()
            localImageFile = dlg.GetFile()
            
            # Push the data to the venue server
            # - copy the image to the venue server
            venueImageFile = self.AG.UploadFile(localImageFile)
            file = os.path.split(localImageFile)[-1]
            
            # - store the image url in the app object
            self.AG.PutData(self.imagedataname, file)
            
            # Load the image locally and erase the current markup
            self.LoadImageFromFilename(localImageFile)
            
            # Now fire the event that tells everyone else the image is changed
            self.AG.SendEvent("NewImage",file)

    def On_OpenVenue(self,event):
    
        self.AG.dataStoreClient.LoadData()
    
        # Let user select a venue file
        dlg = wxSingleChoiceDialog( self, "Select an image file to load", "Load Venue Image Dialog", 
                                    self.AG.dataStoreClient.dataIndex.keys() )


        if dlg.ShowModal() == wxID_OK:
        
            venueImageFile = dlg.GetStringSelection()

            # Download the file locally
            localImageFile = self.AG.DownloadFile(venueImageFile)
            
            # Store the image url in the app object
            self.AG.PutData(self.imagedataname, venueImageFile)
            
            # Now fire the event that tells everyone else the image is changed
            file = os.path.split(localImageFile)[-1]
            self.AG.SendEvent("NewImage",file)

            # Load it
            self.LoadImageFromFilename(localImageFile)
        
    def HandleNewImage(self,eventdata):
        # received notification that there is a new image on the server
        (senderId,venueImageFile) = eventdata.data
        
        if senderId != self.AG.puid:
          try:
            # Download the file and erase current markup
            localImageFile = self.AG.DownloadFile(venueImageFile)
                        
            # Load the image
            self.LoadImageFromFilename(localImageFile)
            
          except:
            print "EXCEPTION : ", sys.exc_type, sys.exc_value
            import traceback
            traceback.print_stack()

    def HandleNewMarks(self,eventdata):
        (senderId,marklist) = eventdata.data
        if senderId != self.AG.puid:
          try:
             # append the marklist to the line list
             #self.wind.SetColour(marklist[0])
             #self.wind.thickness = marklist[1]
             self.wind.lines.append((marklist[0],marklist[1],marklist[2]))
             self.wind.reInitBuffer = true
             self.wind.Refresh(true)
          except:
             print "EXCEPTION : ", sys.exc_type, sys.exc_value
             import traceback
             traceback.print_stack()

    def LoadImageFromFilename(self,filename):
        # Load the image
        self.wind.LoadImageFromFilename(filename)
        
        # Set the window title
        title = os.path.split(filename)[-1]
        self.SetTitle(title)

    def On_Exit(self,event):
        self.Close(true)

class MyApp(wxApp):
    def OnInit(self):
        wxInitAllImageHandlers()
        frame = ImageFrame(None,-1)
        frame.Show(true)
        self.SetTopWindow(frame)
        return true

app = MyApp(0)
app.MainLoop()


More information about the ag-tech mailing list