프로젝트

일반

사용자정보

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

hytos / DTI_PID / DTI_PID / TrainingSymbolEditorDialog.py @ a64f92c8

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

1
# coding: utf-8
2
import sys
3
import os, time, datetime
4
from PyQt5.QtCore import *
5
from PyQt5.QtGui import *
6
from PyQt5.QtWidgets import *
7
from AppDocData import *
8
import TrainingSymbolEditor_UI
9
import QtImageViewer
10
from GraphicsBoundingBoxItem import QGraphicsBoundingBoxItem
11
from TrainingBoxItem import QTrainingBoxItem
12
import cv2
13
import numpy as np
14
from PIL import Image
15
import AreaZoomCommand
16
from xml.etree.ElementTree import Element, SubElement, dump, ElementTree, parse
17

    
18
class QTrainingSymbolEditorDialog(QDialog):
19
    def __init__(self, parent, trainingImgPath, trainingLabelPath, boundaryLabelData, dateItem, boxItem):
20
        self.spinBoxFlag = False
21
        QDialog.__init__(self, parent)
22
        self.setWindowFlag(Qt.WindowMinMaxButtonsHint)
23

    
24
        self.trainingImgPath = trainingImgPath
25
        self.trainingLabelPath = trainingLabelPath
26
        self.boundaryLabelData = boundaryLabelData
27
        self.dateItem = dateItem
28
        self.boxItem = boxItem
29
        self.isChanged = False
30

    
31
        appDocData = AppDocData.instance()
32
        project = appDocData.getCurrentProject()
33

    
34
        self.ui = TrainingSymbolEditor_UI.Ui_TrainingSymbolEditorDialog()
35
        self.ui.setupUi(self)
36

    
37
        self.graphicsViewTrainingDrawing = QtImageViewer.QtImageViewer(self)
38
        self.graphicsViewTrainingDrawing.setParent(self.ui.centralWidget)
39
        self.graphicsViewTrainingDrawing.useDefaultCommand()
40
        self.ui.verticalLayoutTrainingDrawing.addWidget(self.graphicsViewTrainingDrawing)
41

    
42
        self.graphicsViewZoomDrawing = QGraphicsView(self)
43
        self.graphicsViewZoomDrawing.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
44
        self.graphicsViewZoomDrawing.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
45
        self.graphicsViewZoomDrawing.setParent(self.ui.leftSideWidget)
46
        self.ui.horizontalLayoutZoomDrawing.addWidget(self.graphicsViewZoomDrawing)
47
        
48
        
49
        # 학습 이미지 읽어서 메인 뷰에 그림, 사이드 뷰에 추가
50
        try:
51
            #trainingImgPath = os.path.join(project.getTrainingFilePath(), 'seed.seedF.exp0.tif')
52
            cvImg = cv2.cvtColor(cv2.imread(trainingImgPath), cv2.COLOR_BGR2GRAY)
53
            cvImg = cv2.threshold(cvImg, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
54
            bytesPerLine = cvImg.shape[1]
55
            image = QImage(cvImg.data, cvImg.shape[1], cvImg.shape[0], bytesPerLine, QImage.Format_Indexed8)
56

    
57
            self.graphicsViewTrainingDrawing.setImage(image)
58

    
59
            # 사이드 뷰
60
            if type(image) is QPixmap:
61
                pixmap = image
62
            elif type(image) is QImage:
63
                pixmap = QPixmap.fromImage(image)
64
            scene = QGraphicsScene()
65
            self.graphicsViewZoomDrawing.setScene(scene)
66
            self._pixmapHandle = self.graphicsViewZoomDrawing.scene().addPixmap(pixmap)
67
            self.graphicsViewZoomDrawing.scene().setSceneRect(0, 0, pixmap.width(), pixmap.height())
68
            self.graphicsViewZoomDrawing.fitInView(self.graphicsViewZoomDrawing.scene().sceneRect(), Qt.KeepAspectRatioByExpanding)
69
            rect = QGraphicsRectItem(0, 0, 0, 0)
70
            pen = QPen(Qt.SolidLine)
71
            pen.setColor(Qt.green)
72
            pen.setWidthF(1)
73
            pen.setJoinStyle(Qt.MiterJoin)
74
            rect.setPen(pen)
75
            rect.setOpacity(0.7)
76
            self.graphicsViewZoomDrawing.scene().addItem(rect)
77

    
78
        except Exception as ex:
79
            from App import App
80
            from AppDocData import MessageType
81

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

    
85
        # 박스 읽어서 메인 뷰에 그림
86
        try:
87
            boxList = []
88
            if self.boundaryLabelData is None: # already exist label file
89
                xml = parse(trainingLabelPath)
90
                root = xml.getroot()
91
                objects = list(root.iter('object'))
92
                for object_node in objects:
93
                    name = object_node.find('name').text
94
                    boxNode = object_node.find('bndbox')
95
                    xMin = int(boxNode.find('xmin').text)
96
                    yMin = int(boxNode.find('ymin').text)
97
                    xMax = int(boxNode.find('xmax').text)
98
                    yMax = int(boxNode.find('ymax').text)
99
                    boxList.append([xMin, yMin, xMax - xMin, yMax - yMin, name])
100
            else:
101
                for data in boundaryLabelData:
102
                    boxList.append([data[0], data[1], data[2], data[3], 'None'])
103

    
104
            for label in boxList:
105
                singleBox = QTrainingBoxItem(label[4], int(label[0]), int(label[1]), int(label[2]), int(label[3]))
106
                singleBox.transfer.onRemoved.connect(self.itemRemoved)
107
                singleBox.addTextItemToScene(self.ui, self.graphicsViewTrainingDrawing, self.graphicsViewZoomDrawing, self.spinBoxFlag)
108
        except Exception as ex:
109
            from App import App
110
            from AppDocData import MessageType
111

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

    
115
        # label table
116
        self.ui.tableWidget.setSelectionMode(QAbstractItemView.SingleSelection) 
117
        self.ui.tableWidget.setColumnCount(1)
118
        self.ui.tableWidget.setSortingEnabled(True)
119

    
120
        self.ui.tableWidget.setHorizontalHeaderLabels([self.tr('Labels')])
121
        self.ui.tableWidget.horizontalHeaderItem(0).setSizeHint(QSize(30, 30))
122
        self.ui.tableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
123
        self.ui.tableWidget.resizeColumnsToContents()
124
        self.ui.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
125
        self.ui.tableWidget.verticalHeader().hide()
126

    
127
        self.ui.tableWidget.setRowCount(len(parent.labelList))
128
        row = 0
129
        for label in parent.labelList:
130
            textWidget = QTableWidgetItem(label[0])
131
            self.ui.tableWidget.setItem(row, 0, textWidget)
132
            row += 1
133

    
134
        self.ui.tableWidget.cellClicked.connect(self.listCellClicked)
135
        self.ui.pushButtonZoom.clicked.connect(self.onAreaZoom)
136
        self.ui.pushButtonSave.clicked.connect(self.pushButtonSaveClicked)
137
        self.ui.pushButtonCancel.clicked.connect(self.pushButtonCancelClicked)
138
        self.ui.pushButtonDelete.clicked.connect(self.pushButtonDeleteClicked)
139
        self.ui.pushButtonAdd.clicked.connect(self.pushButtonAddClicked)
140
        self.ui.pushButtonSplit.clicked.connect(self.pushButtonSplitClicked)
141
        self.ui.spinBoxLeft.valueChanged.connect(self.spinBoxChangedEvent)
142
        self.ui.spinBoxTop.valueChanged.connect(self.spinBoxChangedEvent)
143
        self.ui.spinBoxWidth.valueChanged.connect(self.spinBoxChangedEvent)
144
        self.ui.spinBoxHeight.valueChanged.connect(self.spinBoxChangedEvent)
145
        self.ui.lineEditChar.returnPressed.connect(self.pushButtonChangeClicked)
146

    
147
        self.removedItems = []
148

    
149
    def listCellClicked(self, row, col):
150
        self.isChanged = True
151
        items = self.graphicsViewTrainingDrawing.scene().selectedItems()
152
        label = self.ui.tableWidget.item(row, col).text()
153
        self.onCharChanged(label)
154

    
155
    '''
156
        @brief      split boxitem by button click
157
        @author     euisung
158
        @date       2018.10.31
159
    '''
160
    def pushButtonSplitClicked(self):
161
        self.isChanged = True
162
        items = self.graphicsViewTrainingDrawing.scene().selectedItems()
163
        if len(items) == 1:
164
            return
165
        rect = items[0].rect()
166
        secondBox = QTrainingBoxItem('', rect.x() + round(rect.width() / 2), rect.y(), round(rect.width() / 2), rect.height())
167
        secondBox.transfer.onRemoved.connect(self.itemRemoved)
168
        secondBox.addTextItemToScene(self.ui, self.graphicsViewTrainingDrawing, self.graphicsViewZoomDrawing, self.spinBoxFlag)
169
        items[0].setRect(rect.x(), rect.y(), round(rect.width() / 2), rect.height())
170

    
171
    '''
172
        @brief      add boxitem by button click
173
        @author     euisung
174
        @date       2018.10.17
175
    '''
176
    def pushButtonAddClicked(self):
177
        self.isChanged = True
178
        items = self.graphicsViewTrainingDrawing.scene().selectedItems()
179
        allItems = self.graphicsViewTrainingDrawing.scene().items()
180
        totalWidth = 0
181
        totalHeight = 0
182
        count = 0
183
        if len(items) == 1:
184
            rect = items[0].rect()
185
        elif len(allItems) > 1:
186
            for item in allItems:
187
                if type(item) is QGraphicsPixmapItem:
188
                    x = int(item.boundingRect().width() / 2)
189
                    y = int(item.boundingRect().height()/ 2)
190
                elif hasattr(item, 'rect'):
191
                    count += 1
192
                    totalWidth += item.rect().width()
193
                    totalHeight += item.rect().height()
194

    
195
            rect = QRectF(x, y, round(totalWidth / count), round(totalHeight / count))
196
        else:
197
            for item in allItems:
198
                if type(item) is QGraphicsPixmapItem:
199
                    x = int(item.boundingRect().width() / 2)
200
                    y = int(item.boundingRect().height()/ 2)
201
            rect = QRectF(x, y, 5, 10)
202
        singleBox = QTrainingBoxItem('', rect.x() + 3, rect.y(), rect.width(), rect.height())
203
        singleBox.transfer.onRemoved.connect(self.itemRemoved)
204
        singleBox.addTextItemToScene(self.ui, self.graphicsViewTrainingDrawing, self.graphicsViewZoomDrawing, self.spinBoxFlag)
205

    
206
    '''
207
        @brief      delete boxitem by button click
208
        @author     euisung
209
        @date       2018.10.17
210
    '''
211
    def pushButtonDeleteClicked(self):
212
        self.isChanged = True
213
        items = self.graphicsViewTrainingDrawing.scene().selectedItems()
214
        for item in items:
215
            item.transfer.onRemoved.emit(item)
216

    
217
    def onCharChanged(self, text):
218
        """
219
        change lineedit's text with given text
220
        """
221
        if text:
222
            self.ui.lineEditChar.setText(text)
223
            self.pushButtonChangeClicked()
224

    
225
    '''
226
        @brief      change boxitem char by button click
227
        @author     euisung
228
        @date       2018.10.17
229
    '''
230
    def pushButtonChangeClicked(self):
231
        self.isChanged = True
232
        items = self.graphicsViewTrainingDrawing.scene().selectedItems()
233

    
234
        label = self.ui.lineEditChar.text()
235
        if label == '' or label == 'None':
236
            return
237

    
238
        for box in [box for box in items if type(box) is QTrainingBoxItem]:
239
            box.char = label
240
            box.textShowBox.setHtml(label)
241

    
242
        rowCount = self.ui.tableWidget.rowCount()
243
        for index in range(rowCount):
244
            if self.ui.tableWidget.item(index, 0).text() == label:
245
                return
246
    
247
        self.ui.tableWidget.setRowCount(rowCount + 1)
248
        textWidget = QTableWidgetItem(label)
249
        self.ui.tableWidget.setItem(rowCount, 0, textWidget)
250

    
251
    '''
252
        @brief      close dialog by button click
253
        @author     euisung
254
        @date       2018.10.17
255
    '''
256
    def pushButtonCancelClicked(self):
257
        if self.isChanged:
258
            reply = QMessageBox.question(self, self.tr('Continue?'), self.tr('Changes may not have been saved.'), QMessageBox.Ignore, QMessageBox.Cancel)
259
            if reply == QMessageBox.Ignore:
260
                QDialog.reject(self)
261
        else:
262
            QDialog.reject(self)
263

    
264
    '''
265
        @brief      save box item by button click
266
        @author     euisung
267
        @date       2018.10.16
268
    '''
269
    def pushButtonSaveClicked(self):
270
        xml = Element('annotation')
271
        SubElement(xml, 'folder').text = 'None'
272
        SubElement(xml, 'filename').text = os.path.basename(self.trainingImgPath)
273

    
274
        pathNode = Element('path')
275
        pathNode.text = self.trainingImgPath.replace('/', '\\')
276
        xml.append(pathNode)
277

    
278
        sourceNode = Element('source')
279
        databaseNode = Element('database')
280
        databaseNode.text = 'Unknown'
281
        sourceNode.append(databaseNode)
282
        xml.append(sourceNode)
283

    
284
        items = self.graphicsViewTrainingDrawing.scene().items()
285
        for item in items:
286
            if type(item) is QGraphicsPixmapItem:
287
                back = item
288
                break
289

    
290
        sizeNode = Element('size')
291
        widthNode = Element('width')
292
        widthNode.text = str(int(back.boundingRect().width()))
293
        sizeNode.append(widthNode)
294
        heightNode = Element('height')
295
        heightNode.text = str(int(back.boundingRect().height()))
296
        sizeNode.append(heightNode)
297
        depthNode = Element('depth')
298
        depthNode.text = '3'
299
        sizeNode.append(depthNode)
300
        xml.append(sizeNode)
301

    
302
        segmentedNode = Element('segmented')
303
        segmentedNode.text = '0'
304
        xml.append(segmentedNode)
305

    
306
        labelContent = []
307
        counts = {}
308
        for item in items:
309
            if type(item) is QTrainingBoxItem:
310
                rect = item.rect()
311
                label, xMin, yMin, xMax, yMax = item.char, int(rect.x()), int(rect.y()), int(rect.x()) + int(rect.width()), int(rect.y()) + int(rect.height())
312

    
313
                if label == 'None' or label == '':
314
                    continue
315
                if label not in labelContent:
316
                    labelContent.append(label)
317
                    counts[label] = 1
318
                else:
319
                    counts[label] = counts[label] + 1
320

    
321
                objectNode = Element('object')
322
                nameNode = Element('name')
323
                nameNode.text = label
324
                objectNode.append(nameNode)
325
                poseNode = Element('pose')
326
                poseNode.text = 'Unspecified'
327
                objectNode.append(poseNode)
328
                truncatedNode = Element('truncated')
329
                truncatedNode.text = '0'
330
                objectNode.append(truncatedNode)
331
                difficultNode = Element('difficult')
332
                difficultNode.text = '0'
333
                objectNode.append(difficultNode)
334

    
335
                bndboxNode = Element('bndbox')
336
                xminNode = Element('xmin')
337
                xminNode.text = str(xMin)
338
                bndboxNode.append(xminNode)
339
                yminNode = Element('ymin')
340
                yminNode.text = str(yMin)
341
                bndboxNode.append(yminNode)
342
                xmaxNode = Element('xmax')
343
                xmaxNode.text = str(xMax)
344
                bndboxNode.append(xmaxNode)
345
                ymaxNode = Element('ymax')
346
                ymaxNode.text = str(yMax)
347
                bndboxNode.append(ymaxNode)
348
                objectNode.append(bndboxNode)
349

    
350
                xml.append(objectNode)
351

    
352
        ElementTree(xml).write(self.trainingLabelPath)
353
        
354
        modifiedTime = str(datetime.datetime.strptime(time.ctime(os.path.getmtime(self.trainingLabelPath)), "%a %b %d %H:%M:%S %Y"))
355
        self.dateItem.setText(modifiedTime)
356

    
357
        labelCount = [label + ':' + str(counts[label]) for label in labelContent]
358
        labels = ','.join(labelCount)
359
        self.boxItem.setText(labels)
360

    
361
        self.isChanged = False
362
        reply = QMessageBox.information(self, self.tr('Information'), self.tr('Save Success!'), QMessageBox.Ok, QMessageBox.Cancel)
363
        if reply == QMessageBox.Ok:
364
            QDialog.reject(self)
365

    
366
    '''
367
        @brief      chane box item's rect by button click
368
        @author     euisung
369
        @date       2018.10.16
370
    '''
371
    def spinBoxChangedEvent(self, event):
372
        self.isChanged = True
373
        items = self.graphicsViewTrainingDrawing.scene().selectedItems()
374
        if (len(items) != 1) or self.spinBoxFlag:
375
            return
376
        spinBoxName = self.sender().objectName()
377
        rect = items[0].rect()
378
        bound = self.graphicsViewZoomDrawing.scene().items()[0]
379

    
380
        if spinBoxName == 'spinBoxLeft':
381
            spinBoxValue = self.ui.spinBoxLeft.value()
382
            items[0].setRect(QRectF(spinBoxValue, rect.y(), rect.width(), rect.height()))
383
            items[0].setPosCustom()
384
        elif spinBoxName == 'spinBoxTop':
385
            spinBoxValue = self.ui.spinBoxTop.value()
386
            items[0].setRect(QRectF(rect.x(), spinBoxValue, rect.width(), rect.height()))
387
            items[0].setPosCustom()
388
        elif spinBoxName == 'spinBoxWidth':
389
            spinBoxValue = self.ui.spinBoxWidth.value()
390
            items[0].setRect(QRectF(rect.x(),  rect.y(), spinBoxValue, rect.height()))
391
            items[0].setPosCustom()
392
        elif spinBoxName == 'spinBoxHeight':
393
            spinBoxValue = self.ui.spinBoxHeight.value()
394
            items[0].setRect(QRectF(rect.x(), rect.y(), rect.width(), spinBoxValue))
395
            items[0].setPosCustom()
396

    
397
        rect = items[0].rect()        
398
        bound.setRect(rect)
399
        rectSide = QRectF(rect.x() - 3, rect.y() - 3, rect.width() + 6,  rect.height() + 6)
400
        self.graphicsViewZoomDrawing.fitInView(rectSide)
401
        self.graphicsViewTrainingDrawing.scene().update()
402
    
403
    def onAreaZoom(self, action):
404
        if self.ui.pushButtonZoom.isChecked():
405
            cmd = AreaZoomCommand.AreaZoomCommand(self.graphicsViewTrainingDrawing)
406
            cmd.onRejected.connect(self.onCommandRejected)
407
            self.graphicsViewTrainingDrawing.command = cmd
408

    
409
    def onCommandRejected(self, cmd):
410
        try:
411
            if type(cmd) is AreaZoomCommand.AreaZoomCommand:
412
                self.ui.pushButtonZoom.setChecked(False)
413
        finally:
414
            self.graphicsViewTrainingDrawing.useDefaultCommand()
415

    
416
    def itemRemoved(self, item):
417
        try:
418
            if type(item) is QTrainingBoxItem:
419
                self.removedItems.append(str(item.uid))
420

    
421
            if item.scene is not None: item.scene().removeItem(item)
422
        except Exception as ex:
423
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
424

    
425
    '''
426
        @brief      key pressed event
427
        @author     euisung
428
        @date       2018.11.05
429
    '''
430
    def keyPressEvent(self, event):
431
        try:
432
            items = self.graphicsViewTrainingDrawing.scene().selectedItems()
433
            if items and type(items[0]) is not QTrainingBoxItem:
434
                return
435
                
436
            selectedItem = items[0]
437
            closestItem = None
438
            x = selectedItem.rect().x()
439

    
440
            if event.key() == Qt.Key_Left:
441
                dx =  sys.maxsize
442
                for item in self.graphicsViewTrainingDrawing.scene().items():
443
                    if type(item) is QTrainingBoxItem:
444
                        if x - item.rect().x() > 0 and x - item.rect().x() < dx:
445
                            closestItem = item
446
                            dx = x - item.rect().x()
447
                if closestItem is not None:
448
                    closestItem.setSelected(True)
449
                    selectedItem.setSelected(False)
450
                    closestItem.mousePressEvent('arrow key')             
451
            elif event.key() == Qt.Key_Right:
452
                dx = -sys.maxsize
453
                for item in self.graphicsViewTrainingDrawing.scene().items():
454
                    if type(item) is QTrainingBoxItem:
455
                        if x - item.rect().x() < 0 and x - item.rect().x() > dx:
456
                            closestItem = item
457
                            dx = x - item.rect().x()
458
                if closestItem is not None:
459
                    closestItem.setSelected(True)
460
                    selectedItem.setSelected(False)
461
                    closestItem.mousePressEvent('arrow key')
462
            elif event.key() == Qt.Key_Up:
463
                self.ui.spinBoxWidth.setValue(self.ui.spinBoxWidth.value() + 1)
464
            elif event.key() == Qt.Key_Down:
465
                self.ui.spinBoxWidth.setValue(self.ui.spinBoxWidth.value() - 1)
466
            elif event.key() == Qt.Key_Return and not self.ui.lineEditChar.hasFocus():
467
                if self.ui.tableWidget.selectedItems():
468
                    self.isChanged = True
469
                    self.onCharChanged(self.ui.tableWidget.selectedItems()[0].text())
470
        except Exception as ex:
471
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
472
            from App import App
473
            from AppDocData import MessageType
474

    
475
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
476
            App.mainWnd().addMessage.emit(MessageType.Error, message)
477
            return None
클립보드 이미지 추가 (최대 크기: 500 MB)