프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

hytos / DTI_PID / DTI_PID / OcrResultDialog.py @ c0d132c5

이력 | 보기 | 이력해설 | 다운로드 (17.3 KB)

1
# coding: utf-8
2
"""
3
    This is ocr result dialog module
4
"""
5
from PIL import Image
6
import io
7
import numpy as np
8
import cv2
9
import math
10
import enum
11

    
12
from PyQt5.QtCore import *
13
from PyQt5.QtGui import *
14
from PyQt5.QtWidgets import *
15
import OcrResultDialog_UI
16
import QtImageViewer
17
import tesseract_ocr_module as TOCR
18
from App import App
19
from AppDocData import *
20
from TextInfo import TextInfo
21
from QtImageViewerScene import QtImageViewerScene
22

    
23

    
24
class SpellTextEdit(QTextEdit):
25
    def __init__(self, *args):
26
        QTextEdit.__init__(self, *args)
27

    
28
        # Default dictionary based on the current locale.
29
        app_doc_data = AppDocData.instance()
30
        white_char_list = app_doc_data.getConfigs('Text Recognition', 'White Character List')
31
        self.highlighter = Highlighter(self.document())
32
        self.highlighter.white_char_list = white_char_list[0].value if white_char_list else None
33

    
34

    
35
class Highlighter(QSyntaxHighlighter):
36
    err_format = QTextCharFormat()
37
    err_format.setUnderlineColor(Qt.red)
38
    err_format.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
39

    
40
    def __init__(self, *args):
41
        QSyntaxHighlighter.__init__(self, *args)
42

    
43
        self.white_char_list = None
44

    
45
    def highlightBlock(self, text):
46
        pos = 0
47
        for word in text.split():
48
            if self.white_char_list and any((c not in self.white_char_list) for c in word):
49
                self.setFormat(pos, len(word), self.err_format)
50
            pos += len(word) + 1
51

    
52

    
53
class QOcrResultDialog(QDialog):
54
    class Format(enum.Enum):
55
        Normal = 0
56
        Table = 1
57

    
58
    def __init__(self, parent, qimage, boundingBox, format=Format.Normal, text_item=None):
59
        QDialog.__init__(self, parent)
60
        self.textInfoList = []
61

    
62
        self._text_item = text_item
63
        self.image = qimage
64
        self.boundingBox = boundingBox
65
        self._format = format
66

    
67
        app_doc_data = AppDocData.instance()
68
        self.img_ocr = app_doc_data.activeDrawing.image.copy()
69
        self.img_ocr = self.img_ocr[int(self.boundingBox.y()):int(self.boundingBox.y() + self.boundingBox.height()), \
70
                int(self.boundingBox.x()):int(self.boundingBox.x() + self.boundingBox.width())]
71

    
72
        self.angle = 0  # angle in degree
73

    
74
        self.ui = OcrResultDialog_UI.Ui_Dialog()
75
        self.ui.setupUi(self)
76
        self.ui.detectResultTextEdit = SpellTextEdit()
77
        self.ui.detectResultTextEdit.setFont(QFont('Consolas', 15, QFont.Bold))
78
        self.ui.horizontalLayoutTextEdit.addWidget(self.ui.detectResultTextEdit)
79

    
80
        configs = app_doc_data.getAppConfigs('app', 'mode')
81
        if configs and 1 == len(configs) and 'advanced' == configs[0].value:
82
            pass
83
        else:
84
            self.ui.pushButtonMakeTrainingImage.setVisible(False)
85

    
86
        self.graphicsView = QtImageViewer.QtImageViewer(App.mainWnd())
87
        self.graphicsView.setScene(QtImageViewerScene(self.graphicsView))
88
        self.graphicsView.useDefaultCommand()  # USE DEFAULT COMMAND
89
        self.graphicsView.setImage(self.image)
90
        self.ui.horizontalLayoutGraphicsView.addWidget(self.graphicsView)
91

    
92
        self.ui.counterClockPushButton_2.clicked.connect(lambda: self.rotateImage(True))
93
        self.ui.clockPushButton_2.clicked.connect(lambda: self.rotateImage(False))
94
        self.ui.pushButtonCopyHori.clicked.connect(self.copy_horizontal)
95
        # add shortcut for detecting text with 't'
96
        self.ui.redetectPushButton.clicked.connect(self.detect_text)
97
        shortcut = QShortcut(QKeySequence('t'), self.ui.redetectPushButton)
98
        shortcut.activated.connect(self.detect_text)
99
        # up to here
100
        self.ui.pushButtonMakeTrainingImage.clicked.connect(self.pushButtonMakeTrainingImageClicked)
101

    
102
        self.ui.comboBoxOCRData.addItem('eng')
