OCR TOOL rev1 made by OpenCV and Tesseract-OCR
2021/03/27 categories:OpenCV| tags:OpenCV|Python|Tesseract-OCR|
Updated the roots created in Table OCR Tool Created with OpenCV and Tesseract-OCR. The main changes are rewriting the structure of the code and recreating the GUI. I changed it because I got used to writing PyQt5 code and wanted to rewrite the code I wrote before. Also, I thought the GUI was difficult to use, so I changed it.
Add and view files
Click the Open button and select the PDF file, the file name will be displayed in the file list. Click the file name to convert the PDF to an image and display it. The image-converted file is displayed as image in the second column. If you click the file displayed as image, the created image will be displayed without converting the image again. You can show or hide the image by switching the check box.

Cell recognition
Click Recognize all to recognize the cells in the table. The recognition result is added to the list as a child of the file name. Rect is the cell recognition result displayed in red, and Crop is the cropped margin in the cell displayed in green. The cropped image based on the recognition result is displayed in the second column of the recognition result.
If you click on an item that is displayed as a child of the file name, it will be displayed in blue so that you can check where that item is.

Delete and add cell recognition area
You can delete the selected item by clicking the Delete rect button while clicking the item displayed as a child of the file name. Also, if you click Draw rect in that state and then drag in the image to specify the range, the range will be cropped and added to the file item.

Cell OCR
Click the OCR button to convert all the images in the cells displayed in the second column to character strings.

Save CSV file
The character string can be saved as a CSV file by clicking the Save button. The CSV file created is as follows.

Source code
main.py
Click here to expand
# -*- coding: utf-8 -*-
import csv
import sys
from pathlib import Path
from poppler import Poppler
from PyQt5 import QtWidgets, QtCore, QtGui
from table_recognition_view import TableRecognitionView
from toolbar import ToolBar
from filelist import FileList
from tesseract_ocr import TesseractOCR
from iamge_processing import ImageProcessing
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.poppler = Poppler('./poppler/')
self.tesseractOCR = TesseractOCR('./Tesseract-OCR/tesseract.exe')
self.resize(800, 600)
self.setCentralWidget( QtWidgets.QWidget(self) )
self.recogView = TableRecognitionView( self.centralWidget() )
self.layout = QtWidgets.QVBoxLayout( self.centralWidget() )
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.recogView)
self.toolBar = ToolBar( self.centralWidget() )
self.toolBarDock = QtWidgets.QDockWidget('', self)
self.toolBarDock.setWidget(self.toolBar)
self.toolBarDock.setFloating(False)
self.toolBarDock.setFeatures(QtWidgets.QDockWidget.NoDockWidgetFeatures)
self.toolBarDock.setTitleBarWidget( QtWidgets.QWidget() )
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.toolBarDock)
self.fileList = FileList(self)
self.fileListDock = QtWidgets.QDockWidget('File list', self)
self.fileListDock.setWidget(self.fileList)
self.fileListDock.setFeatures(QtWidgets.QDockWidget.NoDockWidgetFeatures)
self.fileListDock.setTitleBarWidget( QtWidgets.QWidget() )
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.fileListDock)
self.toolBar.drawMenu.drawRectButton.clicked.connect(lambda flag : self.recogView.setDrawRectFlag(flag))
self.toolBar.drawMenu.deleteRectButton.clicked.connect(self.removeSelectedRects)
self.toolBar.drawMenu.drawRectButton.setEnabled(False)
self.toolBar.fileMenu.openButton.clicked.connect(self.fileOpen)
self.toolBar.fileMenu.saveButton.clicked.connect(self.saveFiles)
self.toolBar.imageMenu.recognizeButton.clicked.connect(self.recognizeAll)
self.toolBar.viewMenu.rowHeight.valueChanged.connect(self.rowHeightChanged)
self.toolBar.viewMenu.addToggleViewButton(self.fileListDock.toggleViewAction(), 0, 0, 1, 1)
self.toolBar.ocrMenu.ocrButton.clicked.connect(self.ocrChildren)
self.recogView.mouseLeftReleasedSignal.connect(self.recogViewClicked)
self.fileList.tableClicked.connect(self.filelistClicked)
self.rowHeightChanged( self.toolBar.viewMenu.rowHeight.value() )
def filelistClicked(self, clickedIndex):
if not clickedIndex.parent().isValid():
self.recogView.drawRectFlag = self.toolBar.drawMenu.drawRectButton.isChecked() and True
pixmap = self.fileList.item(clickedIndex.row(), 1).data()
if pixmap is None:
filePath = self.fileList.filePath( clickedIndex.row() )
pixmap = self.pathToPixmap( clickedIndex, filePath )
pixmapIndex = self.fileList.index(clickedIndex.row(), 1)
self.fileList.setData( pixmapIndex, 'image', pixmap )
parentIndex = clickedIndex
pixmap = self.fileList.getImage( parentIndex.row() )
self.fileList.view.setCurrentIndex(clickedIndex)
self.toolBar.drawMenu.drawRectButton.setEnabled(True)
else:
self.recogView.drawRectFlag = False
parentIndex = clickedIndex.parent()
pixmap = self.fileList.getImage( parentIndex.row() )
self.toolBar.drawMenu.drawRectButton.setEnabled(False)
self.refreshView(pixmap, parentIndex)
def fileOpen(self):
files, _ = QtWidgets.QFileDialog.getOpenFileNames(None, 'Open PDF files', '')
for path in [ Path(f) for f in files]:
self.fileList.appendFile(path)
self.fileList.setViewSize()
def ocrChildren(self):
count = 0
for row in range(self.fileList.model.rowCount()):
for rectRow in range(self.fileList.model.item(row).rowCount()):
count = count + 1
progressBar = QtWidgets.QProgressBar()
progressBar.setMaximum(count)
self.statusBar().addPermanentWidget(progressBar)
count = 0
for row in range(self.fileList.model.rowCount()):
fileItem = self.fileList.model.item(row)
for rectRow in range(fileItem.rowCount()):
rectPixmap = fileItem.child(rectRow, 1).data()
text = self.tesseractOCR.OCR(rectPixmap)
fileItem.child(rectRow, 2).setText(text)
count = count + 1
progressBar.setValue(count)
QtWidgets.QApplication.processEvents()
self.statusBar().removeWidget(progressBar)
def pathToPixmap(self, index, pdfpath):
paths = self.poppler.pdftocairo(pdfpath, Path('pdftocairo_temp.png'), 300)
if len(paths) > 0:
pixmap = QtGui.QPixmap( str(paths[0]) )
for path in paths:
path.unlink()
return pixmap
return None
def refreshView(self, pixmap=None, parentIndex=None):
self.recogView.clear()
if not pixmap is None:
self.recogView.addPixmap(pixmap)
if not parentIndex is None:
for item in self.fileList.childrenWithText('Rect', 0, parentIndex):
self.recogView.appendRect(item.data())
for item in self.fileList.childrenWithText('Crop', 0, parentIndex):
self.recogView.appendCrop(item.data())
for item in self.fileList.selectedChildren(0, parentIndex):
self.recogView.appendSelectedRect(item.data())
self.recogView.update()
def recognizeAll(self):
self.fileList.removeAllRect()
for row in range( self.fileList.model.rowCount() ):
parentIndex = self.fileList.index(row, 0)
pixmap = self.fileList.item(row, 1).data()
if pixmap is None:
filePath = self.fileList.filePath(row)
pixmap = self.pathToPixmap( parentIndex, filePath )
self.fileList.setFileData(row, 1, 'image', pixmap)
area_range = ( self.toolBar.imageMenu.contourAreaMin.value(), self.toolBar.imageMenu.contourAreaMax.value() )
dilate_size = ( self.toolBar.imageMenu.dilate.value(), self.toolBar.imageMenu.dilate.value() )
image_process = ImageProcessing(pixmap)
edge, rects, crops = image_process.recognize_table(area_range, dilate_size)
self.fileList.setFileData(row, 2, 'edge image', edge, QtCore.Qt.Unchecked)
for text, datas in zip(['Rect', 'Crop'], [rects, crops]):
for data in datas:
rectItem = QtGui.QStandardItem(text)
rectItem.setData(QtCore.QRectF(data[0], data[1], data[2], data[3]))
rectItem.setCheckable(True)
rectItem.setCheckState(QtCore.Qt.Checked)
rectItem.setEditable(False)
cropPixmap = pixmap.copy(QtCore.QRect(data[0], data[1], data[2], data[3]))
pixmapItem = QtGui.QStandardItem()
pixmapItem.setData(cropPixmap)
pixmapItem.setData(cropPixmap, QtCore.Qt.DecorationRole)
pixmapItem.setEditable(False)
self.fileList.appendChild( parentIndex, [rectItem, pixmapItem, QtGui.QStandardItem()] )
self.refreshView(None, None)
def recogViewClicked(self, rect):
if self.recogView.drawRectFlag:
self.fileList.addRect(rect)
self.recogView.appendRect(rect)
def removeSelectedRects(self):
removedRects, parentIndex = self.fileList.removeSelectedRects()
for rect in removedRects:
self.recogView.removeRect(rect)
pixmap = self.fileList.getImage( parentIndex.row() )
self.refreshView(pixmap, parentIndex)
def rowHeightChanged(self, value):
self.fileList.view.setStyleSheet('QTreeView::item { padding: ' + str(value) + 'px }')
def saveFiles(self):
csvDatas = []
for row in range(self.fileList.model.rowCount()):
fileItem = self.fileList.model.item(row)
for rectRow in range(fileItem.rowCount()):
csvDatas.append([
fileItem.child(rectRow, 0).text().strip(),
fileItem.child(rectRow, 2).text().strip()
])
with open('output.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(csvDatas)
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
filelist.py
Click here to expand
# -*- coding: utf-8 -*-
from pathlib import Path
from PyQt5 import QtWidgets, QtCore, QtGui
class FileList(QtWidgets.QWidget):
tableClicked = QtCore.pyqtSignal(QtCore.QModelIndex)
def __init__(self, parent=None, path=None):
super(FileList, self).__init__(parent)
self.setLayout( QtWidgets.QVBoxLayout(self) )
self.model = QtGui.QStandardItemModel(self)
self.model.setHorizontalHeaderLabels(['', '', ''])
self.view = QtWidgets.QTreeView(self)
self.view.setModel(self.model)
self.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.view.setItemDelegate(Delegate())
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().addWidget(self.view)
self.view.clicked.connect( lambda index : self.tableClicked.emit(index) )
def addRect(self, rect):
rows = list(set([ index.row() for index in self.view.selectedIndexes() ]))
for row in rows:
parentIndex = self.model.index(row, 0)
parentItem = self.model.item( parentIndex.row(), 1 )
pixmap = parentItem.data()
rectItem = QtGui.QStandardItem('Rect')
rectItem.setData(rect)
rectItem.setCheckable(True)
rectItem.setCheckState(QtCore.Qt.Checked)
rectItem.setEditable(False)
cropPixmap = pixmap.copy(QtCore.QRect( rect.x(), rect.y(), rect.width(), rect.height() ))
pixmapItem = QtGui.QStandardItem()
pixmapItem.setData(cropPixmap)
pixmapItem.setData(cropPixmap, QtCore.Qt.DecorationRole)
pixmapItem.setEditable(False)
self.appendChild( parentIndex, [rectItem, pixmapItem, QtGui.QStandardItem()] )
def appendChild(self, parentIndex, items):
parentItem = self.model.item( parentIndex.row() )
parentItem.appendRow(items)
def appendFile(self, path):
filename = QtGui.QStandardItem(str(path.name))
filename.setData(path)
image = QtGui.QStandardItem()
edge = QtGui.QStandardItem()
self.model.appendRow([ filename, image, edge ])
def childrenWithText(self, text, column=0, parentIndex=QtCore.QModelIndex()):
children = []
selectedIndexes = self.view.selectedIndexes()
for item in self.items(column, parentIndex):
if not item.checkState() == QtCore.Qt.Checked:
continue
if not item.text() == text:
continue
if self.model.index(item.row(), column) in selectedIndexes:
continue
children.append(item)
return children
def filePath(self, row):
return self.model.item(row).data()
def getImage(self, row):
if self.item(row, 1).checkState() == QtCore.Qt.Checked:
return self.item(row, 1).data()
if self.item(row, 2).checkState() == QtCore.Qt.Checked:
return self.item(row, 2).data()
return None
def index(self, row, column, parentIndex=QtCore.QModelIndex()):
return self.model.index(row, column, parentIndex)
def item(self, row, column=0):
return self.model.item(row, column)
def items(self, column=0, parentIndex=QtCore.QModelIndex()):
if parentIndex.isValid():
item = self.model.item( parentIndex.row() )
return [ item.child(row, column) for row in range( item.rowCount() ) ]
else:
return [ self.model.item(row, column) for row in range( self.model.rowCount() ) ]
def removeSelectedRects(self):
removeList = {}
removedRects = []
for index in self.view.selectedIndexes():
parentIndex = index.parent()
if not parentIndex.isValid():
continue
if not parentIndex in removeList:
removeList[parentIndex] = {}
parentItem = self.model.item(parentIndex.row())
item = parentItem.child(index.row())
removeList[parentIndex][index.row()] = item.data()
for parentIndex in removeList:
deleteRowAndRects = sorted( removeList[parentIndex].items(), reverse=True )
deleteRowAndRects = { key : data for key, data in deleteRowAndRects }
for deleteRow in deleteRowAndRects:
rect = deleteRowAndRects[deleteRow]
self.model.removeRow(deleteRow, parentIndex)
removedRects.append(rect)
parentIndex = list(removeList.keys())[0]
return removedRects, parentIndex
def removeAllRect(self):
for row in range( self.model.rowCount() ):
parentItem = self.item(row)
for childRow in list( range(parentItem.rowCount()) )[::-1]:
parentItem.removeRow(childRow)
def selectedChildren(self, column=0, parentIndex=QtCore.QModelIndex()):
children = []
selectedIndexes = [ [i.row(), i.column(), i.parent()] for i in self.view.selectedIndexes() ]
for item in self.items(column, parentIndex):
if not item.checkState() == QtCore.Qt.Checked:
continue
if not [item.row(), 0, parentIndex] in selectedIndexes:
continue
children.append(item)
return children
def selectedIndexes(self):
return self.view.selectedIndexes()
def setData(self, index, text, data, state=QtCore.Qt.Checked):
item = QtGui.QStandardItem(text)
item.setData(data)
item.setCheckable(True)
item.setCheckState(state)
self.model.setItem( index.row(), index.column(), item )
return item
def setFileData(self, row, column, text, data, state=QtCore.Qt.Checked):
item = QtGui.QStandardItem(text)
item.setData(data)
item.setCheckable(True)
item.setCheckState(state)
self.model.setItem( row, column, item )
return item
def setViewSize(self):
self.view.setColumnWidth(0, 150)
self.view.setColumnWidth(1, 80)
self.view.setColumnWidth(2, 80)
class Delegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(Delegate, self).__init__(parent)
def paint(self, painter, option, index):
item = index.model().itemFromIndex(index)
if type( item.data() ) is QtGui.QPixmap and not item.parent() is None:
pixmapWidth, pixmapHeight = item.data().size().width(), item.data().size().height()
cellWidth, cellHeight = option.rect.width(), option.rect.height()
if cellWidth / cellHeight < pixmapWidth / pixmapHeight:
scaledPixmap = item.data().scaledToWidth(cellWidth)
else:
scaledPixmap = item.data().scaledToHeight(cellHeight)
item.setData(scaledPixmap, QtCore.Qt.DecorationRole)
super(Delegate, self).paint(painter, option, index)
if __name__ == '__main__':
import sys
def item(file):
pixmapItem = QtGui.QStandardItem()
pixmapItem.setData(QtGui.QPixmap(file))
pixmapItem.setData(QtGui.QPixmap(file), QtCore.Qt.DecorationRole)
return pixmapItem
app = QtWidgets.QApplication(sys.argv)
view = FileList()
view.model.setHorizontalHeaderLabels([ 'column' + str(i) for i in range(3) ])
#view.model.appendRow([ QtGui.QStandardItem(str(i)) for i in range(3) ])
view.model.appendRow([ item('cell00'), item('cell01'), QtGui.QStandardItem() ])
view.model.appendRow([ item('cell10'), item('cell11'), item('cell12') ])
view.model.appendRow([ item('cell20'), item('cell21'), QtGui.QStandardItem() ])
view.resize(600, 300)
view.show()
app.exec()
iamge_processing.py
Click here to expand
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pathlib import Path, WindowsPath
from PyQt5 import QtGui
class ImageProcessing(object):
def __init__(self, data):
self.data = self.load_data(data)
def cv_to_pixmap(self, cv_image):
shape_size = len(cv_image.shape)
if shape_size == 2:
rgb = cv2.cvtColor(cv_image, cv2.COLOR_GRAY2RGB)
elif shape_size == 3:
rgb = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
height, width, bytesPerComponent = rgb.shape
bytesPerLine = bytesPerComponent * width
image = QtGui.QImage(rgb.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888)
qpixmap = QtGui.QPixmap.fromImage(image)
return qpixmap
def edge_image(self, size):
gray = cv2.cvtColor(self.data, cv2.COLOR_BGR2GRAY)
edge = cv2.Canny(gray, 1, 100, apertureSize=3)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, size)
dilate = cv2.dilate(edge, kernel)
return dilate
def edge_to_rects(self, edge, area_range):
contours, hierarchy = cv2.findContours(edge, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
rects = []
for contour, hierarchy in zip(contours, hierarchy[0]):
if not area_range[0] < cv2.contourArea(contour) < area_range[1]:
continue
curve = cv2.approxPolyDP(contour, 0.01 * cv2.arcLength(contour, True), True)
if len(curve) == 4:
p1, p3 = curve[0][0], curve[2][0]
x, y, w, h = p1[0], p1[1], p3[0] - p1[0], p3[1] - p1[1]
rect = [x, y, w, h]
if False in [ False for r in rect if r < 1 ]:
continue
if self.same_rect_is_in_rects(rect, rects, 10):
continue
rects.append(rect)
rects = sorted( rects, key=lambda x: (x[1], x[0]) )
return rects
def load_data(self, data):
data_type = type(data)
if data_type is str or data_type is WindowsPath:
return cv2.imread( str(data) )
if data_type is QtGui.QPixmap:
return self.qimage_to_cv( data.toImage() )
def qimage_to_cv(self, qimage):
w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth()
bytes_ = qimage.bits().asstring(w * h * d // 8)
arr = np.frombuffer(bytes_, dtype=np.uint8).reshape((h, w, d // 8))
return arr
def recognize_table(self, area_range=(10, 1000), dilate_size=(6, 6)):
edge = self.edge_image(dilate_size)
rects = self.edge_to_rects(edge, area_range)
crops = self.rects_to_crops(rects)
edge = self.cv_to_pixmap(edge)
return edge, rects, crops
def rects_to_crops(self, rects, margin=10):
crops = []
for rect in rects:
x, y, w, h = rect[0], rect[1], rect[2], rect[3]
cropped = self.data[ y : y + h, x : x + w ]
gray = cv2.cvtColor(cropped, cv2.COLOR_BGR2GRAY)
threshold = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)[1]
contours = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]
rects_in_cropped = [ cv2.boundingRect(contour) for contour in contours[1:] ]
if len(rects_in_cropped) == 0:
continue
x1 = min([ r[0] for r in rects_in_cropped ]) - margin
y1 = min([ r[1] for r in rects_in_cropped ]) - margin
x2 = max([ r[0] + r[2] for r in rects_in_cropped ]) + margin
y2 = max([ r[1] + r[3] for r in rects_in_cropped ]) + margin
if x1 < 0:
x1 = 0
if y1 < 0:
y1 = 0
if x2 > cropped.shape[1]:
x2 = cropped.shape[1]
if y2 > cropped.shape[0]:
y2 = cropped.shape[0]
crops.append([x + x1, y + y1, x2 - x1, y2 - y1])
return crops
def same_rect_is_in_rects(self, rect1, rects, tolerance=5):
for rect2 in rects:
frag = True
for r1, r2 in zip(rect1, rect2):
if not r2 - tolerance < r1 < r2 + tolerance:
frag = False
break
if frag:
return True
return False
poppler.py
Click here to expand
# -*- coding: utf-8 -*-
import subprocess
from pathlib import Path
import chardet
class Poppler():
def __init__(self, popplerPath):
pdftocairo = list(Path(popplerPath).glob('**/pdftocairo.exe'))
if len(pdftocairo) > 0:
self.pdftocairo_path = pdftocairo[0]
else:
self.pdftocairo_path = None
def pdftocairo(self, input_path, output_path, resolution):
suffix = '-'+str(output_path.suffix).replace('.', '')
cmd = [str(self.pdftocairo_path), suffix, '-r', str(resolution), str(input_path), str(output_path.stem)]
return_value = self.subprocess_run(cmd)
paths = []
count = 1
p = Path(output_path.stem + '-' + str(count) + '.png')
while p.exists():
paths.append(p)
count = count + 1
p = Path(output_path.stem + '-' + str(count) + '.png')
return paths
def subprocess_run(self, cmd):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
returncode = subprocess.Popen(cmd, startupinfo=startupinfo)
returncode.wait()
return returncode
table_recognition_view.py
Click here to expand
# -*- coding: utf-8 -*-
from PyQt5 import QtWidgets, QtCore, QtGui
class TableRecognitionView(QtWidgets.QWidget):
mouseLeftReleasedSignal = QtCore.pyqtSignal(QtCore.QRectF)
def __init__(self, parent=None):
super().__init__(parent)
self.drawRectFlag = False
self.rectPen = QtGui.QPen( QtGui.QBrush( QtGui.QColor(255, 0, 0, 100) ), 6 )
self.cropPen = QtGui.QPen( QtGui.QBrush( QtGui.QColor( 0, 255, 0, 100) ), 6 )
self.freeHandPen = QtGui.QPen( QtGui.QBrush( QtGui.QColor(100, 100, 0, 150) ), 6 )
self.selectedPen = QtGui.QPen( QtGui.QBrush( QtGui.QColor( 0, 0, 255, 150) ), 20 )
self.scene = QtWidgets.QGraphicsScene()
self.graphicsView = GraphicsView(self)
self.graphicsView.setScene(self.scene)
self.graphicsView.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.setLayout( QtWidgets.QVBoxLayout(self) )
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().addWidget(self.graphicsView)
self.graphicsView.mouseLeftReleased.connect(self.mouseLeftReleased)
def appendCrop(self, rect):
rectItem = QtWidgets.QGraphicsRectItem(rect)
rectItem.setPen(self.cropPen)
self.scene.addItem(rectItem)
def appendRect(self, rect):
rectItem = QtWidgets.QGraphicsRectItem(rect)
rectItem.setPen(self.rectPen)
self.scene.addItem(rectItem)
def appendSelectedRect(self, rect):
rectItem = QtWidgets.QGraphicsRectItem(rect)
rectItem.setPen(self.selectedPen)
self.scene.addItem(rectItem)
def mouseLeftReleased(self, rect):
if self.drawRectFlag:
rectItem = QtWidgets.QGraphicsRectItem(rect)
rectItem.setPen(self.freeHandPen)
self.scene.addItem(rectItem)
self.mouseLeftReleasedSignal.emit(rect)
def resetPixmap(self, pixmap):
self.scene.clear()
if not pixmap is None:
self.scene.addPixmap(pixmap)
def removeRect(self, rect):
for rectItem in self.scene.items():
if type(rectItem) is QtWidgets.QGraphicsRectItem:
if rect == rectItem.rect():
self.scene.removeItem(rectItem)
def removeAllRect(self):
for rectItem in self.scene.items():
if type(rectItem) is QtWidgets.QGraphicsRectItem:
self.scene.removeItem(rectItem)
def setDrawRectFlag(self, flag):
self.drawRectFlag = flag
def setRectVisible(self, rect, isVisible):
for rectItem in self.scene.items():
if type(rectItem) is QtWidgets.QGraphicsRectItem:
if rect is rectItem.rect():
rectItem.setVisible(isVisible)
def addPixmap(self, pixmap):
self.scene.addPixmap(pixmap)
def clear(self):
self.scene.clear()
class GraphicsView(QtWidgets.QGraphicsView):
mouseLeftReleased = QtCore.pyqtSignal(QtCore.QRectF)
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.numScheduledScalings = 0
self.rect = QtCore.QRectF(0.0, 0.0, 0.0, 0.0)
def animation_finished(self):
if self.numScheduledScalings > 0:
self.numScheduledScalings -= 1
else:
self.numScheduledScalings += 1
def wheelEvent(self, event):
numDegrees = event.angleDelta().y() / 8
numSteps = numDegrees / 15
self.numScheduledScalings += numSteps
if self.numScheduledScalings * numSteps < 0:
self.numScheduledScalings = numSteps
anim = QtCore.QTimeLine(350, self)
anim.setUpdateInterval(20)
anim.valueChanged.connect(self.scaling_time)
anim.finished.connect(self.animation_finished)
anim.start()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MidButton:
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
event = QtGui.QMouseEvent(
QtCore.QEvent.GraphicsSceneDragMove,
event.pos(),
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.KeyboardModifier.NoModifier
)
elif event.button() == QtCore.Qt.LeftButton:
self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
point = self.mapToScene( event.pos() )
self.rect = QtCore.QRectF(point, point)
super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
if event.button() == QtCore.Qt.LeftButton:
p2 = self.mapToScene( event.pos() )
self.rect.setBottomRight(p2)
self.mouseLeftReleased.emit(self.rect)
def scaling_time(self, x):
factor = 1.0 + float(self.numScheduledScalings) / 300.0
self.scale(factor, factor)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
view = TableRecognitionView()
view.setPixmap( QtGui.QPixmap('table1.png') )
view.resize(600, 300)
view.show()
app.exec()
tesseract_ocr.py
Click here to expand
# -*- coding: utf-8 -*-
import subprocess
from pathlib import Path, WindowsPath
from PyQt5 import QtGui
class TesseractOCR():
def __init__(self, tesseractPath):
self.tesseract = tesseractPath
def command(self, filename, output_file):
return [self.tesseract, str(filename), output_file.stem]
def OCR(self, data):
if type(data) is str or type(data) is WindowsPath:
return self.OCR_file(data)
if type(data) is QtGui.QPixmap:
imagefile = Path('__temp__.png')
data.save(str(imagefile))
output = self.OCR_file(imagefile)
imagefile.unlink()
return output
def OCR_file(self, filename):
output_file = Path('__temp__.txt')
cmd = [self.tesseract, str(filename), output_file.stem]
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
returncode = subprocess.Popen(cmd, startupinfo=startupinfo)
returncode.wait()
try:
with open(output_file, 'r', encoding='utf-8') as file:
output = file.readline()
output_file.unlink()
return output
except:
return ''
toolbar.py
Click here to expand
# -*- coding: utf-8 -*-
from PyQt5 import QtWidgets, QtCore, QtGui
class ToolBar(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.tab = QtWidgets.QTabWidget(self)
self.setLayout( QtWidgets.QGridLayout(self) )
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().setHorizontalSpacing(0)
self.layout().setVerticalSpacing(0)
self.layout().addWidget(self.tab)
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
self.homeTab, homeTabLayout = self.addTab('Home')
self.fileMenu = FileMenu(self.homeTab, 'File')
self.viewMenu = ViewMenu(self.homeTab, 'View')
self.imageMenu = ImageMenu(self.homeTab, 'Image')
self.ocrMenu = OcrMenu(self.homeTab, 'OCR')
self.drawMenu = DrawMenu(self.homeTab, 'Draw')
homeTabLayout.addWidget(self.fileMenu)
homeTabLayout.addWidget(self.viewMenu)
homeTabLayout.addWidget(self.imageMenu)
homeTabLayout.addWidget(self.ocrMenu)
homeTabLayout.addWidget(self.drawMenu)
homeTabLayout.addSpacerItem( QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) )
def addTab(self, title):
tabWidget = QtWidgets.QWidget(self)
tabWidget.setLayout( QtWidgets.QHBoxLayout(tabWidget) )
tabWidget.layout().setContentsMargins(2, 2, 2, 2)
self.tab.addTab(tabWidget, title)
return tabWidget, tabWidget.layout()
class MenuGroup(QtWidgets.QGroupBox):
def __init__(self, parent, title):
super().__init__(parent)
self.toggleViewButton = {}
self.setLayout( QtWidgets.QVBoxLayout(self) )
self.layout().setContentsMargins(2, 2, 2, 2)
self.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding)
self.gridWidget = QtWidgets.QWidget(self)
self.layout().addWidget(self.gridWidget)
self.layout().addSpacerItem( QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding) )
label = QtWidgets.QLabel(title, self)
label.setAlignment(QtCore.Qt.AlignHCenter)
self.layout().addWidget(label)
self.gridWidgetLayout = QtWidgets.QGridLayout(self.gridWidget)
self.gridWidgetLayout.setContentsMargins(0, 0, 0, 0)
self.gridWidgetLayout.setHorizontalSpacing(0)
self.gridWidgetLayout.setVerticalSpacing(0)
def addWidget(self, widget, fromRow, fromColumn, rowSpan, columnSpan):
if widget is None:
return widget
widget.setParent(self.gridWidget)
self.gridWidgetLayout.addWidget(widget, fromRow, fromColumn, rowSpan, columnSpan)
return widget
def createButton(self, parent=None, text='', offIconPath=None, onIconPath=None, checkable=False, iconSize=24):
button = QtWidgets.QToolButton(parent)
button.setText(text)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(offIconPath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap(onIconPath), QtGui.QIcon.Normal, QtGui.QIcon.On)
button.setIcon(icon)
button.setIconSize( QtCore.QSize(iconSize, iconSize) )
button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
button.setAutoRaise(True)
button.setArrowType(QtCore.Qt.NoArrow)
button.setCheckable(checkable)
button.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
return button
def createSlider(self, parent, orientation, minimum, maximum, maximumWidth=100):
slider = QtWidgets.QSlider(orientation)
slider.setMinimum(minimum)
slider.setMaximum(maximum)
slider.setMaximumWidth(maximumWidth)
return slider
def addToggleViewButton(self, action, fromRow, fromColumn, rowSpan, columnSpan):
toolButton = QtWidgets.QToolButton(self)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap('./icons/task-line.svg'), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap('./icons/task-fill.svg'), QtGui.QIcon.Normal, QtGui.QIcon.On)
toolButton.setArrowType(QtCore.Qt.NoArrow)
toolButton.setAutoRaise(True)
toolButton.setCheckable(True)
toolButton.setIcon(icon)
toolButton.setIconSize( QtCore.QSize(24, 24) )
toolButton.setText( action.text() )
toolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
toolButton.click()
toolButton.toggled.connect( lambda : action.trigger() )
toolButton.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.toggleViewButton[toolButton.text()] = toolButton
self.addWidget(toolButton, fromRow, fromColumn, rowSpan, columnSpan)
class FileMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.openButton = self.createButton(self, 'Open', './icons/file-line.svg')
self.saveButton = self.createButton(self, 'Save', './icons/save-3-line.svg')
self.openButton = self.addWidget(self.openButton, 0, 0, 1, 1)
self.saveButton = self.addWidget(self.saveButton, 1, 0, 1, 1)
class ViewMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.rowHeight = SpinBox(self, 'Row height ', 1, 100, 5, 40)
self.addWidget(self.rowHeight, 1, 0, 1, 1)
class ImageMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.recognizeButton = self.createButton(self, 'Recognize all', './icons/image-line.svg')
self.contourAreaMin = SpinBox(self, 'Contour area min ', 1, 999999999, 5000)
self.contourAreaMax = SpinBox(self, 'Contour area max ', 1, 999999999, 1000000)
self.dilate = SpinBox(self, 'Dilate ', 1, 999999999, 3)
self.addWidget(self.recognizeButton, 0, 0, 1, 1)
self.addWidget(self.contourAreaMin, 0, 1, 1, 1)
self.addWidget(self.contourAreaMax, 1, 1, 1, 1)
self.addWidget(self.dilate, 2, 1, 1, 1)
class OcrMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.ocrButton = self.createButton(self, 'OCR', './icons/search-line.svg')
self.addWidget(self.ocrButton, 0, 0, 1, 1)
class DrawMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.drawRectButton = self.createButton(self, 'Draw rect', './icons/edit-box-line.svg', './icons/edit-box-line.svg', True)
self.deleteRectButton = self.createButton(self, 'Delete rect', './icons/delete-bin-line.svg')
self.addWidget(self.drawRectButton, 0, 0, 1, 1)
self.addWidget(self.deleteRectButton, 1, 0, 1, 1)
class SpinBox(QtWidgets.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent, text, minimum, maximum, value, width=80):
super().__init__(parent)
self.setLayout( QtWidgets.QHBoxLayout(self) )
self.layout().addWidget( QtWidgets.QLabel(text, self) )
self.spinBox = QtWidgets.QSpinBox(self)
self.spinBox.setMinimum(minimum)
self.spinBox.setMaximum(maximum)
self.spinBox.setMinimumWidth(width)
self.spinBox.setMaximumWidth(width)
self.spinBox.setValue(value)
self.layout().addWidget(self.spinBox)
self.layout().setContentsMargins(0, 0, 0, 0)
self.spinBox.valueChanged.connect(lambda value : self.valueChanged.emit(value))
def value(self):
return self.spinBox.value()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
view = ToolBar()
view.show()
app.exec()