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 semer...@ncsa.uiuc.edu (217) 244-1852
""" 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()