103
        tessdata_path = os.path.join(os.getenv('ALLUSERSPROFILE'), 'Digital PID', 'Tesseract-OCR', 'tessdata')
104
        if os.path.isfile(os.path.join(tessdata_path, app_doc_data.getCurrentProject().name + '.traineddata')):
105
            self.ui.comboBoxOCRData.addItem(app_doc_data.getCurrentProject().name)
106

    
107
        configs = app_doc_data.getConfigs('Text Recognition', 'OCR Data')
108
        value = configs[0].value if 1 == len(configs) else ''
109
        if value:
110
            at = self.ui.comboBoxOCRData.findText(value)
111
            self.ui.comboBoxOCRData.setCurrentIndex(at)
112
        else:
113
            self.ui.comboBoxOCRData.selectedIndex = 0
114

    
115
        if not self._text_item:
116
            if format == QOcrResultDialog.Format.Normal and self.boundingBox.height() > self.boundingBox.width():
117
                self.rotateImage(False)
118

    
119
            self.detect_text()
120
        else:
121
            rect = self._text_item.sceneBoundingRect()
122
            text_info = TextInfo(self._text_item.text(), 0, 0, rect.width(), rect.height(), 0)
123
            self.textInfoList.append(text_info)
124
            self.display_text_rect()
125

    
126
            allowed_error = 0.001
127
            if abs(self._text_item.angle - 1.57) < allowed_error or abs(self._text_item.angle - 4.71) < allowed_error:
128
                self.rotateImage(False)
129

    
130
            self.ui.detectResultTextEdit.setPlainText(self._text_item.text())
131
            self.ui.checkBoxSeperate.setChecked(False)
132

    
133
        self.isAccepted = False
134

    
135
    def showEvent(self, QShowEvent):
136
        """show event"""
137
        self.graphicsView.zoomImageInit()
138

    
139
    def display_text_rect(self):
140
        """display text bounding rectangle"""
141
        for item in self.graphicsView.scene().items():
142
            if type(item) is not QGraphicsPixmapItem:
143
                self.graphicsView.scene().removeItem(item)
144

    
145
        for text_info in self.textInfoList:
146
            self.graphicsView.scene().addRect(text_info.getX(), text_info.getY(),
147
                                            text_info.getW(), text_info.getH(), QPen(Qt.red, 1, Qt.SolidLine))
148

    
149
    '''
150
        @brief      Make OCR Training Image
151
        @author     euisung
152
        @date       2018.10.16
153
        @history    euisung     2018.11.02       add notice push
154
    '''
155

    
156
    def pushButtonMakeTrainingImageClicked(self):
157
        import uuid
158
        uid = str(uuid.uuid4()) + '.png'
159
        appDocData = AppDocData.instance()
160
        project = appDocData.getCurrentProject()
161
        trainingImgPath = os.path.join(project.getTrainingFilePath(), uid)
162

    
163
        self.image.save(trainingImgPath)
164
        QMessageBox.about(self, self.tr("INFO"), self.tr('Successfully saved.'))
165
        QDialog.reject(self)
166

    
167
    def rotateImage(self, isCounterClock):
168
        """rotate the image"""
169

    
170
        transform = QTransform()
171
        if isCounterClock:
172
            '''CounterClock'''
173
            self.angle = (self.angle - 90) % 360
174
            transform.rotate(-90)
175

    
176
            self.img_ocr = cv2.rotate(self.img_ocr, cv2.ROTATE_90_COUNTERCLOCKWISE)
177
        else:
178
            '''Clock'''
179
            self.angle = (self.angle - 270) % 360
180
            transform.rotate(90)
181

    
182
            self.img_ocr = cv2.rotate(self.img_ocr, cv2.ROTATE_90_CLOCKWISE)
183

    
184
        self.graphicsView.clearImage()
185
        self.image = self.image.transformed(transform)
186
        self.graphicsView.setImage(self.image)
187

    
188
        for text_info in self.textInfoList:
189
            rect = QRectF(text_info.getX(), text_info.getY(), text_info.getW(), text_info.getH())
190
            rect = transform.mapRect(rect)
191
            text_info.setX(self.image.width() + rect.left() if rect.left() < 0 else rect.left())
192
            text_info.setY(self.image.height() - max(abs(rect.top()), abs(rect.bottom())) if rect.top() < 0 else rect.top())
193
            text_info.setW(rect.width())
194
            text_info.setH(rect.height())
195

    
196
            self.graphicsView.scene().addRect(QRectF(text_info.getX(), text_info.getY(), text_info.getW(), text_info.getH()),
197
                                            QPen(Qt.red, 1, Qt.SolidLine))
