pyclickimage 2.0.4__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.bumpver.toml +1 -1
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/PKG-INFO +1 -1
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/__version__.py +1 -1
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/click_image_app.py +69 -6
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/click_manager.py +38 -6
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/image_viewer.py +26 -7
- pyclickimage-2.2.0/pyclickimage/resources/app.png +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/PKG-INFO +1 -1
- pyclickimage-2.0.4/pyclickimage/resources/app.png +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.github/workflows/publish.yml +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.github/workflows/sphinx.yml +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.gitignore +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.gitlab-ci.yml +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/COPYING +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/LICENSE +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/Makefile +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/README.md +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api_doc/click_image_app.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api_doc/click_manager.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api_doc/image_viewer.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api_doc/run.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/conf.py +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/index.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/installation.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/usage.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/usage_doc/extracting_the_clicks.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/usage_doc/running_the_GUI.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/usage_doc/using_the_GUI.rst +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/examples/clicks.csv +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/examples/example.png +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/examples/example.py +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/__init__.py +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/__main__.py +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/resources/__init__.py +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/run.py +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/SOURCES.txt +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/dependency_links.txt +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/entry_points.txt +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/requires.txt +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/top_level.txt +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyproject.toml +0 -0
- {pyclickimage-2.0.4 → pyclickimage-2.2.0}/setup.cfg +0 -0
|
@@ -62,7 +62,7 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
62
62
|
# Core components
|
|
63
63
|
# -------------------------
|
|
64
64
|
self.click_manager = ClickManager(precision_mode="float")
|
|
65
|
-
self.viewer = ImageViewer()
|
|
65
|
+
self.viewer = ImageViewer(half_shift=True)
|
|
66
66
|
|
|
67
67
|
# -------------------------
|
|
68
68
|
# Logging
|
|
@@ -184,6 +184,20 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
184
184
|
self.precision_checkbox.setShortcut("Ctrl+I")
|
|
185
185
|
side.addWidget(self.precision_checkbox)
|
|
186
186
|
|
|
187
|
+
self.half_shift_checkbox = QtWidgets.QCheckBox(
|
|
188
|
+
"Half-Shift: (0,0) on the pixel center."
|
|
189
|
+
)
|
|
190
|
+
self.half_shift_checkbox.setChecked(True)
|
|
191
|
+
self.half_shift_checkbox.stateChanged.connect(self.on_half_shift_changed)
|
|
192
|
+
side.addWidget(self.half_shift_checkbox)
|
|
193
|
+
|
|
194
|
+
self.display_clicks_checkbox = QtWidgets.QCheckBox("Display clicks")
|
|
195
|
+
self.display_clicks_checkbox.setChecked(True)
|
|
196
|
+
self.display_clicks_checkbox.stateChanged.connect(
|
|
197
|
+
self.on_display_clicks_changed
|
|
198
|
+
)
|
|
199
|
+
side.addWidget(self.display_clicks_checkbox)
|
|
200
|
+
|
|
187
201
|
# ============================================================
|
|
188
202
|
# Group selector
|
|
189
203
|
# ============================================================
|
|
@@ -372,6 +386,10 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
372
386
|
|
|
373
387
|
toolbar.addSeparator()
|
|
374
388
|
|
|
389
|
+
toolbar.addAction("Clear Logs", self.clear_logs)
|
|
390
|
+
|
|
391
|
+
toolbar.addSeparator()
|
|
392
|
+
|
|
375
393
|
# ============================================================
|
|
376
394
|
# Precision mode
|
|
377
395
|
# ============================================================
|
|
@@ -385,6 +403,44 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
385
403
|
self._append_log(f"Precision mode: {'INT' if INT else 'FLOAT'}")
|
|
386
404
|
self.update()
|
|
387
405
|
|
|
406
|
+
def on_half_shift_changed(self, state):
|
|
407
|
+
r"""
|
|
408
|
+
Toggle half-shift mode.
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
half_shift = state == QtCore.Qt.Checked
|
|
412
|
+
|
|
413
|
+
if self.click_manager.n_clicks > 0:
|
|
414
|
+
|
|
415
|
+
msg = QtWidgets.QMessageBox(self)
|
|
416
|
+
msg.setWindowTitle("Existing clicks detected")
|
|
417
|
+
msg.setText(
|
|
418
|
+
f"Do you want to shift all previous clicked points by 0.5 to match the new coordinates system ?"
|
|
419
|
+
)
|
|
420
|
+
msg.setInformativeText("No = keep clicks\nYes = shift clicks")
|
|
421
|
+
msg.setStandardButtons(QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)
|
|
422
|
+
msg.setDefaultButton(QtWidgets.QMessageBox.Yes)
|
|
423
|
+
|
|
424
|
+
choice = msg.exec_()
|
|
425
|
+
|
|
426
|
+
if choice == QtWidgets.QMessageBox.Yes:
|
|
427
|
+
if half_shift:
|
|
428
|
+
self.click_manager.to_half_shift_on()
|
|
429
|
+
else:
|
|
430
|
+
self.click_manager.to_half_shift_off()
|
|
431
|
+
|
|
432
|
+
self.viewer.half_shift = half_shift
|
|
433
|
+
self._append_log(f"Half-Shift mode: {half_shift}")
|
|
434
|
+
self.update()
|
|
435
|
+
|
|
436
|
+
def on_display_clicks_changed(self, state):
|
|
437
|
+
r"""
|
|
438
|
+
Toggle dispaly clicks.
|
|
439
|
+
"""
|
|
440
|
+
DISPLAY = state == QtCore.Qt.Checked
|
|
441
|
+
self._append_log(f"Display clicks: {DISPLAY}")
|
|
442
|
+
self.update()
|
|
443
|
+
|
|
388
444
|
# ============================================================
|
|
389
445
|
# Image
|
|
390
446
|
# ============================================================
|
|
@@ -393,7 +449,6 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
393
449
|
r"""
|
|
394
450
|
Set image to display.
|
|
395
451
|
"""
|
|
396
|
-
|
|
397
452
|
if image is None:
|
|
398
453
|
image = np.zeros((512, 512, 3), dtype=np.uint8)
|
|
399
454
|
self._is_empty_image = True
|
|
@@ -530,10 +585,12 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
530
585
|
|
|
531
586
|
pts = self.click_manager.extract_group()
|
|
532
587
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
588
|
+
DISPLAY = self.display_clicks_checkbox.isChecked()
|
|
589
|
+
if DISPLAY:
|
|
590
|
+
for x, y in pts:
|
|
591
|
+
if x is None or y is None:
|
|
592
|
+
continue
|
|
593
|
+
self.viewer.add_marker((x, y), self.marker_color, self.marker_size)
|
|
537
594
|
|
|
538
595
|
def update_table(self):
|
|
539
596
|
r"""
|
|
@@ -878,6 +935,12 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
878
935
|
"""
|
|
879
936
|
self.log_text_edit.append(msg)
|
|
880
937
|
|
|
938
|
+
def clear_logs(self):
|
|
939
|
+
r"""
|
|
940
|
+
Clear log message.
|
|
941
|
+
"""
|
|
942
|
+
self.log_text_edit.clear()
|
|
943
|
+
|
|
881
944
|
# ============================================================
|
|
882
945
|
# Close event
|
|
883
946
|
# ============================================================
|
|
@@ -56,7 +56,6 @@ class ClickManager:
|
|
|
56
56
|
# =========================================================
|
|
57
57
|
# PRECISION MODE
|
|
58
58
|
# =========================================================
|
|
59
|
-
|
|
60
59
|
@property
|
|
61
60
|
def precision_mode(self) -> str:
|
|
62
61
|
r"""
|
|
@@ -87,7 +86,7 @@ class ClickManager:
|
|
|
87
86
|
if mode not in ("float", "int"):
|
|
88
87
|
raise ValueError("precision_mode must be 'float' or 'int'")
|
|
89
88
|
self._precision_mode = mode
|
|
90
|
-
|
|
89
|
+
|
|
91
90
|
@property
|
|
92
91
|
def use_int_precision(self) -> bool:
|
|
93
92
|
r"""
|
|
@@ -95,7 +94,6 @@ class ClickManager:
|
|
|
95
94
|
"""
|
|
96
95
|
return self._precision_mode == "int"
|
|
97
96
|
|
|
98
|
-
|
|
99
97
|
@use_int_precision.setter
|
|
100
98
|
def use_int_precision(self, value: bool) -> None:
|
|
101
99
|
r"""
|
|
@@ -103,7 +101,6 @@ class ClickManager:
|
|
|
103
101
|
"""
|
|
104
102
|
self._precision_mode = "int" if value else "float"
|
|
105
103
|
|
|
106
|
-
|
|
107
104
|
@property
|
|
108
105
|
def use_float_precision(self) -> bool:
|
|
109
106
|
r"""
|
|
@@ -111,7 +108,6 @@ class ClickManager:
|
|
|
111
108
|
"""
|
|
112
109
|
return self._precision_mode == "float"
|
|
113
110
|
|
|
114
|
-
|
|
115
111
|
@use_float_precision.setter
|
|
116
112
|
def use_float_precision(self, value: bool) -> None:
|
|
117
113
|
r"""
|
|
@@ -217,6 +213,12 @@ class ClickManager:
|
|
|
217
213
|
# =========================================================
|
|
218
214
|
# CLICKS
|
|
219
215
|
# =========================================================
|
|
216
|
+
@property
|
|
217
|
+
def n_clicks(self) -> int:
|
|
218
|
+
r"""
|
|
219
|
+
Return the number of clicks
|
|
220
|
+
"""
|
|
221
|
+
return sum(len(g) for _, g in self.groups.items())
|
|
220
222
|
|
|
221
223
|
def add_click(
|
|
222
224
|
self, x: Optional[Number], y: Optional[Number], group_name: Optional[str] = None
|
|
@@ -243,6 +245,36 @@ class ClickManager:
|
|
|
243
245
|
|
|
244
246
|
self.groups[group_name].append((x, y))
|
|
245
247
|
|
|
248
|
+
def to_half_shift_on(self):
|
|
249
|
+
r"""
|
|
250
|
+
Shift all points by -0.5 to use pixel-centered coordinates.
|
|
251
|
+
|
|
252
|
+
Point (a, b) -> (a - 0.5, b - 0.5)
|
|
253
|
+
"""
|
|
254
|
+
for group_name, points in self.groups.items():
|
|
255
|
+
self.groups[group_name] = [
|
|
256
|
+
(
|
|
257
|
+
(x - 0.5) if x is not None else None,
|
|
258
|
+
(y - 0.5) if y is not None else None,
|
|
259
|
+
)
|
|
260
|
+
for (x, y) in points
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
def to_half_shift_off(self):
|
|
264
|
+
r"""
|
|
265
|
+
Reverse the half-pixel shift.
|
|
266
|
+
|
|
267
|
+
Point (a, b) -> (a + 0.5, b + 0.5)
|
|
268
|
+
"""
|
|
269
|
+
for group_name, points in self.groups.items():
|
|
270
|
+
self.groups[group_name] = [
|
|
271
|
+
(
|
|
272
|
+
(x + 0.5) if x is not None else None,
|
|
273
|
+
(y + 0.5) if y is not None else None,
|
|
274
|
+
)
|
|
275
|
+
for (x, y) in points
|
|
276
|
+
]
|
|
277
|
+
|
|
246
278
|
def extract_group(self, group_name: Optional[str] = None) -> List[Point]:
|
|
247
279
|
r"""
|
|
248
280
|
Extracts the clicks for the specified group.
|
|
@@ -380,7 +412,7 @@ class ClickManager:
|
|
|
380
412
|
next(reader)
|
|
381
413
|
|
|
382
414
|
for group, _, x, y in reader:
|
|
383
|
-
|
|
415
|
+
|
|
384
416
|
instance.add_group(group)
|
|
385
417
|
|
|
386
418
|
x_val = float(x) if x != "" else None
|
|
@@ -29,7 +29,14 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
29
29
|
- zoom with mouse wheel
|
|
30
30
|
- precise click detection (drag-safe)
|
|
31
31
|
- marker system for annotation
|
|
32
|
+
|
|
33
|
+
half-shift :
|
|
34
|
+
|
|
35
|
+
- True = (0,0) on the center of the first pixel.
|
|
36
|
+
- False = (0,0) on the corner of the first pixel.
|
|
37
|
+
|
|
32
38
|
"""
|
|
39
|
+
|
|
33
40
|
left_click_signal = QtCore.pyqtSignal(float, float)
|
|
34
41
|
right_click_signal = QtCore.pyqtSignal(float, float)
|
|
35
42
|
|
|
@@ -37,7 +44,7 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
37
44
|
# INIT
|
|
38
45
|
# ======================================================================
|
|
39
46
|
|
|
40
|
-
def __init__(self, parent=None):
|
|
47
|
+
def __init__(self, parent=None, half_shift=True):
|
|
41
48
|
r"""
|
|
42
49
|
Initialize the ImageViewer.
|
|
43
50
|
|
|
@@ -92,6 +99,7 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
92
99
|
Tuple[QtWidgets.QGraphicsLineItem, QtWidgets.QGraphicsLineItem]
|
|
93
100
|
] = []
|
|
94
101
|
self.auto_marker = True
|
|
102
|
+
self.half_shift = bool(half_shift)
|
|
95
103
|
|
|
96
104
|
# ======================================================================
|
|
97
105
|
# IMAGE LOADING
|
|
@@ -141,7 +149,7 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
141
149
|
# IMPORTANT: reset markers if needed
|
|
142
150
|
self._zoom = 0
|
|
143
151
|
self._markers = []
|
|
144
|
-
|
|
152
|
+
|
|
145
153
|
# ======================================================================
|
|
146
154
|
# CROSSHAIR MANAGEMENT
|
|
147
155
|
# ======================================================================
|
|
@@ -150,8 +158,7 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
150
158
|
Return current crosshair color.
|
|
151
159
|
"""
|
|
152
160
|
return self._crosshair_color
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
|
|
155
162
|
def set_crosshair_color(self, color):
|
|
156
163
|
r"""
|
|
157
164
|
Set the crosshair color
|
|
@@ -163,7 +170,7 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
163
170
|
|
|
164
171
|
self._cross_h.setPen(pen)
|
|
165
172
|
self._cross_v.setPen(pen)
|
|
166
|
-
|
|
173
|
+
|
|
167
174
|
def _create_crosshair(self):
|
|
168
175
|
r"""
|
|
169
176
|
Create the crosshair
|
|
@@ -212,11 +219,15 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
212
219
|
|
|
213
220
|
self._cross_v.setLine(scene_pos.x(), rect.top(), scene_pos.x(), rect.bottom())
|
|
214
221
|
|
|
222
|
+
x, y = scene_pos.x(), scene_pos.y()
|
|
223
|
+
if self.half_shift:
|
|
224
|
+
x = x - 0.5
|
|
225
|
+
y = y - 0.5
|
|
226
|
+
|
|
215
227
|
self._coord_label.show()
|
|
216
|
-
self._coord_label.setText(f"X={
|
|
228
|
+
self._coord_label.setText(f"X={x:.3f} Y={y:.3f}")
|
|
217
229
|
self._coord_label.adjustSize()
|
|
218
230
|
|
|
219
|
-
|
|
220
231
|
# ======================================================================
|
|
221
232
|
# ZOOM
|
|
222
233
|
# ======================================================================
|
|
@@ -317,6 +328,10 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
317
328
|
|
|
318
329
|
x, y = scene_pos.x(), scene_pos.y()
|
|
319
330
|
|
|
331
|
+
if self.half_shift:
|
|
332
|
+
x = x - 0.5
|
|
333
|
+
y = y - 0.5
|
|
334
|
+
|
|
320
335
|
if event.button() == QtCore.Qt.LeftButton:
|
|
321
336
|
self.left_click_signal.emit(x, y)
|
|
322
337
|
|
|
@@ -350,6 +365,10 @@ class ImageViewer(QtWidgets.QGraphicsView):
|
|
|
350
365
|
"""
|
|
351
366
|
x, y = center
|
|
352
367
|
|
|
368
|
+
if self.half_shift:
|
|
369
|
+
x = x + 0.5
|
|
370
|
+
y = y + 0.5
|
|
371
|
+
|
|
353
372
|
pen = QtGui.QPen(color)
|
|
354
373
|
pen.setWidthF(0)
|
|
355
374
|
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|