labeler.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import sys
  2. import json
  3. from PyQt5.QtWidgets import (
  4. QApplication, QMainWindow, QLabel, QPushButton, QColorDialog, QInputDialog,
  5. QFileDialog, QMessageBox, QVBoxLayout, QHBoxLayout, QWidget
  6. )
  7. from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QColor
  8. from PyQt5.QtCore import Qt, QPoint
  9. class ImageLabel(QLabel):
  10. def __init__(self, parent=None):
  11. super().__init__(parent)
  12. self.setMouseTracking(True)
  13. self.image = QImage()
  14. self.rois = []
  15. self.current_roi = []
  16. self.drawing = False
  17. self.current_color = QColor(Qt.red)
  18. self.current_label = "ROI"
  19. self.undo_stack = []
  20. def load_image(self, file_path):
  21. self.image = QImage(file_path)
  22. if self.image.isNull():
  23. raise ValueError("Failed to load image.")
  24. self.setPixmap(QPixmap.fromImage(self.image))
  25. self.rois.clear()
  26. self.current_roi.clear()
  27. self.update()
  28. def set_current_color(self, color):
  29. self.current_color = color
  30. def set_current_label(self, label):
  31. self.current_label = label
  32. def mousePressEvent(self, event):
  33. if event.button() == Qt.LeftButton:
  34. self.drawing = True
  35. self.current_roi = [event.pos()]
  36. self.update()
  37. def mouseMoveEvent(self, event):
  38. if self.drawing:
  39. self.current_roi.append(event.pos())
  40. self.update()
  41. def mouseReleaseEvent(self, event):
  42. if event.button() == Qt.LeftButton and self.drawing:
  43. self.drawing = False
  44. if len(self.current_roi) > 2:
  45. self.rois.append({
  46. 'label': self.current_label,
  47. 'color': self.current_color,
  48. 'points': self.current_roi
  49. })
  50. self.undo_stack.append(self.rois[-1])
  51. self.current_roi = []
  52. self.update()
  53. def paintEvent(self, event):
  54. super().paintEvent(event)
  55. if not self.image.isNull():
  56. painter = QPainter(self)
  57. painter.drawImage(self.rect(), self.image, self.image.rect())
  58. pen = QPen(Qt.red, 2, Qt.SolidLine)
  59. painter.setPen(pen)
  60. for roi in self.rois:
  61. pen.setColor(roi['color'])
  62. painter.setPen(pen)
  63. points = [QPoint(p.x(), p.y()) for p in roi['points']]
  64. painter.drawPolygon(*points)
  65. if self.current_roi:
  66. pen.setColor(Qt.blue)
  67. painter.setPen(pen)
  68. points = [QPoint(p.x(), p.y()) for p in self.current_roi]
  69. painter.drawPolyline(*points)
  70. painter.end()
  71. def undo(self):
  72. if self.undo_stack:
  73. last_action = self.undo_stack.pop()
  74. if last_action in self.rois:
  75. self.rois.remove(last_action)
  76. self.update()
  77. def save_rois(self, file_path):
  78. roi_data = []
  79. for roi in self.rois:
  80. roi_data.append({
  81. 'label': roi['label'],
  82. 'color': roi['color'].name(),
  83. 'points': [(point.x(), point.y()) for point in roi['points']]
  84. })
  85. with open(file_path, 'w') as file:
  86. json.dump(roi_data, file, indent=4)
  87. class ROIDrawer(QMainWindow):
  88. def __init__(self):
  89. super().__init__()
  90. self.initUI()
  91. def initUI(self):
  92. self.setWindowTitle('ROI Drawer')
  93. self.setGeometry(100, 100, 1000, 600)
  94. # Central widget
  95. central_widget = QWidget(self)
  96. self.setCentralWidget(central_widget)
  97. # Layouts
  98. main_layout = QHBoxLayout(central_widget)
  99. image_layout = QVBoxLayout()
  100. button_layout = QVBoxLayout()
  101. # Image display
  102. self.image_label = ImageLabel(self)
  103. image_layout.addWidget(self.image_label)
  104. # Buttons
  105. load_button = QPushButton('Load Image', self)
  106. load_button.clicked.connect(self.load_image)
  107. button_layout.addWidget(load_button)
  108. color_button = QPushButton('Select Color', self)
  109. color_button.clicked.connect(self.select_color)
  110. button_layout.addWidget(color_button)
  111. label_button = QPushButton('Set Label', self)
  112. label_button.clicked.connect(self.set_label)
  113. button_layout.addWidget(label_button)
  114. undo_button = QPushButton('Undo', self)
  115. undo_button.clicked.connect(self.image_label.undo)
  116. button_layout.addWidget(undo_button)
  117. save_button = QPushButton('Save ROIs', self)
  118. save_button.clicked.connect(self.save_rois)
  119. button_layout.addWidget(save_button)
  120. button_layout.addStretch(1) # Push buttons to the top
  121. # Add layouts to main layout
  122. main_layout.addLayout(image_layout, 4)
  123. main_layout.addLayout(button_layout, 1)
  124. def load_image(self):
  125. options = QFileDialog.Options()
  126. file_path, _ = QFileDialog.getOpenFileName(
  127. self, "Open Image File", "", "Images (*.png *.jpg *.bmp *.tiff)", options=options)
  128. if file_path:
  129. try:
  130. self.image_label.load_image(file_path)
  131. except ValueError as e:
  132. QMessageBox.critical(self, "Error", str(e))
  133. def select_color(self):
  134. color = QColorDialog.getColor()
  135. if color.isValid():
  136. self.image_label.set_current_color(color)
  137. def set_label(self):
  138. label, ok = QInputDialog.getText(self, 'Set ROI Label', 'Enter label for ROI:')
  139. if ok and label:
  140. self.image_label.set_current_label(label)
  141. def save_rois(self):
  142. options = QFileDialog.Options()
  143. file_path, _ = QFileDialog.getSaveFileName(
  144. self, "Save ROIs", "", "JSON Files (*.json);;All Files (*)", options=options)
  145. if file_path:
  146. self.image_label.save_rois(file_path)
  147. if __name__ == '__main__':
  148. app = QApplication(sys.argv)
  149. drawer = ROIDrawer()
  150. drawer.show()
  151. sys.exit(app.exec_())