[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