[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