AccessGrid.org

Developer Example: Shared Image Viewer


SharedImageViewer

SharedImageViewer is a sample shared application for collaborative viewing of images. One user can load an image into SharedImageViewer, and all other users in the same SharedImageViewer session will download and view the image. This is a very simple shared application written to demonstrate the collaborative facilities included in the Access Grid software.


The SharedImageViewer application in action


Application Structure


Events

The application uses two events to indicate that an image has been and should be loaded, and that an image should be shown.

LOAD_IMAGE
SHOW_IMAGE

Application State

The state of the application consists only of images that have been loaded.

imagelist = [ filenames ]

Code Review

ImageViewer.py (link)

 

OnAddImages

Loads the image files, builds thumbnails, and displays them

 

 

def OnAddImages(self,event=None):
dialog = wx.FileDialog(self,style=wx.MULTIPLE)
ret = dialog.ShowModal()
if ret == wx.ID_OK:
filenames = dialog.GetPaths()
for f in filenames:
wx.CallAfter(self.OpenImage,f)
dialog.Destroy()

 

OnImageSelect

Displays the selected image in the main panel

def OnImageSelect(self,event):
# Determine the image selected and show it
o = event.GetEventObject()
image = self.buttonImageMapping[o]
self.ShowImage(image)

 

SharedImageViewer.py (link)

SharedImageViewer is derived from ImageViewer
Overrides ImageViewer methods to extend them

Registers for shared application events

# Register event callbacks
self.sharedAppClient.RegisterEventCallback(self.LOAD_IMAGE, self.HandleLoadImageEvent)
self.sharedAppClient.RegisterEventCallback(self.SHOW_IMAGE, self.HandleShowImageEvent)

OnAddImages (called when AddImages button is pressed)

Loads the image files, builds thumbnails, and displays them
Sends a LOAD_IMAGE event
Updates the application state 'imagelist' key

def OnAddImages(self):
try:
dialog = wx.FileDialog(self,style=wx.MULTIPLE)
ret = dialog.ShowModal()
if ret == wx.ID_OK:
filenames = dialog.GetPaths()
dialog.Destroy()
for f in filenames:
wx.CallAfter(self.OpenImage,f)

# upload the file to venue
self.dataStoreClient.Upload(f)

# send event notifying others to upload
filename = os.path.basename(f)
self.sharedAppClient.SendEvent(SharedImageViewer.LOAD_IMAGE,filename)

# update the application state
keys= self.sharedAppClient.GetDataKeys()
filelist = map( lambda x: os.path.basename(x), filenames)
if SharedImageViewer.imagelist not in keys:
self.sharedAppClient.SetData(SharedImageViewer.imagelist,filelist)
else:
myfileliststr = self.sharedAppClient.GetData(SharedImageViewer.imagelist)
myfilelist = eval(myfileliststr)
myfilelist += filelist
self.sharedAppClient.SetData(SharedImageViewer.imagelist,myfilelist)

except:
import traceback
traceback.print_exc()
self.log.exception('Error in OnLoad')

HandleLoadImageEvent (called when a LOAD_IMAGE event is received)

Downloads the specified image from the Venue and displays it

def HandleLoadImageEvent(self, event):
filename = None
try:
if event.senderId != self.publicId:
filename = event.GetData()
self.DownloadAndOpenImage(filename)
except:
import traceback
traceback.print_exc()
self.log.exception('Error handling LoadImage event')
wx.MessageDialog('Error loading file '+filename)

OnImageSelect (called when thumbnail is selected)

Displays the selected image in the main panel
Sends a SHOW_IMAGE event

def OnImageSelect(self,event):
try:
o = event.GetEventObject()
image = self.buttonImageMapping[o]
self.ShowImage(image)
name = None
for k,v in self.nameImageMapping.items():
if v == image:
name = k
if name:
self.sharedAppClient.SendEvent(SharedImageViewer.SHOW_IMAGE,name)
else:
print 'Image not found in mapping'
except:
import traceback
traceback.print_exc()
self.log.exception('Error in OnImageSelect')

HandleShowImageEvent (called when a SHOW_IMAGE event is received)

Displays the specified image

def HandleShowImageEvent(self, event):
try:
filename = event.GetData()
print 'GotShowImage event for image: ', filename
if filename in self.nameImageMapping.keys():
self.ShowImage(self.nameImageMapping[filename])
except:
import traceback
traceback.print_exc()
self.log.exception('Error handling ShowImage event')
wx.MessageDialog('Error showing file '+filename)


Execution

SharedImageViewer requires, as do all shared applications, a pointer to the shared application. Also, because it interacts with the Venue Data Store, it needs the connection id of a client in the Venue. This is passed on the command line when the VenueClient executes SharedImageViewer; for example:

pythonw SharedImageViewer.py -c <connection_id> -a <application URL>

Conclusion

Shared image viewing is a powerful way for remote collaborators to share the results of their work. For scientists and non-scientists alike, being able to trivially display images to remote colleagues facilitates the flow of information and discussion in a way that approximates 'being there'. One could argue that this functionality should actually be integrated directly into the Venue Client.

Suggested Further Work

  • Support removal of images from session
  • Track local mouse movements and display remotely, to allow users to indicate areas of images with the mouse to remote users
  • Support markup of images
  • Support different layouts, so multiple images can be displayed large for comparison (shared canvas)
  • Support more than just images. Could support movies very easily (using wxMediaCtrl). Technically, this could be a shared launcher for any type of file.

Supporting Code

To be used as a proper shared application, SharedImageViewer requires also a SharedImageViewer.app file, and to be zipped up in a bundle as SharedImageViewer.zip.

SharedImageViewer.app

SharedImageViewer.py

SharedImageViewer.zip

login or register to post comments