├── .gitignore ├── setup.cfg ├── MANIFEST ├── setup.py ├── pyScanLib ├── unitConverter.py ├── __init__.py ├── saneLib.py └── twainLib.py ├── exampleUsage.py ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.cfg 3 | setup.py 4 | pyScanLib\__init__.py 5 | pyScanLib\saneLib.py 6 | pyScanLib\twainLib.py 7 | pyScanLib\unitConverter.py 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name = 'pyScanLib', 5 | packages = ['pyScanLib'], # this must be the same as the name above 6 | version = 'v1.0', 7 | description = 'An combination of Twain and SANE API', 8 | author = 'soachishti', 9 | author_email = 'soachishti@outlook.com', 10 | url = 'https://github.com/soachishti/pyScanLib', # use the URL to the github repo 11 | download_url = 'https://github.com/soachishti/pyScanLib/archive/v1.0.tar.gz', # I'll explain this in a second 12 | keywords = ['scanning', 'scanner', 'twain', 'sane'], # arbitrary keywords 13 | classifiers = [], 14 | ) -------------------------------------------------------------------------------- /pyScanLib/unitConverter.py: -------------------------------------------------------------------------------- 1 | 2 | #====================================================================== 3 | # File Name: unitConverter 4 | # Location: https://github.com/soachishti/pyScanLib 5 | # License: BSD 2-Clause License 6 | #====================================================================== 7 | 8 | 9 | class unitConverter(object): 10 | 11 | def pixelToInch(self, pixel): 12 | """Convert pixels to Inch using current dpi set 13 | It must be called after setDPI function otherwise dpi = 200 14 | """ 15 | return (pixel / float(self.dpi)) 16 | 17 | def cmToInch(self, cm): 18 | """Convert Centimetre(cm) to Inch 19 | """ 20 | return (cm * 0.39370) 21 | 22 | def inchTomm(self, inch): 23 | """Convert inch to millimeter 24 | """ 25 | return (inch / 0.039370) 26 | 27 | def mmToInch(self, mm): 28 | """Convert millimeter to inch 29 | """ 30 | return mm * 0.039370 31 | -------------------------------------------------------------------------------- /exampleUsage.py: -------------------------------------------------------------------------------- 1 | from pyScanLib import pyScanLib 2 | 3 | #====================================================================== 4 | # Name: saneLib 5 | # Location: https://github.com/soachishti/pyScanLib 6 | # License: BSD 2-Clause License 7 | #====================================================================== 8 | 9 | ls = pyScanLib() 10 | 11 | scanners = pyScanLib.getScanners() 12 | print scanners 13 | 14 | ls.setScanner(scanners[0]) 15 | 16 | # Below statement must be run after setScanner() 17 | ls.setDPI(300) 18 | 19 | print ls.getScannerSize() # (left,top,width,height) 20 | 21 | # Set Area in Pixels 22 | # width = ls.pixelToInch(128) 23 | # height = ls.pixelToInch(128) 24 | # ls.setScanArea(width=width,height=height) # (left,top,width,height) 25 | 26 | # Set Area in centimeter 27 | # width = ls.cmToInch(10) 28 | # height = ls.cmToInch(10) 29 | # ls.setScanArea(width=width,height=height) # (left,top,width,height) 30 | 31 | # A4 Example 32 | ls.setScanArea(width=8.26,height=11.693) # (left,top,width,height) in Inches 33 | 34 | ls.setPixelType("color") # bw/gray/color 35 | 36 | pil = ls.scan() 37 | pil.show() 38 | pil.save("scannedImage.jpg") 39 | 40 | ls.closeScanner() # unselect selected scanner in setScanners() 41 | ls.close() # Destory whole class -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | pyScanLib 2 | Location: https://github.com/soachishti/pyScanLib 3 | 4 | Copyright (c) 2014, SOAChishti (soachishti@outlook.com). 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL SOAChishti BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /pyScanLib/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from . import unitConverter # Contains unit converting functions ie millimeter to inch (mmToInch) etc 3 | 4 | """ 5 | * pyScanLib 6 | * Location: https://github.com/soachishti/pyScanLib 7 | * 8 | * Copyright (c) 2014, SOAChishti (soachishti@outlook.com). 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that the following conditions are met: 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * * Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in the 17 | * documentation and/or other materials provided with the distribution. 18 | * 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | * DISCLAIMED. IN NO EVENT SHALL SOAChishti BE LIABLE FOR ANY 24 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Mac OS Installation: 33 | brew install sane-backends 34 | pip install python-sane 35 | """ 36 | 37 | 38 | platform = sys.platform 39 | 40 | if platform == "win32": 41 | from . import twainLib 42 | 43 | class pyScanLib(twainLib.twainLib, unitConverter.unitConverter): 44 | pass 45 | 46 | elif platform.startswith("linux") or platform == 'darwin': 47 | from . import saneLib 48 | 49 | class pyScanLib(saneLib.saneLib, unitConverter.unitConverter): 50 | pass 51 | 52 | else: 53 | raise Exception("UnknownPlatform") 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pyScanLib 2 | ============================= 3 | 4 | An combination of Twain and SANE API 5 | 6 | Requirements: 7 | ------------ 8 | * Python 2.6/2.7 (Windows) 9 | * Python 2.6-3.4 (Linux) 10 | * SANE API for Linux, Mac OS 11 | * TWAIN API for Win32 12 | 13 | Functions: 14 | ------------ 15 | * pyScanLib() - Main Class 16 | * getScanners() - Return Scanner with Name 17 | * setScanners(scannerName) 18 | * setDPI(dpi) 19 | * setScanArea(left,top,width,height) - For scanning selected Area size in Inches 20 | * getScannerSize() - Return scanner size eg. (left, top, right, bottom) 21 | * setPixelType("color") - bw (Black & White), gray and color 22 | * scan() - Start Scanning 23 | * closeScanner() - Unselect selected scanner 24 | * close() - Destory connected API Class 25 | 26 | Special Function: 27 | ---------------- 28 | * pixelToInch(pixel) - Convert Pixel(s) to Inch(es) 29 | * cmToInch(cm) - Convert Centimeter(s) to Inch(es) 30 | * inchTomm(inch) - Convert Inch(es) to Millimeter(s) 31 | * mmToInch(mm) - Convert Millimeter(s) to Inch(es) 32 | 33 | Library Installation: 34 | ------------------ 35 | - Linux or Mac OS 36 | * brew install sane-backends 37 | * pip install python-sane 38 | - Windows 39 | * Download and install twain from [TWAIN PyPI](https://pypi.python.org/pypi/twain) 40 | 41 | Example: 42 | ------------ 43 | 44 | from pyScanLib import pyScanLib 45 | 46 | ls = pyScanLib() # load scanner library 47 | devices = ls.getScanners() 48 | ls.setScanner(devices[0]) 49 | 50 | ls.setDPI(300) 51 | 52 | # A4 Example 53 | ls.setScanArea(width=8.26,height=11.693) # (left,top,width,height) in inches 54 | 55 | ls.setPixelType("color") # bw/gray/color 56 | 57 | pil = ls.scan() 58 | pil.show() 59 | pil.save("scannedImage.jpg") 60 | 61 | ls.closeScanner() # unselect selected scanner, set in setScanners() 62 | ls.close() # Destory whole class 63 | 64 | Detail Example: 65 | ------------ 66 | Check [exampleUsage.py](exampleUsage.py) in repo 67 | 68 | Notice: 69 | ------------ 70 | * Known to work on Linux (Python 3.4) [Pull #1](https://github.com/soachishti/pyScanLib/pull/1) 71 | * Known to work on Mac OS [Pull #2](https://github.com/soachishti/pyScanLib/pull/2) 72 | * Not tested on Linux by author, however work perfect on Windows. 73 | 74 | TODO: 75 | ------- 76 | * Implement scanPreview() 77 | 78 | License: 79 | ------------ 80 | pyScanLib uses BSD 2-Clause License. 81 | -------------------------------------------------------------------------------- /pyScanLib/saneLib.py: -------------------------------------------------------------------------------- 1 | import sane 2 | 3 | #====================================================================== 4 | # Name: saneLib 5 | # Location: https://github.com/soachishti/pyScanLib 6 | # License: BSD 2-Clause License 7 | #====================================================================== 8 | 9 | class saneLib(object): 10 | 11 | """The is main class of SANE API (Linux) 12 | """ 13 | 14 | def __init__(self): 15 | self.dpi = 200 16 | self.layout = False 17 | self.scanner = None 18 | 19 | def getScanners(self): 20 | """ 21 | Get available scanner from sane module 22 | """ 23 | sane.init() 24 | devices = sane.get_devices()[0] 25 | if len(devices) > 0: 26 | return devices 27 | else: 28 | return None 29 | 30 | def setScanner(self, scannerName): 31 | """ 32 | Connected to Scanner using Scanner Name 33 | 34 | Arguments: 35 | scannerName -- Name of Scanner return by getScanners() 36 | """ 37 | 38 | self.scanner = sane.open(scannerName) 39 | 40 | def setDPI(self, dpi): 41 | """ 42 | Set DPI to selected scanner and dpi to self.dpi 43 | """ 44 | 45 | if self.scanner == None: 46 | raise ScannerNotSet 47 | 48 | self.dpi = dpi 49 | self.scanner.resolution = self.dpi 50 | 51 | def setScanArea(self, left=0.0, top=0.0, width=8.267, height=11.693): 52 | """ 53 | Set Custom scanner layout to selected scanner in Inches 54 | 55 | Arguments: 56 | left -- Left position of scanned Image in scanner 57 | top -- Top position of scanned Image in scanner 58 | width(right) -- Width of scanned Image 59 | bottom(height) -- Height of scanned Image 60 | """ 61 | 62 | if self.scanner == None: 63 | raise ScannerNotSet 64 | 65 | # http://www.sane-project.org/html/doc014.html#f5 66 | # (left, top, right, bottom) 67 | # top left x axis left 68 | self.scanner.tl_x = float(inchTomm(left)) 69 | # top left y axis top 70 | self.scanner.tl_y = float(inchTomm(top)) 71 | # bottom left x axis width 72 | self.scanner.br_x = float(inchTomm(width)) 73 | # bottom left y axis height 74 | self.scanner.br_y = float(inchTomm(height)) 75 | 76 | def getScannerSize(self): 77 | """ 78 | Return Scanner Layout as Tuple (left, top, right, bottom) in Inches 79 | """ 80 | 81 | return (mmToInch(self.scanner.tl_x), mmToInch(self.scanner.tl_y), mmToInch(self.scanner.br_x), mmToInch(self.scanner.br_y)) 82 | 83 | def setPixelType(self, pixelType): 84 | """ 85 | Set pixelType to selected scanner 86 | 87 | Arguments: 88 | pixelType -- Pixel type - bw (Black & White), gray (Gray) and color(Colored) 89 | """ 90 | 91 | if self.scanner == None: 92 | raise ScannerNotSet 93 | 94 | self.scanner.mode = pixelType.lower() 95 | 96 | def scan(self): 97 | """ 98 | Scan and return PIL object if success else return False 99 | """ 100 | 101 | if self.scanner == None: 102 | raise ScannerNotSet 103 | 104 | try: 105 | self.scanner.start() 106 | image = self.scanner.snap() 107 | return image 108 | except: 109 | return False 110 | 111 | def closeScanner(self): 112 | """ 113 | Destory 'self.scanner' class of sane module generated in setScanner function 114 | """ 115 | if self.scanner: 116 | self.scanner.close() 117 | del self.scanner 118 | self.scanner = None 119 | 120 | def scanPreview(self): 121 | """ 122 | Show preview of image while scanning in progress. 123 | """ 124 | raise NotImplementedError 125 | 126 | def close(self): 127 | """ 128 | Destory 'sane' class of sane module generated in getScanners function ie sane.init() 129 | Destory 'self.scanner' class of sane module generated in setScanner function 130 | """ 131 | if self.scanner: 132 | self.scanner.close() 133 | sane.exit() 134 | -------------------------------------------------------------------------------- /pyScanLib/twainLib.py: -------------------------------------------------------------------------------- 1 | import twain 2 | import Image 3 | from StringIO import StringIO 4 | 5 | #====================================================================== 6 | # Name: twainLib 7 | # Location: https://github.com/soachishti/pyScanLib 8 | # License: BSD 2-Clause License 9 | #====================================================================== 10 | 11 | class twainLib(object): 12 | 13 | """ 14 | The is main class of Twain API (Win32) 15 | """ 16 | 17 | def __init__(self): 18 | self.scanner = None 19 | self.dpi = 200 # Define for use in pixeltoInch function 20 | 21 | def getScanners(self): 22 | """ 23 | Get available scanner from twain module 24 | """ 25 | self.sourceManager = twain.SourceManager(0) 26 | scanners = self.sourceManager.GetSourceList() 27 | if scanners: 28 | return scanners 29 | else: 30 | return None 31 | 32 | def setScanner(self, scannerName): 33 | """ 34 | Connected to Scanner using Scanner Name 35 | 36 | Arguments: 37 | scannerName -- Name of Scanner return by getScanners() 38 | """ 39 | 40 | self.scanner = self.sourceManager.OpenSource(scannerName) 41 | 42 | def setDPI(self, dpi): 43 | """ 44 | Set DPI to selected scanner and dpi to self.dpi 45 | """ 46 | 47 | if self.scanner == None: 48 | raise ScannerNotSet 49 | 50 | self.dpi = dpi 51 | 52 | self.scanner.SetCapability( 53 | twain.ICAP_XRESOLUTION, twain.TWTY_FIX32, float(self.dpi)) 54 | self.scanner.SetCapability( 55 | twain.ICAP_YRESOLUTION, twain.TWTY_FIX32, float(self.dpi)) 56 | 57 | def setScanArea(self, left=0.0, top=0.0, width=8.267, height=11.693): 58 | """ 59 | Set Custom scanner layout to selected scanner in Inches 60 | 61 | Arguments: 62 | left -- Left position of scanned Image in scanner 63 | top -- Top position of scanned Image in scanner 64 | width(right) -- Width of scanned Image 65 | bottom(height) -- Height of scanned Image 66 | """ 67 | 68 | if self.scanner == None: 69 | raise ScannerNotSet 70 | 71 | #((left, top, width, height) document_number, page_number, frame_number) 72 | width = float(width) 73 | height = float(height) 74 | left = float(left) 75 | top = float(top) 76 | self.scanner.SetImageLayout((left, top, width, height), 1, 1, 1) 77 | 78 | # size in inches 79 | def getScannerSize(self): 80 | """ 81 | Return Scanner Layout as Tuple (left, top, right, bottom) in Inches 82 | """ 83 | 84 | if self.scanner == None: 85 | raise ScannerNotSet 86 | 87 | return self.scanner.GetImageLayout() 88 | 89 | def setPixelType(self, pixelType): 90 | """ 91 | Set pixelType to selected scanner 92 | 93 | Arguments: 94 | pixelType -- Pixel type - bw (Black & White), gray (Gray) and color(Colored) 95 | """ 96 | 97 | if self.scanner == None: 98 | raise ScannerNotSet 99 | 100 | pixelTypeMap = {'bw': twain.TWPT_BW, 101 | 'gray': twain.TWPT_GRAY, 102 | 'color': twain.TWPT_RGB} 103 | try: 104 | pixelType = pixelTypeMap[pixelType] 105 | except: 106 | pixelType = twain.TWPT_RGB 107 | self.scanner.SetCapability( 108 | twain.ICAP_PIXELTYPE, twain.TWTY_UINT16, pixelType) 109 | 110 | def scan(self): 111 | """ 112 | Scan and return PIL object if success else return False 113 | """ 114 | if self.scanner == None: 115 | raise ScannerNotSet 116 | 117 | self.scanner.RequestAcquire(0, 1) 118 | info = self.scanner.GetImageInfo() 119 | try: 120 | self.handle = self.scanner.XferImageNatively()[0] 121 | image = twain.DIBToBMFile(self.handle) 122 | twain.GlobalHandleFree(self.handle) 123 | return Image.open(StringIO(image)) 124 | except: 125 | return False 126 | 127 | def closeScanner(self): 128 | """ 129 | Destory 'self.scanner' class of twain module generated in setScanner function 130 | """ 131 | if self.scanner: 132 | self.scanner.destroy() 133 | self.scanner = None 134 | 135 | def scanPreview(self): 136 | """ 137 | Show preview of image while scanning in progress. 138 | """ 139 | raise NotImplementedError 140 | 141 | def close(self): 142 | """ 143 | Destory 'self.sourceManager' class of twain module generated in getScanners function 144 | Destory 'self.scanner' class of twain module generated in setScanner function 145 | """ 146 | if self.scanner: 147 | self.scanner.destroy() 148 | if self.sourceManager: 149 | self.sourceManager.destroy() 150 | (self.scanner, self.sourceManager) = (None, None) 151 | --------------------------------------------------------------------------------