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.
Files changed (43) hide show
  1. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.bumpver.toml +1 -1
  2. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/PKG-INFO +1 -1
  3. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/__version__.py +1 -1
  4. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/click_image_app.py +69 -6
  5. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/click_manager.py +38 -6
  6. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/image_viewer.py +26 -7
  7. pyclickimage-2.2.0/pyclickimage/resources/app.png +0 -0
  8. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/PKG-INFO +1 -1
  9. pyclickimage-2.0.4/pyclickimage/resources/app.png +0 -0
  10. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.github/workflows/publish.yml +0 -0
  11. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.github/workflows/sphinx.yml +0 -0
  12. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.gitignore +0 -0
  13. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/.gitlab-ci.yml +0 -0
  14. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/COPYING +0 -0
  15. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/LICENSE +0 -0
  16. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/Makefile +0 -0
  17. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/README.md +0 -0
  18. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api.rst +0 -0
  19. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api_doc/click_image_app.rst +0 -0
  20. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api_doc/click_manager.rst +0 -0
  21. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api_doc/image_viewer.rst +0 -0
  22. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/api_doc/run.rst +0 -0
  23. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/conf.py +0 -0
  24. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/index.rst +0 -0
  25. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/installation.rst +0 -0
  26. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/usage.rst +0 -0
  27. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/usage_doc/extracting_the_clicks.rst +0 -0
  28. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/usage_doc/running_the_GUI.rst +0 -0
  29. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/docs/source/usage_doc/using_the_GUI.rst +0 -0
  30. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/examples/clicks.csv +0 -0
  31. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/examples/example.png +0 -0
  32. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/examples/example.py +0 -0
  33. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/__init__.py +0 -0
  34. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/__main__.py +0 -0
  35. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/resources/__init__.py +0 -0
  36. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage/run.py +0 -0
  37. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/SOURCES.txt +0 -0
  38. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/dependency_links.txt +0 -0
  39. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/entry_points.txt +0 -0
  40. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/requires.txt +0 -0
  41. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyclickimage.egg-info/top_level.txt +0 -0
  42. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/pyproject.toml +0 -0
  43. {pyclickimage-2.0.4 → pyclickimage-2.2.0}/setup.cfg +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpver]
2
- current_version = "2.0.4"
2
+ current_version = "2.2.0"
3
3
  version_pattern = "MAJOR.MINOR.PATCH"
4
4
  default_level = "patch"
5
5
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyclickimage
3
- Version: 2.0.4
3
+ Version: 2.2.0
4
4
  Summary: Python library to select points on a image [pyqt5 GUI]
5
5
  Author-email: Artezaru <artezaru.github@proton.me>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -16,4 +16,4 @@ You should have received a copy of the GNU General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  """
18
18
 
19
- __version__ = "2.0.4"
19
+ __version__ = "2.2.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
- for x, y in pts:
534
- if x is None or y is None:
535
- continue
536
- self.viewer.add_marker((x, y), self.marker_color, self.marker_size)
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={scene_pos.x():.3f} Y={scene_pos.y():.3f}")
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyclickimage
3
- Version: 2.0.4
3
+ Version: 2.2.0
4
4
  Summary: Python library to select points on a image [pyqt5 GUI]
5
5
  Author-email: Artezaru <artezaru.github@proton.me>
6
6
  License: GNU GENERAL PUBLIC LICENSE
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes