├── libs
├── __init__.py
├── version.py
├── ustr.py
├── constants.py
├── settings.py
├── lib.py
├── scene.py
└── canvas.py
├── README.md
├── resources.qrc
├── utils.py
└── annotator.py
/libs/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | python annotator.py
2 |
--------------------------------------------------------------------------------
/libs/version.py:
--------------------------------------------------------------------------------
1 | __version_info__ = ('1', '5', '2')
2 | __version__ = '.'.join(__version_info__)
3 |
--------------------------------------------------------------------------------
/libs/ustr.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | def ustr(x):
4 | '''py2/py3 unicode helper'''
5 |
6 | if sys.version_info < (3, 0, 0):
7 | from PyQt4.QtCore import QString
8 | if type(x) == str:
9 | return x.decode('utf-8')
10 | if type(x) == QString:
11 | return unicode(x)
12 | return x
13 | else:
14 | return x # py3
15 |
--------------------------------------------------------------------------------
/libs/constants.py:
--------------------------------------------------------------------------------
1 | SETTING_FILENAME = 'filename'
2 | SETTING_RECENT_FILES = 'recentFiles'
3 | SETTING_WIN_SIZE = 'window/size'
4 | SETTING_WIN_POSE = 'window/position'
5 | SETTING_WIN_GEOMETRY = 'window/geometry'
6 | SETTING_LINE_COLOR = 'line/color'
7 | SETTING_FILL_COLOR = 'fill/color'
8 | SETTING_ADVANCE_MODE = 'advanced'
9 | SETTING_WIN_STATE = 'window/state'
10 | SETTING_SAVE_DIR = 'savedir'
11 | SETTING_LAST_OPEN_DIR = 'lastOpenDir'
12 | SETTING_AUTO_SAVE = 'autosave'
13 | SETTING_SINGLE_CLASS = 'singleclass'
--------------------------------------------------------------------------------
/libs/settings.py:
--------------------------------------------------------------------------------
1 | import pickle
2 | import os
3 | import sys
4 |
5 | class Settings(object):
6 | def __init__(self):
7 | # Be default, the home will be in the same folder as labelImg
8 | home = os.path.expanduser("~")
9 | self.data = {}
10 | self.path = os.path.join(home, '.labelImgSettings.pkl')
11 |
12 | def __setitem__(self, key, value):
13 | self.data[key] = value
14 |
15 | def __getitem__(self, key):
16 | return self.data[key]
17 |
18 | def get(self, key, default=None):
19 | if key in self.data:
20 | return self.data[key]
21 | return default
22 |
23 | def save(self):
24 | if self.path:
25 | with open(self.path, 'wb') as f:
26 | pickle.dump(self.data, f, pickle.HIGHEST_PROTOCOL)
27 | return True
28 | return False
29 |
30 | def load(self):
31 | if os.path.exists(self.path):
32 | with open(self.path, 'rb') as f:
33 | self.data = pickle.load(f)
34 | return True
35 | return False
36 |
37 | def reset(self):
38 | if os.path.exists(self.path):
39 | os.remove(self.path)
40 | print ('Remove setting pkl file ${0}'.format(self.path))
41 | self.data = {}
42 | self.path = None
--------------------------------------------------------------------------------
/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | icons/help.png
5 | icons/expert2.png
6 | icons/expert2.png
7 | icons/done.png
8 | icons/file.png
9 | icons/labels.png
10 | icons/objects.png
11 | icons/close.png
12 | icons/fit-width.png
13 | icons/fit-window.png
14 | icons/undo.png
15 | icons/eye.png
16 | icons/quit.png
17 | icons/copy.png
18 | icons/edit.png
19 | icons/open.png
20 | icons/save.png
21 | icons/save-as.png
22 | icons/color.png
23 | icons/color_line.png
24 | icons/zoom.png
25 | icons/zoom-in.png
26 | icons/zoom-out.png
27 | icons/cancel.png
28 | icons/next.png
29 | icons/prev.png
30 | icons/resetall.png
31 | icons/verify.png
32 |
33 |
34 |
--------------------------------------------------------------------------------
/libs/lib.py:
--------------------------------------------------------------------------------
1 | from math import sqrt, atan2
2 | from libs.ustr import ustr
3 | import hashlib
4 | try:
5 | from PyQt5.QtGui import *
6 | from PyQt5.QtCore import *
7 | from PyQt5.QtWidgets import *
8 | except ImportError:
9 | from PyQt4.QtGui import *
10 | from PyQt4.QtCore import *
11 |
12 |
13 | def newIcon(icon):
14 | return QIcon(':/' + icon)
15 |
16 |
17 | def newButton(text, icon=None, slot=None):
18 | b = QPushButton(text)
19 | if icon is not None:
20 | b.setIcon(newIcon(icon))
21 | if slot is not None:
22 | b.clicked.connect(slot)
23 | return b
24 |
25 |
26 | def newAction(parent, text, slot=None, shortcut=None, icon=None,
27 | tip=None, checkable=False, enabled=True):
28 | """Create a new action and assign callbacks, shortcuts, etc."""
29 | a = QAction(text, parent)
30 | if icon is not None:
31 | a.setIcon(newIcon(icon))
32 | if shortcut is not None:
33 | if isinstance(shortcut, (list, tuple)):
34 | a.setShortcuts(shortcut)
35 | else:
36 | a.setShortcut(shortcut)
37 | if tip is not None:
38 | a.setToolTip(tip)
39 | a.setStatusTip(tip)
40 | if slot is not None:
41 | a.triggered.connect(slot)
42 | if checkable:
43 | a.setCheckable(True)
44 | a.setEnabled(enabled)
45 | return a
46 |
47 |
48 | def addActions(widget, actions):
49 | for action in actions:
50 | if action is None:
51 | widget.addSeparator()
52 | elif isinstance(action, QMenu):
53 | widget.addMenu(action)
54 | else:
55 | widget.addAction(action)
56 |
57 |
58 | def labelValidator():
59 | return QRegExpValidator(QRegExp(r'^[^ \t].+'), None)
60 |
61 |
62 | class struct(object):
63 |
64 | def __init__(self, **kwargs):
65 | self.__dict__.update(kwargs)
66 |
67 |
68 | def distance(p):
69 | return sqrt(p.x() * p.x() + p.y() * p.y())
70 |
71 | def calcAngle(p):
72 | return atan2(p.y(), p.x())
73 |
74 | def fmtShortcut(text):
75 | mod, key = text.split('+', 1)
76 | return '%s+%s' % (mod, key)
77 |
78 |
79 | def generateColorByText(text):
80 | s = str(ustr(text))
81 | hashCode = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16)
82 | r = int((hashCode / 255) % 255)
83 | g = int((hashCode / 65025) % 255)
84 | b = int((hashCode / 16581375) % 255)
85 | return QColor(r, g, b, 100)
86 |
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | class ColorPalette:
4 | def __init__(self, numColors):
5 | np.random.seed(1)
6 |
7 | self.colorMap = np.array([[255, 0, 0],
8 | [50, 150, 0],
9 | [0, 0, 255],
10 | [80, 128, 255],
11 | [255, 230, 180],
12 | [255, 0, 255],
13 | [0, 255, 255],
14 | [255, 255, 0],
15 | [0, 255, 0],
16 | [200, 255, 255],
17 | [255, 200, 255],
18 | [100, 0, 0],
19 | [0, 100, 0],
20 | [128, 128, 80],
21 | [0, 50, 128],
22 | [0, 100, 100],
23 | [0, 255, 128],
24 | [0, 128, 255],
25 | [255, 0, 128],
26 | [128, 0, 255],
27 | [255, 128, 0],
28 | [128, 255, 0],
29 | ], dtype=np.uint8)
30 | self.colorMap = np.concatenate([self.colorMap, self.colorMap], axis=0)
31 |
32 | #self.colorMap = np.maximum(self.colorMap, 1)
33 |
34 | if numColors > self.colorMap.shape[0]:
35 | self.colorMap = np.concatenate([self.colorMap, np.random.randint(255, size = (numColors, 3), dtype=np.uint8)], axis=0)
36 | pass
37 |
38 | return
39 |
40 | def getColorMap(self):
41 | return self.colorMap
42 |
43 | def getColor(self, index):
44 | if index >= colorMap.shape[0]:
45 | return np.random.randint(255, size = (3), dtype=np.uint8)
46 | else:
47 | return self.colorMap[index]
48 | pass
49 |
50 | def intersectFaceLine(face, line, return_ratio=False):
51 | faceNormal = np.cross(face[1] - face[0], face[2] - face[0])
52 | faceArea = 0
53 | for c in xrange(1, len(face) - 1):
54 | faceArea += np.linalg.norm(np.cross(face[c] - face[0], face[c + 1] - face[c])) / 2
55 | pass
56 | faceNormal /= np.maximum(faceArea * 2, 1e-4)
57 | faceOffset = np.sum(faceNormal * face[0])
58 | offset_1 = np.sum(faceNormal * line[0])
59 | offset_2 = np.sum(faceNormal * line[1])
60 | if offset_2 == offset_1:
61 | if return_ratio:
62 | return False, 0
63 | else:
64 | return False
65 |
66 | alpha = (faceOffset - offset_1) / (offset_2 - offset_1)
67 | if alpha <= 0 or alpha >= 1:
68 | if return_ratio:
69 | return False, alpha
70 | else:
71 | return False
72 |
73 | point = line[0] + alpha * (line[1] - line[0])
74 | intersectionArea = 0
75 | for c in xrange(len(face)):
76 | intersectionArea += np.linalg.norm(np.cross(point - face[c], point - face[(c + 1) % len(face)])) / 2
77 | continue
78 | #print(intersectionArea, faceArea)
79 | if intersectionArea <= faceArea + 1e-4:
80 | if return_ratio:
81 | return True, alpha
82 | else:
83 | return True
84 | else:
85 | if return_ratio:
86 | return False, alpha
87 | else:
88 | return False
89 | return
90 |
91 |
92 | if __name__ == '__main__':
93 | line = [np.array([ 2.4764291 , 4.37349266, -9.5168555 ]), np.array([ 2.4764291 , 4.37349266, 10.4831445 ])]
94 | face = [np.array([2.1361478 , 0.01942726, 0.06335368]), np.array([8.41647591, 2.27955277, 0.06335368]), np.array([6.15570054, 8.74293862, 0.06335368]), np.array([-0.12478519, 6.48326369, 0.06335368])]
95 | intersection, ratio = intersectFaceLine(face, line, return_ratio=True)
96 | print(face)
97 | print(line)
98 | print(intersection, ratio)
99 | exit(1)
100 |
--------------------------------------------------------------------------------
/annotator.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | from functools import partial
4 | import numpy as np
5 |
6 | try:
7 | from PyQt5.QtGui import *
8 | from PyQt5.QtCore import *
9 | from PyQt5.QtWidgets import *
10 | except ImportError:
11 | # needed for py3+qt4
12 | # Ref:
13 | # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
14 | # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string
15 | if sys.version_info.major >= 3:
16 | import sip
17 | sip.setapi('QVariant', 2)
18 | from PyQt4.QtGui import *
19 | from PyQt4.QtCore import *
20 |
21 | import resources
22 | # Add internal libs
23 | from libs.constants import *
24 | from libs.lib import struct, newAction, newIcon, addActions, fmtShortcut, generateColorByText
25 | from libs.settings import Settings
26 | from libs.canvas import Canvas
27 | from libs.ustr import ustr
28 | from libs.version import __version__
29 | import glob
30 |
31 | __appname__ = 'annotator'
32 |
33 | class MainWindow(QMainWindow):
34 | FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = list(range(3))
35 |
36 | def __init__(self):
37 | super(MainWindow, self).__init__()
38 | self.setWindowTitle(__appname__)
39 |
40 | self.settings = Settings()
41 | self.settings.load()
42 | settings = self.settings
43 |
44 | self.dataFolder = '../floor_plan_chinese/'
45 |
46 | self.canvas = Canvas()
47 |
48 | self.setCentralWidget(self.canvas)
49 |
50 | action = partial(newAction, self)
51 |
52 | nextU = action('&NextU', self.moveToNextUnannotated,
53 | 'n', 'nextU', u'Move to next unannotated example')
54 | next = action('&Next', self.moveToNext,
55 | 'Ctrl+n', 'next', u'Move to next example')
56 |
57 |
58 | # Store actions for further handling.
59 | self.actions = struct(nextU=nextU, next=next)
60 |
61 |
62 | #self.scenePaths = os.listdir(self.dataFolder)
63 | imagePaths = glob.glob('../floor_plan_chinese/*') + glob.glob('../floor_plan_chinese/*/*') + glob.glob('../floor_plan_chinese/*/*/*') + glob.glob('../floor_plan_chinese/*/*/*/*')
64 | self.imagePaths = [imagePath for imagePath in imagePaths if '.jpg' in imagePath or '.png' in imagePath or '.jpeg' in imagePath]
65 | print(len(self.imagePaths))
66 | self.imageIndex = 0
67 |
68 | self.moveToNextUnannotated()
69 |
70 | size = settings.get(SETTING_WIN_SIZE, QSize(640, 480))
71 | position = settings.get(SETTING_WIN_POSE, QPoint(0, 0))
72 | self.resize(size)
73 | self.move(position)
74 |
75 | self.queueEvent(self.loadImage)
76 |
77 |
78 | def paintCanvas(self):
79 | #assert not self.image.isNull(), "cannot paint null image"
80 | self.canvas.adjustSize()
81 | self.canvas.update()
82 | return
83 |
84 | def moveToNextUnannotated(self):
85 | self.imageIndex = (self.imageIndex + 1) % len(self.imagePaths)
86 | self.loadImage()
87 | return
88 |
89 | def moveToNext(self):
90 | self.imageIndex = (self.imageIndex + 1) % len(self.imagePaths)
91 | self.loadImage()
92 | return
93 |
94 | def loadImage(self):
95 | imagePath = self.imagePaths[self.imageIndex]
96 | self.canvas.loadScene(imagePath)
97 | self.paintCanvas()
98 | self.setWindowTitle(__appname__ + ' ' + imagePath)
99 | self.canvas.setFocus(True)
100 | return
101 |
102 |
103 | def queueEvent(self, function):
104 | QTimer.singleShot(0, function)
105 | return
106 |
107 |
108 | def get_main_app(argv=[]):
109 | """
110 | Standard boilerplate Qt application code.
111 | Do everything but app.exec_() -- so that we can test the application in one thread
112 | """
113 | app = QApplication(argv)
114 | app.setApplicationName(__appname__)
115 | app.setWindowIcon(newIcon("app"))
116 | # Tzutalin 201705+: Accept extra agruments to change predefined class file
117 | # Usage : labelImg.py image predefClassFile
118 | win = MainWindow()
119 | win.show()
120 | return app, win
121 |
122 |
123 | def main(argv=[]):
124 | '''construct main app and run it'''
125 | app, _win = get_main_app(argv)
126 | return app.exec_()
127 |
128 | if __name__ == '__main__':
129 | sys.exit(main(sys.argv))
130 |
--------------------------------------------------------------------------------
/libs/scene.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | try:
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtCore import *
6 | except ImportError:
7 | from PyQt4.QtGui import *
8 | from PyQt4.QtCore import *
9 | import copy
10 | import os
11 | from utils import *
12 | import cv2
13 | import glob
14 | import json
15 |
16 | COLOR_MAP = [QColor(255, 0, 0), QColor(0, 255, 0), QColor(0, 0, 255), QColor(255, 0, 255)]
17 |
18 |
19 | class Scene():
20 | def __init__(self, scenePath):
21 | self.colorMap = ColorPalette(1000).getColorMap()
22 | self.scenePath = scenePath
23 |
24 | self.imageWidth = 256
25 | self.imageHeight = 256
26 |
27 | image = cv2.imread(scenePath)
28 | self.topdownImage = QPixmap(QImage(image.reshape(-1), image.shape[1], image.shape[0], image.shape[1] * 3, QImage.Format_RGB888))
29 | os.system('mkdir ' + self.scenePath + '_annotation')
30 | self.reset()
31 | return
32 |
33 | def reset(self, mode='load'):
34 | self.loading = True
35 | #self.topdownImage = None
36 |
37 | self.corners = []
38 | self.connections = []
39 | self.prevCornerIndex = -1
40 |
41 | if os.path.exists(self.scenePath + '_annotation/corners.npy'):
42 | self.corners = np.load(self.scenePath + '_annotation/corners.npy').tolist()
43 | pass
44 | if os.path.exists(self.scenePath + '_annotation/connections.npy'):
45 | self.connections = np.load(self.scenePath + '_annotation/connections.npy').tolist()
46 | pass
47 |
48 | #self.fixCornersOnEdges()
49 | self.load()
50 | return
51 |
52 | def fixCornersOnEdges(self, epsilon=10):
53 | while True:
54 | hasChange = False
55 | for cornerIndex, corner in enumerate(self.corners):
56 | for connection in self.connections:
57 | if cornerIndex in connection:
58 | continue
59 | corner_1 = np.array(self.corners[connection[0]])
60 | corner_2 = np.array(self.corners[connection[1]])
61 | normal = corner_1 - corner_2
62 | normal /= max(np.linalg.norm(normal), 1e-4)
63 | normal = np.array([normal[1], -normal[0]])
64 | distance = np.dot(corner_1, normal) - np.dot(corner, normal)
65 | if abs(distance) < epsilon * 2 and ((abs(normal[1]) > abs(normal[0]) and corner[0] > min(corner_1[0], corner_2[0]) and corner[0] < max(corner_1[0], corner_2[0])) or (abs(normal[1]) < abs(normal[0]) and corner[1] > min(corner_1[1], corner_2[1]) and corner[1] < max(corner_1[1], corner_2[1]))):
66 | self.connections.append((connection[0], cornerIndex))
67 | self.connections.append((connection[1], cornerIndex))
68 | self.connections.remove(connection)
69 | hasChange = True
70 | break
71 | continue
72 | continue
73 | if not hasChange:
74 | break
75 | continue
76 | return
77 |
78 | def findRoomCorners(self):
79 | #for roomName, roomLabel in self.roomLabelDict.iteritems():
80 | self.roomCorners = {}
81 | initialCornerSize = 40
82 | for cornerIndex, corner in enumerate(self.corners):
83 | cornerSize = initialCornerSize
84 | corner = np.round(np.array(corner)).astype(np.int32)
85 | while True:
86 | roomLabels = self.roomSegmentation[max(corner[1] - cornerSize / 2, 0):min(corner[1] + cornerSize / 2, self.roomSegmentation.shape[0] - 1), max(corner[0] - cornerSize / 2, 0):min(corner[0] + cornerSize / 2, self.roomSegmentation.shape[1] - 1)]
87 | roomLabels = roomLabels[roomLabels > 0]
88 | if len(roomLabels) == 0:
89 | cornerSize += 2
90 | continue
91 | roomLabels = np.unique(roomLabels)
92 | for roomLabel in roomLabels:
93 | if roomLabel not in self.roomCorners:
94 | self.roomCorners[roomLabel] = []
95 | pass
96 | self.roomCorners[roomLabel].append(cornerIndex)
97 | continue
98 | break
99 | continue
100 | return
101 |
102 |
103 | def paint(self, painter, patchOffsets, patchSizes, topdownOffset, topdownScale, offsetX, offsetY):
104 | if self.loading:
105 | return
106 | #sizes = np.array([self.topdown.shape[1], self.topdown.shape[0]])
107 | #patchOffsets = np.minimum(np.maximum(patchOffsets, 0), sizes - patchSizes)
108 |
109 | #topdown = self.topdown[patchOffsets[1]:patchOffsets[1] + patchSizes[1]][patchOffsets[0]:patchOffsets[0] + patchSizes[0]]
110 | #topdown = np.minimum((topdown - topdownOffset).astype(np.float32) / topdownScale * 255, 255).astype(np.uint8)
111 | #topdown = np.tile(np.expand_dims(topdown, axis=-1), [1, 1, 3])
112 |
113 | painter.drawPixmap(offsetX, offsetY, self.topdownImage)
114 |
115 | color = COLOR_MAP[0]
116 | pen = QPen(color)
117 | pen.setWidth(3)
118 | painter.setPen(pen)
119 | d = 10
120 |
121 | corner_path = QPainterPath()
122 | points = []
123 | for _, corner in enumerate(self.corners):
124 | point = QPoint(int(round(corner[0] - patchOffsets[0] + offsetX)), int(round(corner[1] - patchOffsets[1] + offsetY)))
125 | points.append(point)
126 | corner_path.addEllipse(point, d / 2.0, d / 2.0)
127 | continue
128 | painter.drawPath(corner_path)
129 |
130 | connection_path = QPainterPath()
131 | for connection in self.connections:
132 | connection_path.moveTo(points[connection[0]])
133 | connection_path.lineTo(points[connection[1]])
134 | continue
135 | painter.drawPath(connection_path)
136 |
137 |
138 | return
139 |
140 |
141 | def addCorner(self, newCorner, axisAligned=True, epsilon=10):
142 | newCornerIndex = -1
143 | for cornerIndex, corner in enumerate(self.corners):
144 | if np.linalg.norm(corner - newCorner) < epsilon:
145 | newCornerIndex = cornerIndex
146 | break
147 | continue
148 | if newCornerIndex == -1:
149 | newCornerIndex = len(self.corners)
150 | if self.prevCornerIndex != -1 and axisAligned:
151 | delta = newCorner - self.corners[self.prevCornerIndex]
152 | if abs(delta[0]) < abs(delta[1]):
153 | delta[0] = 0
154 | else:
155 | delta[1] = 0
156 | pass
157 | newCorner = self.corners[self.prevCornerIndex] + delta
158 | pass
159 |
160 | for connection in self.connections:
161 | corner_1 = np.array(self.corners[connection[0]])
162 | corner_2 = np.array(self.corners[connection[1]])
163 | normal = corner_1 - corner_2
164 | normal /= max(np.linalg.norm(normal), 1e-4)
165 | normal = np.array([normal[1], -normal[0]])
166 | distance = np.dot(corner_1, normal) - np.dot(newCorner, normal)
167 | #print(abs(normal[1]) < abs(normal[0]), corner[1] > min(corner_1[1], corner_2[1]), corner[1] < max(corner_1[1], corner_2[1]))
168 | if abs(distance) < epsilon * 2 and ((abs(normal[1]) > abs(normal[0]) and newCorner[0] > min(corner_1[0], corner_2[0]) and newCorner[0] < max(corner_1[0], corner_2[0])) or (abs(normal[1]) < abs(normal[0]) and newCorner[1] > min(corner_1[1], corner_2[1]) and newCorner[1] < max(corner_1[1], corner_2[1]))):
169 | #print(connection, corner_1, corner_2, newCorner, normal)
170 | newCorner = newCorner + distance * normal
171 | self.connections.append((connection[0], newCornerIndex))
172 | self.connections.append((connection[1], newCornerIndex))
173 | self.connections.remove(connection)
174 | break
175 | continue
176 |
177 | self.corners.append(newCorner)
178 | pass
179 | if self.prevCornerIndex != -1 and self.prevCornerIndex != newCornerIndex:
180 | self.connections.append((self.prevCornerIndex, newCornerIndex))
181 | pass
182 | self.prevCornerIndex = newCornerIndex
183 |
184 | return
185 |
186 | def moveCorner(self, delta):
187 | if self.prevCornerIndex == -1:
188 | return
189 | self.corners[self.prevCornerIndex] += delta
190 | pass
191 |
192 | def finalize(self):
193 | self.prevCornerIndex = -1
194 | return
195 |
196 | def save(self):
197 | #scene_info = {'corners': self.corners, 'cornersOpp': self.cornersOpp, 'faces': self.faces, 'dominantNormals': self.dominantNormals}
198 | np.save(self.scenePath + '_annotation/corners.npy', np.array(self.corners))
199 | np.save(self.scenePath + '_annotation/connections.npy', np.array(self.connections))
200 | return
201 |
202 |
203 | def loadImage(self, imageIndex):
204 | imagePath = self.imagePaths[imageIndex % len(self.imagePaths)]
205 | #print(imagePath)
206 | with open(imagePath.replace('rgb/', 'pose/').replace('rgb.png', 'pose.json')) as f:
207 | pose = json.load(f)
208 | pass
209 | extrinsics = np.array(pose['camera_rt_matrix'])
210 | intrinsics = np.array(pose['camera_k_matrix'])
211 |
212 | image = cv2.imread(imagePath)
213 | imageSizes = np.array([image.shape[1], image.shape[0]])
214 | image = cv2.resize(image, (self.imageWidth, self.imageHeight))
215 | roomName = '_'.join(imagePath.split('/')[-1].split('_')[2:4])
216 | if roomName not in self.roomLabelDict:
217 | return None, []
218 | roomLabel = self.roomLabelDict[roomName]
219 | if roomLabel not in self.roomCorners:
220 | return None, []
221 | cornerIndices = self.roomCorners[roomLabel]
222 | corners2D = np.array([self.corners[cornerIndex] for cornerIndex in cornerIndices]).astype(np.float32)
223 |
224 | #print(corners2D)
225 | X = corners2D[:, 0] / self.box[4] * (self.box[1] - self.box[0]) + self.box[0]
226 | Y = -(corners2D[:, 1] / self.box[5] * (self.box[3] - self.box[2]) + self.box[2])
227 | cornerGT = []
228 | for cornerType, horizontalHeight in enumerate(self.box[6:8]):
229 | wallCorners3D = np.stack([X, Y, np.full(X.shape, horizontalHeight), np.ones(X.shape)], axis=-1)
230 | wallCorners3D = np.matmul(extrinsics, wallCorners3D.transpose()).transpose()
231 | wallCorners = np.matmul(intrinsics, wallCorners3D.transpose()).transpose()
232 |
233 | wallCorners = np.round(wallCorners[:, :2] / wallCorners[:, 2:3] / imageSizes * np.array([self.imageWidth, self.imageHeight])).astype(np.int32)
234 | cornerMask = np.logical_and(np.logical_and(wallCorners3D[:, 2] > 0, np.logical_and(np.logical_and(np.all(wallCorners >= 0, axis=-1), wallCorners[:, 0] < self.imageWidth), wallCorners[:, 1] < self.imageHeight)), wallCorners3D[:, 2] < 10)
235 |
236 | for index, wallCorner in enumerate(wallCorners):
237 | if not cornerMask[index]:
238 | continue
239 | cornerGT.append(wallCorner.tolist() + [cornerType, cornerIndices[index], wallCorners3D[index, 2]])
240 | continue
241 | continue
242 | cornerGT = np.array(cornerGT)
243 | if True:
244 | cornerImage = image.copy()
245 | for index, wallCorner in enumerate(cornerGT):
246 | cv2.circle(cornerImage, (int(round(wallCorner[0])), int(round(wallCorner[1]))), 10, (0, 0, 255), -1)
247 | continue
248 | cv2.imwrite('test/corner.png', cornerImage)
249 | pass
250 | return image, cornerGT
251 |
252 |
253 | def loadImages(self):
254 | self.findRoomCorners()
255 |
256 | imagePaths = glob.glob(self.scenePath + '/data/rgb/*.png')
257 | self.imagePaths = sorted(imagePaths)
258 | self.trajectory = []
259 | return
260 | #centers = []
261 | for imageIndex, imagePath in enumerate(imagePaths):
262 | if imageIndex != 80:
263 | continue
264 | self.loadImage(imagePath)
265 | continue
266 | #centers = np.array(centers)
267 | return
268 |
269 | def load(self):
270 | self.loading = False
271 | return
272 |
273 | def removeLast(self):
274 | if self.prevCornerIndex == len(self.corners) - 1:
275 | self.corners = self.corners[:-1]
276 | self.connections = [connection for connection in self.connections if len(self.corners) not in connection]
277 | self.prevCornerIndex -= 1
278 | else:
279 | self.connections = self.connections[:-1]
280 | pass
281 | return
282 |
--------------------------------------------------------------------------------
/libs/canvas.py:
--------------------------------------------------------------------------------
1 | try:
2 | from PyQt5.QtGui import *
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtWidgets import *
5 | except ImportError:
6 | from PyQt4.QtGui import *
7 | from PyQt4.QtCore import *
8 |
9 | #from PyQt4.QtOpenGL import *
10 |
11 | #from libs.corner import Corner
12 | from libs.lib import distance
13 | import numpy as np
14 | import cv2
15 | from PIL import Image
16 | import requests
17 | #import StringIO
18 | import urllib
19 | import sys
20 | import glob
21 | from scene import Scene
22 | import os
23 |
24 | sys.path.append('../code/')
25 | Image.MAX_IMAGE_PIXELS = 1000000000
26 |
27 | CURSOR_DEFAULT = Qt.ArrowCursor
28 | CURSOR_POINT = Qt.PointingHandCursor
29 | CURSOR_DRAW = Qt.CrossCursor
30 | CURSOR_MOVE = Qt.ClosedHandCursor
31 | CURSOR_GRAB = Qt.OpenHandCursor
32 |
33 | # class Canvas(QGLWidget):
34 |
35 |
36 | class Canvas(QWidget):
37 | newCorner = pyqtSignal()
38 | cornerMoved = pyqtSignal()
39 | drawing = pyqtSignal(bool)
40 |
41 | CREATE, EDIT = list(range(2))
42 | image = None
43 |
44 | def __init__(self, *args, **kwargs):
45 | super(Canvas, self).__init__(*args, **kwargs)
46 | # Initialise local state.
47 |
48 | self.prevPoint = QPointF()
49 | self._painter = QPainter()
50 | # Set widget options.
51 | self.setMouseTracking(True)
52 | self.setFocusPolicy(Qt.WheelFocus)
53 |
54 | self.width = 1280
55 | self.height = 960
56 |
57 | self.layout_width = 1000
58 | self.layout_height = 1000
59 |
60 | self.offsetX = 10
61 | self.offsetY = 10
62 |
63 | self.currentLabel = 0
64 | self.hiding = False
65 | self.resize(self.width, self.height)
66 | self.imageIndex = -1
67 | self.mode = 'layout'
68 | self.ctrlPressed = False
69 | self.shiftPressed = False
70 |
71 | self.patchOffsets = np.zeros(2, dtype=np.int32)
72 | self.patchSizes = np.full((2, ), 1000, dtype=np.int32)
73 | self.topdownOffset = 0
74 | self.topdownScale = 1
75 | self.topdownImage = None
76 | return
77 |
78 | # def creating(self):
79 | # return self.mode == self.CREATE
80 |
81 | # def editing(self):
82 | # return self.mode == self.EDIT
83 |
84 | # def setMode(self, value=True):
85 | # self.mode = self.CREATE if value else self.EDIT
86 | # if value: # Create
87 | # self.unHighlight()
88 | # self.deSelectCorner()
89 | # self.prevPoint = QPointF()
90 | # self.repaint()
91 |
92 |
93 | def readDepth(self, point):
94 | u = point[0] / self.color_width * self.depth_width
95 | v = point[1] / self.color_height * self.depth_height
96 | return self.depth[int(round(v)), int(round(u))]
97 |
98 | def mousePressEvent(self, ev):
99 | #print(self.drawing(), pos)
100 | point = self.transformPos(ev.pos())
101 | if ev.button() == Qt.LeftButton:
102 | self.scene.addCorner(point + self.patchOffsets, axisAligned=not self.shiftPressed)
103 | pass
104 | self.prevPoint = point
105 |
106 | #if ev.button() == Qt.RightButton:
107 | #pos = self.transformPos(ev.pos(), moving=True)
108 | #pass
109 | self.repaint()
110 | return
111 |
112 | def mouseMoveEvent(self, ev):
113 | """Update line with last point and current coordinates."""
114 |
115 | if (Qt.RightButton & ev.buttons()):
116 | point = self.transformPos(ev.pos())
117 | self.patchOffsets -= (point - self.prevPoint).astype(np.int32)
118 | sizes = np.array([self.scene.topdown.shape[1], self.scene.topdown.shape[0]])
119 | self.patchOffsets = np.minimum(np.maximum(self.patchOffsets, 0), sizes - self.patchSizes)
120 | #self.scene.reloadTopdown()
121 | self.repaint()
122 | #self.patchOffsets = np.minimum(np.maximum(self.patchOffsets, 0), self.patchSizes)
123 | self.prevPoint = point
124 | #self.loadTopdownImage()
125 | return
126 |
127 | if (Qt.LeftButton & ev.buttons()):
128 | point = self.transformPos(ev.pos())
129 | self.scene.moveCorner(point - self.prevPoint)
130 | self.repaint()
131 | #self.patchOffsets = np.minimum(np.maximum(self.patchOffsets, 0), self.patchSizes)
132 | self.prevPoint = point
133 | #self.loadTopdownImage()
134 | return
135 |
136 | return
137 |
138 | def mouseReleaseEvent(self, ev):
139 | if self.ctrlPressed and self.shiftPressed and self.scene.selectedCornerIndex != -1:
140 | point = self.transformPos(ev.pos())
141 | self.scene.moveCorner(point, self.extrinsics_inv, self.intrinsics, self.imageIndex, recording=True)
142 | pass
143 | elif self.shiftPressed and self.scene.selectedCornerIndex != -1:
144 | point = self.transformPos(ev.pos())
145 | self.scene.moveCorner(point, self.extrinsics_inv, self.intrinsics, self.imageIndex, concave=True)
146 | self.repaint()
147 | pass
148 | self.scene.selectedLayoutCorner = [-1, -1]
149 | self.scene.selectedCornerIndex = -1
150 | self.scene.selectedEdgeIndex = -1
151 |
152 | return
153 |
154 | def wheelEvent(self, ev):
155 | if ev.delta() < 0:
156 | self.topdownScale = max(self.topdownScale - 1, 1)
157 | else:
158 | self.topdownScale = self.topdownScale + 1
159 | pass
160 | #self.scene.reloadTopdown()
161 | self.repaint()
162 | return
163 |
164 | def handleDrawing(self, pos):
165 | self.update()
166 |
167 | def selectCornerPoint(self, point):
168 | """Select the first corner created which contains this point."""
169 | self.deSelectCorner()
170 | for corner in reversed(self.corners):
171 | if corner.selectCorner(point, self.epsilon):
172 | self.selectCorner(corner)
173 | #self.calculateOffsets(corner, point)
174 | break
175 | continue
176 | return
177 |
178 | def paintEvent(self, event):
179 | if (self.imageIndex == -1 or not self.image) and self.mode != 'layout':
180 | return super(Canvas, self).paintEvent(event)
181 |
182 | p = self._painter
183 | p.begin(self)
184 | p.setRenderHint(QPainter.Antialiasing)
185 | p.setRenderHint(QPainter.HighQualityAntialiasing)
186 | p.setRenderHint(QPainter.SmoothPixmapTransform)
187 |
188 | self.scene.paint(p, self.patchOffsets, self.patchSizes, self.topdownOffset, self.topdownScale, self.offsetX, self.offsetY)
189 |
190 | p.end()
191 | return
192 |
193 | def transformPos(self, point, moving=False):
194 | """Convert from widget-logical coordinates to painter-logical coordinates."""
195 |
196 | return np.array([float(point.x() - self.offsetX), float(point.y() - self.offsetY)])
197 |
198 |
199 | def closeEnough(self, p1, p2):
200 | return distance(p1 - p2) < self.epsilon
201 |
202 |
203 | def keyPressEvent(self, ev):
204 | key = ev.key()
205 | if (ev.modifiers() & Qt.ControlModifier):
206 | self.ctrlPressed = True
207 | if self.hiding:
208 | self.repaint()
209 | pass
210 | else:
211 | self.ctrlPressed = False
212 | pass
213 | if (ev.modifiers() & Qt.ShiftModifier):
214 | self.shiftPressed = True
215 | if self.hiding:
216 | self.repaint()
217 | pass
218 | else:
219 | self.shiftPressed = False
220 | pass
221 |
222 | if key == Qt.Key_Escape:
223 | #self.mode = 'moving'
224 | self.scene.deleteSelected()
225 | self.repaint()
226 | if key == Qt.Key_Z:
227 | self.scene.removeLast()
228 | self.repaint()
229 | elif key == Qt.Key_R:
230 | if self.ctrlPressed:
231 | self.scene.reset('init')
232 | self.repaint()
233 | pass
234 | elif key == Qt.Key_H:
235 | #and Qt.ControlModifier == int(ev.modifiers()):
236 | self.hiding = not self.hiding
237 | self.repaint()
238 | elif key == Qt.Key_A:
239 | self.scene.finalize()
240 | elif key == Qt.Key_Q:
241 | if self.mode != 'layout':
242 | self.mode = 'point'
243 | pass
244 | elif key == Qt.Key_S:
245 | if self.ctrlPressed:
246 | print('save')
247 | self.scene.save()
248 | pass
249 | elif key == Qt.Key_D:
250 | self.setCurrentLabel(2)
251 | self.setMode(False)
252 | elif key == Qt.Key_F:
253 | self.setCurrentLabel(3)
254 | self.setMode(False)
255 | elif key == Qt.Key_M:
256 | self.writePLYFile()
257 | elif key == Qt.Key_Right:
258 | self.moveToNextImage()
259 | elif key == Qt.Key_Left:
260 | self.moveToPreviousImage()
261 | elif key == Qt.Key_Down:
262 | self.moveToNextImage(5)
263 | elif key == Qt.Key_Up:
264 | self.moveToPreviousImage(5)
265 | elif key == Qt.Key_1:
266 | self.moveToNextImage()
267 | self.mode = 'move'
268 | self.repaint()
269 | elif key == Qt.Key_2:
270 | self.showDensityImage()
271 | self.mode = 'layout'
272 | self.repaint()
273 | elif key == Qt.Key_E:
274 | if self.ctrlPressed:
275 | self.scene.exportPly()
276 | pass
277 | elif key == Qt.Key_Space:
278 | self.scene.finalize()
279 | self.repaint()
280 | pass
281 | return
282 |
283 | def keyReleaseEvent(self, ev):
284 | if self.hiding and self.ctrlPressed:
285 | self.repaint()
286 | pass
287 | self.ctrlPressed = False
288 | self.shiftPressed = False
289 | return
290 |
291 | def setCurrentLabel(self, label):
292 | self.currentLabel = label
293 | return
294 |
295 |
296 |
297 | def loadCorners(self, corners):
298 | self.corners = list(corners)
299 | self.current = None
300 | self.currentGroup = currentGroup
301 | self.repaint()
302 |
303 | def onPoint(self, pos):
304 | for corner in self.corners:
305 | if corner.nearestVertex(pos, self.epsilon) is not None:
306 | return True
307 | continue
308 | return False
309 |
310 |
311 | def loadScene(self, scenePath):
312 | self.scene = Scene(scenePath)
313 |
314 | #self.scene.reloadTopdown()
315 | self.repaint()
316 | return
317 |
318 | def showDensityImage(self):
319 | image = self.scene.getDensityImage(self.layout_width, self.layout_height)
320 | self.image = QPixmap(QImage(image[:, :, ::-1].reshape(-1), self.layout_width, self.layout_height, self.layout_width * 3, QImage.Format_RGB888))
321 | return
322 |
323 | def moveToNextImage(self, delta=1):
324 | self.imageIndex = min(self.imageIndex + delta, len(self.imagePaths) - 1)
325 | self.loadImage()
326 | return
327 |
328 | def moveToPreviousImage(self, delta=1):
329 | self.imageIndex = max(self.imageIndex - delta, 0)
330 | self.loadImage()
331 | return
332 |
333 | def loadImage(self):
334 | image = cv2.imread(self.imagePaths[self.imageIndex])
335 | self.image = QPixmap(QImage(image[:, :, ::-1].reshape(-1), self.color_width, self.color_height, self.color_width * 3, QImage.Format_RGB888))
336 |
337 | self.depth = cv2.imread(self.imagePaths[self.imageIndex].replace('color.jpg', 'depth.pgm'), -1).astype(np.float32) / 1000
338 |
339 |
340 | self.extrinsics_inv = []
341 | with open(self.imagePaths[self.imageIndex].replace('color.jpg', 'pose.txt'), 'r') as f:
342 | for line in f:
343 | self.extrinsics_inv += [float(value) for value in line.strip().split(' ') if value.strip() != '']
344 | continue
345 | pass
346 | self.extrinsics_inv = np.array(self.extrinsics_inv).reshape((4, 4))
347 | self.extrinsics = np.linalg.inv(self.extrinsics_inv)
348 | self.repaint()
349 | return
350 |
351 | # def loadTopdownImage(self):
352 | # topdown = self.scene.grabTopdown(self.patchOffsets, self.patchSizes)
353 | # #topdown = topdown[:, :, ::-1]
354 | # topdown = np.minimum((topdown - self.topdownOffset).astype(np.float32) / self.topdownScale * 255, 255).astype(np.uint8)
355 | # topdown = np.tile(np.expand_dims(topdown, axis=-1), [1, 1, 3])
356 | # self.topdownImage = QPixmap(QImage(topdown.reshape(-1), self.patchSizes[0], self.patchSizes[1], self.patchSizes[0] * 3, QImage.Format_RGB888))
357 | # self.repaint()
358 | # return
359 |
360 | def removeLastPoint(self):
361 | self.corners = self.corners[:-1]
362 | self.repaint()
363 | return
364 |
365 | def sizeHint(self):
366 | return self.minimumSizeHint()
367 |
368 | def minimumSizeHint(self):
369 | if self.image:
370 | return self.image.size()
371 | return super(Canvas, self).minimumSizeHint()
372 |
--------------------------------------------------------------------------------