Previous Up Next Tutorial

Quick Start Base Class

The distribution contains a demo directory which contains demonstrations for the wxPython, Tkinter and pyGTK windows toolkits. These demos use a common base class and a specific sub-class for the target windows toolkit.


Example : simple_base.py


import twain
import traceback, sys
import os, os.path

XferByFile='File'
XferNatively='Natively'

tmpfilename="tmp.bmp"
OverrideXferFileName = 'c:/twainxfer.jpg'

class CannotWriteTransferFile(Exception):
    pass
    

class TwainBase:
    """Simple Base Class for twain functionality. This class should
    work with all the windows librarys, i.e. wxPython, pyGTK and Tk.
    """

    SM=None                        # Source Manager
    SD=None                        # Data Source
    ProductName='SimpleTwainDemo'  # Name of this product
    XferMethod = XferNatively      # Transfer method currently in use
    AcquirePending = False         # Flag to indicate that there is an acquire pending
    mainWindow = None              # Window handle for the application window

    # Methods to be implemented by Sub-Class
    def LogMessage(self, message):
        print "****LogMessage:", message

    def DisplayImage(self, ImageFileName):
        """Display the image from a file"""
        print "DisplayImage:", message

    # End of required methods


    def Initialise(self):
        """Set up the variables used by this class"""
        (self.SD, self.SM) = (None, None)
        self.ProductName='SimpleTwainDemo'
        self.XferMethod = XferNatively
        self.AcquirePending = False
        self.mainWindow = None

    def Terminate(self):
        """Destroy the data source and source manager objects."""
        if self.SD: self.SD.destroy()
        if self.SM: self.SM.destroy()
        (self.SD, self.SM) = (None, None)

    def OpenScanner(self, mainWindow=None, ProductName=None, UseCallback=False):
        """Connect to the scanner"""
        if ProductName: self.ProductName = ProductName
        if mainWindow: self.mainWindow = mainWindow
        if not self.SM:
            self.SM = twain.SourceManager(self.mainWindow, ProductName=self.ProductName)
        if not self.SM:
            return
        if self.SD:
            self.SD.destroy()
            self.SD=None
        self.SD = self.SM.OpenSource()
        if self.SD:
            self.LogMessage(self.ProductName+': ' + self.SD.GetSourceName())

        if UseCallback:
            self.SM.SetCallback(self.OnTwainEvent)
    
    def _Acquire(self):
        """Begin the acquisition process. The actual acquisition will be notified by 
        either polling or a callback function."""
        if not self.SD:
            self.OpenScanner()
        if not self.SD: return
        try:
            self.SD.SetCapability(twain.ICAP_YRESOLUTION, twain.TWTY_FIX32, 100.0) 
        except:
            pass
        self.SD.RequestAcquire(0, 0)  # 1,1 to show scanner user interface
        self.AcquirePending=True
        self.LogMessage(self.ProductName + ':' + 'Waiting for Scanner')

    def AcquireNatively(self):
        """Acquire Natively - this is a memory based transfer"""
        self.XferMethod = XferNatively
        return self._Acquire()

    def AcquireByFile(self):
        """Acquire by file"""
        self.XferMethod = XferByFile
        return self._Acquire()

    def PollForImage(self):
        """This is a polling mechanism. Get the image without relying on the callback."""
        if self.AcquirePending:
            Info = self.SD.GetImageInfo()
            if Info:
                self.AcquirePending = False
                self.ProcessXFer()

    def ProcessXFer(self):
        """An image is ready at the scanner - fetch and display it"""
        more_to_come = False
        try:
            if self.XferMethod == XferNatively:
                XferFileName=tmpfilename
                (handle, more_to_come) = self.SD.XferImageNatively()
                twain.DIBToBMFile(handle, XferFileName)
                twain.GlobalHandleFree(handle)
                self.LogMessage(self.ProductName + ':' + 'Image acquired natively')
            else:
                try:
                    XferFileName='TWAIN.TMP' # Default
                    rv = self.SD.GetXferFileName()
                    if rv:
                        (XferFileName, type) = rv

                    # Verify that the transfer file can be produced. Security 
                    # configurations on windows can prevent it working.
                    try:
                        self.VerifyCanWrite(XferFileName)
                    except CannotWriteTransferFile:
                        self.SD.SetXferFileName(OverrideXferFileName)
                        XferFileName = OverrideXferFileName

                except:
                    # Functionality to influence file name is not implemented.
                    # The default is 'TWAIN.TMP'
                    pass

                self.VerifyCanWrite(XferFileName)
                self.SD.XferImageByFile()
                self.LogMessage(self.ProductName + ':' + "Image acquired by file (%s)" % XferFileName)

            self.DisplayImage(XferFileName)
            if more_to_come: self.AcquirePending = True
            else: self.SD = None
        except:
            # Display information about the exception
            import sys, traceback
            ei = sys.exc_info()
            traceback.print_exception(ei[0], ei[1], ei[2])

    def OnTwainEvent(self, event):
        """This is an event handler for the twain event. It is called 
        by the thread that set up the callback in the first place.

        It is only reliable on wxPython. Otherwise use the Polling mechanism above.
        
        """
        try:
            if event == twain.MSG_XFERREADY:
                self.AcquirePending = False
                self.ProcessXFer()
            elif event == twain.MSG_CLOSEDSREQ:
                self.SD = None
        except:
            # Display information about the exception
            import sys, traceback
            ei = sys.exc_info()
            traceback.print_exception(ei[0], ei[1], ei[2])

    def VerifyCanWrite(self, filepath):
        """The scanner can have a configuration with a transfer file that cannot
        be created. This method raises an exception for this case."""
        parts = os.path.split(filepath)
        if parts[0]:
            dirpart=parts[0]
        else:
            dirpart='.'
        if not os.access(dirpart, os.W_OK):
            raise CannotWriteTransferFile, filepath
        

Step 1 - OpenScanner

The method 'OpenScanner' involves two steps, creating a source manager object, and creating a source object.

The source manager object is created using the call twain.SourceManager(). The TWAIN protocol uses the client's windows message queue to perform communication. To access the message queue, the clients window handle is always passed to the SourceManager constructor. The window handle is retrieved using the a toolkit specific method.

Once the source manager is opened, the client can get a data source object. The data source object is retrieve using the factory method, OpenSource. The data source object is used for all document scanning logic.

When the scanner has an image for the application, the application must find out that it is available. There are two mechanisms. The client can Poll the twain source or the application can handle an event. The mechanism for sending events uses the windows event queue. This can cause problems in Tkinter. For pyGTK or wxPython you can use either a callback or poll the source. For Tkinter or other windows libraries you must poll the source.

Step 2 - Acquire

When the client application wants to acquire a document from the scanner, it makes a request to the scanner to acquire the document. This request is available as a method of the Data Source object, called RequestAcquire. If you want the scanner user interface to display, pass (1,1) to RequestAcquire rather than (0,0).

There are two protocols, acquisition by file or acquisition natively (via windows shared memory).

Step 3 - ProcessXFer

When the data is available in the server, the client application must perform a transfer of that data from the server to the application. The application is notified that the image is ready through the 'OnTwainEvent' method or discovers the data is available by polling the source.

For native image transfer (shared memory) the image is transferred using the call XferImageNatively. XFerImageNatively returns two items (when successful), a windows handle to a global memory area, which contains the image, and an indicator to show whether there are more images pending. There will be pending images where the scanner is using a sheet-feeder.

The global memory areas are not accessible directly from Python. We use a function (in the twain module), DIBToBMFile to save the image from this global memory area to a file. DIBToBMFile stores the image which is in memory to a windows Bitmap file named tmpfilename. The application then display the image on the screen.

The application must free the global handle, using the GlobalHandleFree() method. If the application does not free these handles, the memory will be unavailable to all windows programs, even after the program exists.

For file based image transfer the image is transferred using the call XferImageByFile. XFerImageByFile creates a file containing the image.

There is a number of complications where the scanner may try to save the file to an area which cannot be written. The source code tries to retrieve the filename and handle these sort of problems. These issues are scanner device and driver specific.

This method uses a method DisplayImage to display the image. This method is implemented by the toolkit specific subclass.

Step 4 - Terminate

The user interface for the scanner software will remain displayed until the application deletes the data source object. This occurs in the MnuQuit method.

Note: The TWAIN software interfaces to your window message queue. If you close your window without deleting the Source Manager object, the TWAIN software may attempt to write messages to a deleted queue. This may cause your program to hang when it attempts to exit.


Previous Up Next Tutorial