198

    
199
    '''
200
        @history 2018.04.26 Jeongwoo    Add Rectangle with modified Coords
201
                 2018.06.20 Jeongwoo    Remove test code
202
                 2018.11.08 euisung     add white char list check process on db
203
                 2018.11.22 euisung     OCR lang apply fixed
204
    '''
205
    def detect_text(self):
206
        from TextDetector import TextDetector
207
        from TextInfo import TextInfo
208

    
209
        try:
210
            '''
211
            buffer = QBuffer()
212
            buffer.open(QBuffer.ReadWrite)
213
            self.image.save(buffer, "PNG")
214
            pyImage = Image.open(io.BytesIO(buffer.data()))
215
            img = np.array(pyImage)
216
            if len(img.shape[::-1]) == 2:
217
                img_width, img_height = img.shape[::-1]
218
            else:
219
                _, img_width, img_height = img.shape[::-1]
220
            '''
221

    
222
            app_doc_data = AppDocData.instance()
223

    
224
            ocr_data = self.ui.comboBoxOCRData.currentText()
225
            white_char_list = app_doc_data.getConfigs('Text Recognition', 'White Character List')
226
            if self._format == QOcrResultDialog.Format.Normal:
227
                self.textInfoList = TOCR.getTextInfo(self.img_ocr, (0, 0), 0, language=ocr_data,
228
                                                     conf=white_char_list[0].value if white_char_list else '')
229

    
230
                if self.textInfoList:
231
                    self.ui.detectResultTextEdit.setText(self.getPlainText(self.textInfoList))
232
                    self.display_text_rect()
233

    
234
                    self.copy_horizontal()
235
                else:
236
                    self.ui.detectResultTextEdit.setText(self.tr("Not Found"))
237
            else:
238
                cv_image = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 200, 255, cv2.THRESH_BINARY)[1]
239
                # contours 추출
240
                contours, _ = cv2.findContours(cv_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
241
                text_info_list = []
242
                for contour in contours:
243
                    [x, y, w, h] = cv2.boundingRect(contour)
244
                    text_info_list.append(TextInfo('', x, y, w, h, 0))
245

    
246
                text_info_list = [text_info for text_info in text_info_list if
247
                                  not any([_text_info for _text_info in text_info_list if _text_info is not text_info
248
                                           and text_info.contains(_text_info.center)])]
249

    
250
                detector = TextDetector()
251
                detector.recognizeText(img, (0, 0), text_info_list, None, None, None, None, onlyTextArea=True)
252
                self.textInfoList = detector.textInfoList.copy()
253
                self.textInfoList.sort(key=lambda x: x.getY())
254

    
255
                '''
256
                index = 0
257
                for text_info in text_info_list:
258
                    cropped = img[text_info.getY():text_info.getY() + text_info.getH(), text_info.getX():text_info.getX() + text_info.getW()]
259
                    cv2.imwrite(f"c:\\temp\\ocr-{index}.png", cropped)
260
                    index = index + 1
261
                '''
262

    
263
                if self.textInfoList:
264
                    self.ui.detectResultTextEdit.setText(self.getPlainText(self.textInfoList))
265
                    self.display_text_rect()
266

    
267
                    self.copy_horizontal()
268
                else:
269
                    self.ui.detectResultTextEdit.setText(self.tr("Not Found"))
270

    
271
        except Exception as ex:
272
            from App import App
273
            message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename,
274
                                                           sys.exc_info()[-1].tb_lineno)
275
            App.mainWnd().addMessage.emit(MessageType.Error, message)
276

    
277
    def getPlainText(self, textInfoList):
278
        text = ''
279
        for index in range(len(textInfoList)):
280
            textInfo = textInfoList[index]
281
            if index != 0:
282
                text = text + '\n'
283
            text = text + textInfo.getText()
284
        return text
285

    
286
    '''
287
        @brief      OK Button Clicked. Remake TextInfo object
288
        @author     Jeongwoo
289
        @date       18.04.19
290
        @history    18.04.20    Jeongwoo    Calculate Start Point Coordinates by rotated angle
291
                    18.04.26    Jeongwoo    Scene.itemAt(textX - boundBox.x(), textY - boundBox.y())
292
    '''
293

    
294
    def accept(self):
295
        from TextInfo import TextInfo
296
        self.isAccepted = True
297

    
298
        try:
299
            text = self.ui.detectResultTextEdit.toPlainText()
300
            if text == '' or text == 'Not Found':
301
                QMessageBox.about(self.ui.ocrDialogButtonBox, self.tr('Notice'),
302
                                  self.tr('Please try again after recognition or type.'))
303
                return
304

    
305
            isSplit = self.ui.checkBoxSeperate.isChecked()
306
            if isSplit:
307
                splitText = text.split('\n')
308
            else:
309
                splitText = [text]
310

    
311
            # try to detect text if there is no result of detection or
312
            # count of text info list not match with count of split text
313
            if isSplit:
314
                if self.textInfoList and (len(self.textInfoList) == len(splitText)):
315
                    for index in range(len(self.textInfoList)):
316
                        self.textInfoList[index].setText(splitText[index])
317
                elif not self.textInfoList:
318
                    self.detect_text()
319
                    if len(self.textInfoList) == len(splitText):
320
                        for index in range(len(self.textInfoList)):
321
                            self.textInfoList[index].setText(splitText[index])
322
                    else:
323
                        self.textInfoList = self.getMergedTextInfo(text)
324
            elif len(self.textInfoList) > 1 or len(self.textInfoList) == 0:
325
                self.textInfoList = self.getMergedTextInfo(text)
326

    
327
            radian = round(math.radians(abs(self.angle)), 2)
328
            for idx in range(len(self.textInfoList)):
329
                text_info = self.textInfoList[idx]
330
                # update text using user input text
331
                if idx < len(splitText):
332
                    text_info.setText(splitText[idx])
333
                # up to here
334

    
335
                if radian == 1.57 or radian == 4.71:
336
                    text_info.setAngle(radian)  # 360 degree == 6.28319 radian
337

    
338
                    # rotate text information
339
                    trans = QTransform()
340
                    trans.rotate(self.angle*-1)
341
                    rect = QRectF(text_info.getX(), text_info.getY(), text_info.getW(), text_info.getH())
342
                    rect = trans.mapRect(rect)
343
                    width, height = self.image.height(), self.image.width()
344
                    x = width + rect.left() if rect.left() < 0 else rect.left()
345
                    y = height - max(abs(rect.top()), abs(rect.bottom())) if rect.top() < 0 else rect.top()
346
                    text_info.setX(self.boundingBox.x() + x)
347
                    text_info.setY(self.boundingBox.y() + y)
348
                    text_info.setW(rect.width())
349
                    text_info.setH(rect.height())
350
                    # up to here
351
                else:
352
                    text_info.setX(int(self.boundingBox.x()) + text_info.getX())
353
                    text_info.setY(int(self.boundingBox.y()) + text_info.getY())
354

    
355
            QDialog.accept(self)
356

    
357
        except Exception as ex:
358
            from App import App
359
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
360
                                                           sys.exc_info()[-1].tb_lineno)
361
            App.mainWnd().addMessage.emit(MessageType.Error, message)
362

    
363
    def getMergedTextInfo(self, text):
364
        from TextInfo import TextInfo
365

    
366
        buffer = QBuffer()
367
        buffer.open(QBuffer.ReadWrite)
368
        self.image.save(buffer, "PNG")
369
        pyImage = Image.open(io.BytesIO(buffer.data()))
370
        img = np.array(pyImage)
371

    
372
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
373
        imgNot = np.ones(img.shape, np.uint8)
374
        cv2.bitwise_not(img, imgNot)
375
        imgNot = cv2.dilate(imgNot, np.ones((4, 4), np.uint8))
376

    
377
        contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
378
        minX, minY, maxX, maxY = sys.maxsize, sys.maxsize, 0, 0
379
        if len(contours) is 0:
380
            minX, minY, maxX, maxY = 0, 0, self.image.width(), self.image.height()
381
        else:
382
            minX, minY, maxX, maxY = sys.maxsize, sys.maxsize, 0, 0
383
            for cnt in contours:
384
                x, y, w, h = cv2.boundingRect(cnt)
385
                minX = min(x, minX)
386
                minY = min(y, minY)
387
                maxX = max(x + w, maxX)
388
                maxY = max(y + h, maxY)
389
            minX, minY, maxX, maxY = minX, minY, maxX, maxY
390

    
391
        return [TextInfo(text, minX, minY, maxX - minX, maxY - minY, 0)]
392

    
393
    def reject(self):
394
        self.isAccepted = False
395
        self.textInfoList = None
396
        QDialog.reject(self)
397

    
398
    def copy_horizontal(self):
399
        import io, csv
400

    
401
        try:
402
            table = [[text for text in self.ui.detectResultTextEdit.toPlainText().split('\n')]]
403
            stream = io.StringIO()
404
            csv.writer(stream, delimiter='\t').writerows(table)
405
            QApplication.clipboard().setText(stream.getvalue())
406

    
407
        except Exception as ex:
408
            from App import App 
409
            from AppDocData import MessageType
410

    
411
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
412
            App.mainWnd().addMessage.emit(MessageType.Error, message)
413

    
414
    '''
415
        @brief  Display this QDialog
416
    '''
417

    
418
    def showDialog(self):
419
        # self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
420
        res = self.exec_()
421
        return res, self.textInfoList
클립보드 이미지 추가 (최대 크기: 500 MB)