pyclickimage 2.2.0__tar.gz → 2.4.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.2.0 → pyclickimage-2.4.0}/.bumpver.toml +1 -1
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/PKG-INFO +5 -5
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage/__main__.py +1 -1
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage/__version__.py +1 -1
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage/click_image_app.py +481 -261
- pyclickimage-2.4.0/pyclickimage/resources/app.png +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage.egg-info/PKG-INFO +5 -5
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyproject.toml +4 -4
- pyclickimage-2.2.0/pyclickimage/resources/app.png +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/.github/workflows/publish.yml +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/.github/workflows/sphinx.yml +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/.gitignore +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/.gitlab-ci.yml +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/COPYING +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/LICENSE +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/Makefile +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/README.md +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/api.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/api_doc/click_image_app.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/api_doc/click_manager.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/api_doc/image_viewer.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/api_doc/run.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/conf.py +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/index.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/installation.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/usage.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/usage_doc/extracting_the_clicks.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/usage_doc/running_the_GUI.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/docs/source/usage_doc/using_the_GUI.rst +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/examples/clicks.csv +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/examples/example.png +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/examples/example.py +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage/__init__.py +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage/click_manager.py +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage/image_viewer.py +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage/resources/__init__.py +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage/run.py +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage.egg-info/SOURCES.txt +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage.egg-info/dependency_links.txt +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage.egg-info/entry_points.txt +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage.egg-info/requires.txt +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/pyclickimage.egg-info/top_level.txt +0 -0
- {pyclickimage-2.2.0 → pyclickimage-2.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyclickimage
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -677,10 +677,10 @@ License: GNU GENERAL PUBLIC LICENSE
|
|
|
677
677
|
the library. If this is what you want to do, use the GNU Lesser General
|
|
678
678
|
Public License instead of this License. But first, please read
|
|
679
679
|
<https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
680
|
-
Project-URL: Homepage, https://github.com/Artezaru/
|
|
681
|
-
Project-URL: Documentation, https://Artezaru.github.io/
|
|
682
|
-
Project-URL: Source, https://github.com/Artezaru/
|
|
683
|
-
Project-URL: Tracker, https://github.com/Artezaru/
|
|
680
|
+
Project-URL: Homepage, https://github.com/Artezaru/pyclickimage
|
|
681
|
+
Project-URL: Documentation, https://Artezaru.github.io/pyclickimage
|
|
682
|
+
Project-URL: Source, https://github.com/Artezaru/pyclickimage
|
|
683
|
+
Project-URL: Tracker, https://github.com/Artezaru/pyclickimage/issues
|
|
684
684
|
Classifier: Programming Language :: Python :: 3
|
|
685
685
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
686
686
|
Classifier: Operating System :: OS Independent
|
|
@@ -58,12 +58,27 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
58
58
|
self._colormap_has_changed = True
|
|
59
59
|
self._is_empty_image = True
|
|
60
60
|
|
|
61
|
+
# --------------------------
|
|
62
|
+
# States
|
|
63
|
+
# --------------------------
|
|
64
|
+
self.alpha = 1.0
|
|
65
|
+
self.beta_pc = 0
|
|
66
|
+
self.display_min_pc = 0
|
|
67
|
+
self.display_max_pc = 100
|
|
68
|
+
self.show_clicks = True
|
|
69
|
+
self.marker_color = QtGui.QColor(255, 0, 0)
|
|
70
|
+
self.marker_size = 8
|
|
71
|
+
|
|
61
72
|
# -------------------------
|
|
62
73
|
# Core components
|
|
63
74
|
# -------------------------
|
|
64
75
|
self.click_manager = ClickManager(precision_mode="float")
|
|
65
76
|
self.viewer = ImageViewer(half_shift=True)
|
|
66
77
|
|
|
78
|
+
self.viewer.auto_marker = False # Don't draw directly
|
|
79
|
+
self.viewer.left_click_signal.connect(self._process_left_click)
|
|
80
|
+
self.viewer.right_click_signal.connect(self._process_right_click)
|
|
81
|
+
|
|
67
82
|
# -------------------------
|
|
68
83
|
# Logging
|
|
69
84
|
# -------------------------
|
|
@@ -80,24 +95,32 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
80
95
|
# -------------------------
|
|
81
96
|
self.set_image(image)
|
|
82
97
|
|
|
83
|
-
# -------------------------
|
|
84
|
-
# Markers
|
|
85
|
-
# -------------------------
|
|
86
|
-
self.marker_color = QtGui.QColor(255, 0, 0)
|
|
87
|
-
self.marker_size = 8
|
|
88
|
-
|
|
89
98
|
# -------------------------
|
|
90
99
|
# UI
|
|
91
100
|
# -------------------------
|
|
92
|
-
self.
|
|
101
|
+
self.central = QtWidgets.QWidget()
|
|
102
|
+
self.setCentralWidget(self.central)
|
|
103
|
+
|
|
104
|
+
self.layout = QtWidgets.QHBoxLayout()
|
|
105
|
+
self.central.setLayout(self.layout)
|
|
106
|
+
|
|
107
|
+
quit_action = QtWidgets.QAction("Quit", self)
|
|
108
|
+
quit_action.setShortcut("Ctrl+Q")
|
|
109
|
+
quit_action.triggered.connect(self.close)
|
|
110
|
+
|
|
111
|
+
self.addAction(quit_action)
|
|
112
|
+
self.layout.addWidget(self.viewer)
|
|
113
|
+
self.side = QtWidgets.QVBoxLayout()
|
|
114
|
+
self.side_widget = QtWidgets.QWidget()
|
|
115
|
+
self.side_widget.setFixedWidth(340)
|
|
116
|
+
self.side_widget.setLayout(self.side)
|
|
117
|
+
|
|
118
|
+
self._init_left_panel()
|
|
119
|
+
self._init_toolbar()
|
|
93
120
|
|
|
94
121
|
# -------------------------
|
|
95
122
|
# Signals
|
|
96
123
|
# -------------------------
|
|
97
|
-
self.viewer.auto_marker = False # Don't draw directly
|
|
98
|
-
self.viewer.left_click_signal.connect(self._process_left_click)
|
|
99
|
-
self.viewer.right_click_signal.connect(self._process_right_click)
|
|
100
|
-
|
|
101
124
|
self.initialization_done = True
|
|
102
125
|
self._append_log("Application ready.")
|
|
103
126
|
if self.output_path is not None:
|
|
@@ -108,72 +131,29 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
108
131
|
# ============================================================
|
|
109
132
|
# UI
|
|
110
133
|
# ============================================================
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
central = QtWidgets.QWidget()
|
|
118
|
-
self.setCentralWidget(central)
|
|
119
|
-
|
|
120
|
-
layout = QtWidgets.QHBoxLayout()
|
|
121
|
-
central.setLayout(layout)
|
|
122
|
-
|
|
123
|
-
quit_action = QtWidgets.QAction("Quit", self)
|
|
124
|
-
quit_action.setShortcut("Ctrl+Q")
|
|
125
|
-
quit_action.triggered.connect(self.close)
|
|
126
|
-
|
|
127
|
-
self.addAction(quit_action)
|
|
128
|
-
|
|
129
|
-
# -------------------------
|
|
130
|
-
# Viewer
|
|
131
|
-
# -------------------------
|
|
132
|
-
layout.addWidget(self.viewer)
|
|
133
|
-
|
|
134
|
-
# -------------------------
|
|
135
|
-
# Side panel
|
|
136
|
-
# -------------------------
|
|
137
|
-
side = QtWidgets.QVBoxLayout()
|
|
138
|
-
side_widget = QtWidgets.QWidget()
|
|
139
|
-
side_widget.setFixedWidth(340)
|
|
140
|
-
side_widget.setLayout(side)
|
|
141
|
-
|
|
134
|
+
def _init_left_panel(self):
|
|
135
|
+
r"""Build the left interface"""
|
|
136
|
+
# ============================================================
|
|
137
|
+
# Scroll Area
|
|
138
|
+
# ============================================================
|
|
142
139
|
scroll = QtWidgets.QScrollArea()
|
|
143
140
|
scroll.setWidgetResizable(True)
|
|
144
141
|
scroll.setFixedWidth(360)
|
|
145
|
-
scroll.setWidget(side_widget)
|
|
142
|
+
scroll.setWidget(self.side_widget)
|
|
146
143
|
|
|
147
|
-
layout.addWidget(scroll)
|
|
144
|
+
self.layout.addWidget(scroll)
|
|
148
145
|
|
|
149
146
|
# ============================================================
|
|
150
147
|
# Load Image
|
|
151
148
|
# ============================================================
|
|
152
|
-
self.
|
|
153
|
-
self.
|
|
154
|
-
self.
|
|
155
|
-
side.addWidget(self.
|
|
156
|
-
|
|
157
|
-
row = QtWidgets.QHBoxLayout()
|
|
158
|
-
|
|
159
|
-
self.colormap_selector = QtWidgets.QComboBox()
|
|
160
|
-
self.colormap_selector.addItem("Default")
|
|
161
|
-
self.colormap_selector.addItem("Gray")
|
|
162
|
-
self.colormap_selector.addItem("Hot")
|
|
163
|
-
self.colormap_selector.addItem("Jet")
|
|
164
|
-
self.colormap_selector.addItem("Rainbow")
|
|
165
|
-
self.colormap_selector.addItem("Cool")
|
|
166
|
-
self.colormap_selector.addItem("Spring")
|
|
167
|
-
self.colormap_selector.setCurrentIndex(0) # Default to "Default"
|
|
168
|
-
self.colormap_selector.currentIndexChanged.connect(self.on_colormap_changed)
|
|
169
|
-
row.addWidget(QtWidgets.QLabel("Image Colormap:"))
|
|
170
|
-
row.addWidget(self.colormap_selector)
|
|
149
|
+
self.load_image_btn = QtWidgets.QPushButton("Load Image (Ctrl+O)")
|
|
150
|
+
self.load_image_btn.clicked.connect(self.on_load_image)
|
|
151
|
+
self.load_image_btn.setShortcut("Ctrl+O")
|
|
152
|
+
self.side.addWidget(self.load_image_btn)
|
|
171
153
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
self.
|
|
175
|
-
self.loadc_btn.clicked.connect(self.on_load_clicks)
|
|
176
|
-
side.addWidget(self.loadc_btn)
|
|
154
|
+
self.load_click_btn = QtWidgets.QPushButton("Load Clicks")
|
|
155
|
+
self.load_click_btn.clicked.connect(self.on_load_clicks)
|
|
156
|
+
self.side.addWidget(self.load_click_btn)
|
|
177
157
|
|
|
178
158
|
# ============================================================
|
|
179
159
|
# Precision mode
|
|
@@ -182,21 +162,20 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
182
162
|
self.precision_checkbox.setChecked(False)
|
|
183
163
|
self.precision_checkbox.stateChanged.connect(self.on_precision_changed)
|
|
184
164
|
self.precision_checkbox.setShortcut("Ctrl+I")
|
|
185
|
-
side.addWidget(self.precision_checkbox)
|
|
186
|
-
|
|
165
|
+
self.side.addWidget(self.precision_checkbox)
|
|
187
166
|
self.half_shift_checkbox = QtWidgets.QCheckBox(
|
|
188
167
|
"Half-Shift: (0,0) on the pixel center."
|
|
189
168
|
)
|
|
190
169
|
self.half_shift_checkbox.setChecked(True)
|
|
191
170
|
self.half_shift_checkbox.stateChanged.connect(self.on_half_shift_changed)
|
|
192
|
-
side.addWidget(self.half_shift_checkbox)
|
|
171
|
+
self.side.addWidget(self.half_shift_checkbox)
|
|
193
172
|
|
|
194
173
|
self.display_clicks_checkbox = QtWidgets.QCheckBox("Display clicks")
|
|
195
174
|
self.display_clicks_checkbox.setChecked(True)
|
|
196
175
|
self.display_clicks_checkbox.stateChanged.connect(
|
|
197
176
|
self.on_display_clicks_changed
|
|
198
177
|
)
|
|
199
|
-
side.addWidget(self.display_clicks_checkbox)
|
|
178
|
+
self.side.addWidget(self.display_clicks_checkbox)
|
|
200
179
|
|
|
201
180
|
# ============================================================
|
|
202
181
|
# Group selector
|
|
@@ -211,7 +190,7 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
211
190
|
|
|
212
191
|
row.addWidget(self.group_selector)
|
|
213
192
|
|
|
214
|
-
side.addLayout(row)
|
|
193
|
+
self.side.addLayout(row)
|
|
215
194
|
|
|
216
195
|
# -------------------------
|
|
217
196
|
# Group management buttons
|
|
@@ -237,7 +216,7 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
237
216
|
row.addWidget(self.rename_group_btn)
|
|
238
217
|
row.addWidget(self.delete_group_btn)
|
|
239
218
|
|
|
240
|
-
side.addLayout(row)
|
|
219
|
+
self.side.addLayout(row)
|
|
241
220
|
|
|
242
221
|
# --------------------------
|
|
243
222
|
# Click Management
|
|
@@ -255,7 +234,7 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
255
234
|
row.addWidget(self.undo_btn)
|
|
256
235
|
row.addWidget(self.clear_btn)
|
|
257
236
|
|
|
258
|
-
side.addLayout(row)
|
|
237
|
+
self.side.addLayout(row)
|
|
259
238
|
|
|
260
239
|
# ============================================================
|
|
261
240
|
# Table
|
|
@@ -263,7 +242,7 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
263
242
|
self.table = QtWidgets.QTableWidget(0, 3)
|
|
264
243
|
self.table.setHorizontalHeaderLabels(["Index", "X", "Y"])
|
|
265
244
|
self.table.setMinimumHeight(180)
|
|
266
|
-
side.addWidget(self.table)
|
|
245
|
+
self.side.addWidget(self.table)
|
|
267
246
|
|
|
268
247
|
# ============================================================
|
|
269
248
|
# Save button
|
|
@@ -280,81 +259,216 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
280
259
|
self.save_as_btn.setShortcut("Ctrl+Shift+S")
|
|
281
260
|
row.addWidget(self.save_as_btn)
|
|
282
261
|
|
|
283
|
-
side.addLayout(row)
|
|
262
|
+
self.side.addLayout(row)
|
|
284
263
|
|
|
285
264
|
# ============================================================
|
|
286
265
|
# Log
|
|
287
266
|
# ============================================================
|
|
288
|
-
side.addWidget(self.log_text_edit)
|
|
267
|
+
self.side.addWidget(self.log_text_edit)
|
|
289
268
|
|
|
269
|
+
def _init_toolbar(self):
|
|
270
|
+
r"""Build toolbar"""
|
|
290
271
|
# ============================================================
|
|
291
272
|
# Toolbar
|
|
292
273
|
# ============================================================
|
|
274
|
+
|
|
293
275
|
toolbar = QtWidgets.QToolBar()
|
|
294
276
|
self.addToolBar(toolbar)
|
|
295
277
|
|
|
278
|
+
# ============================================================
|
|
279
|
+
# RESET
|
|
280
|
+
# ============================================================
|
|
281
|
+
|
|
296
282
|
toolbar.addAction("Reset View", self.viewer.reset_view)
|
|
297
283
|
|
|
298
284
|
toolbar.addSeparator()
|
|
299
285
|
|
|
300
286
|
# ============================================================
|
|
301
|
-
#
|
|
287
|
+
# IMAGE
|
|
302
288
|
# ============================================================
|
|
303
|
-
crosshair_color_widget = QtWidgets.QWidget()
|
|
304
|
-
crosshair_color_layout = QtWidgets.QHBoxLayout()
|
|
305
|
-
crosshair_color_layout.setContentsMargins(0, 0, 0, 0)
|
|
306
289
|
|
|
307
|
-
|
|
308
|
-
|
|
290
|
+
image_btn = QtWidgets.QToolButton()
|
|
291
|
+
image_btn.setText("⚙ Image")
|
|
292
|
+
image_btn.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
|
309
293
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
294
|
+
image_menu = QtWidgets.QMenu(self)
|
|
295
|
+
|
|
296
|
+
image_panel = QtWidgets.QWidget()
|
|
297
|
+
image_layout = QtWidgets.QFormLayout(image_panel)
|
|
298
|
+
|
|
299
|
+
# -------------------------
|
|
300
|
+
# Colormap
|
|
301
|
+
# -------------------------
|
|
302
|
+
|
|
303
|
+
self.colormap_selector = QtWidgets.QComboBox()
|
|
304
|
+
self.colormap_selector.addItems(
|
|
305
|
+
[
|
|
306
|
+
"Default",
|
|
307
|
+
"Gray",
|
|
308
|
+
"Hot",
|
|
309
|
+
"Jet",
|
|
310
|
+
"Rainbow",
|
|
311
|
+
"Cool",
|
|
312
|
+
"Spring",
|
|
313
|
+
]
|
|
313
314
|
)
|
|
314
315
|
|
|
315
|
-
|
|
316
|
-
crosshair_color_layout.addWidget(self.crosshair_color_btn)
|
|
317
|
-
crosshair_color_layout.addWidget(self.crosshair_color_preview)
|
|
316
|
+
self.colormap_selector.currentIndexChanged.connect(self.on_colormap_changed)
|
|
318
317
|
|
|
319
|
-
|
|
318
|
+
image_layout.addRow("Colormap", self.colormap_selector)
|
|
320
319
|
|
|
321
|
-
|
|
320
|
+
# -------------------------
|
|
321
|
+
# Alpha
|
|
322
|
+
# -------------------------
|
|
322
323
|
|
|
323
|
-
|
|
324
|
+
self.alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
|
325
|
+
self.alpha_slider.setRange(1, 500)
|
|
326
|
+
self.alpha_slider.setValue(100)
|
|
327
|
+
|
|
328
|
+
self.alpha_value_label = QtWidgets.QLabel("1.00")
|
|
329
|
+
|
|
330
|
+
self.alpha_slider.valueChanged.connect(self.on_contrast_changed)
|
|
331
|
+
|
|
332
|
+
alpha_widget = QtWidgets.QWidget()
|
|
333
|
+
alpha_layout = QtWidgets.QHBoxLayout(alpha_widget)
|
|
334
|
+
alpha_layout.setContentsMargins(0, 0, 0, 0)
|
|
335
|
+
|
|
336
|
+
alpha_layout.addWidget(self.alpha_slider)
|
|
337
|
+
alpha_layout.addWidget(self.alpha_value_label)
|
|
338
|
+
|
|
339
|
+
image_layout.addRow("Alpha", alpha_widget)
|
|
340
|
+
|
|
341
|
+
# -------------------------
|
|
342
|
+
# Beta
|
|
343
|
+
# -------------------------
|
|
344
|
+
|
|
345
|
+
self.beta_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
|
346
|
+
self.beta_slider.setRange(-100, 100)
|
|
347
|
+
self.beta_slider.setValue(0)
|
|
348
|
+
|
|
349
|
+
self.beta_value_label = QtWidgets.QLabel("0%")
|
|
350
|
+
|
|
351
|
+
self.beta_slider.valueChanged.connect(self.on_contrast_changed)
|
|
352
|
+
|
|
353
|
+
beta_widget = QtWidgets.QWidget()
|
|
354
|
+
beta_layout = QtWidgets.QHBoxLayout(beta_widget)
|
|
355
|
+
beta_layout.setContentsMargins(0, 0, 0, 0)
|
|
356
|
+
|
|
357
|
+
beta_layout.addWidget(self.beta_slider)
|
|
358
|
+
beta_layout.addWidget(self.beta_value_label)
|
|
359
|
+
|
|
360
|
+
image_layout.addRow("Beta", beta_widget)
|
|
361
|
+
|
|
362
|
+
# -------------------------
|
|
363
|
+
# Min
|
|
364
|
+
# -------------------------
|
|
365
|
+
|
|
366
|
+
self.min_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
|
367
|
+
self.min_slider.setRange(0, 100)
|
|
368
|
+
self.min_slider.setValue(0)
|
|
369
|
+
|
|
370
|
+
self.min_value_label = QtWidgets.QLabel("0%")
|
|
371
|
+
|
|
372
|
+
self.min_slider.valueChanged.connect(self.on_contrast_changed)
|
|
373
|
+
|
|
374
|
+
min_widget = QtWidgets.QWidget()
|
|
375
|
+
min_layout = QtWidgets.QHBoxLayout(min_widget)
|
|
376
|
+
min_layout.setContentsMargins(0, 0, 0, 0)
|
|
377
|
+
|
|
378
|
+
min_layout.addWidget(self.min_slider)
|
|
379
|
+
min_layout.addWidget(self.min_value_label)
|
|
380
|
+
|
|
381
|
+
image_layout.addRow("Min", min_widget)
|
|
382
|
+
|
|
383
|
+
# -------------------------
|
|
384
|
+
# Max
|
|
385
|
+
# -------------------------
|
|
386
|
+
|
|
387
|
+
self.max_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
|
388
|
+
self.max_slider.setRange(0, 100)
|
|
389
|
+
self.max_slider.setValue(100)
|
|
390
|
+
|
|
391
|
+
self.max_value_label = QtWidgets.QLabel("100%")
|
|
392
|
+
|
|
393
|
+
self.max_slider.valueChanged.connect(self.on_contrast_changed)
|
|
394
|
+
|
|
395
|
+
max_widget = QtWidgets.QWidget()
|
|
396
|
+
max_layout = QtWidgets.QHBoxLayout(max_widget)
|
|
397
|
+
max_layout.setContentsMargins(0, 0, 0, 0)
|
|
398
|
+
|
|
399
|
+
max_layout.addWidget(self.max_slider)
|
|
400
|
+
max_layout.addWidget(self.max_value_label)
|
|
401
|
+
|
|
402
|
+
image_layout.addRow("Max", max_widget)
|
|
403
|
+
|
|
404
|
+
# -------------------------
|
|
405
|
+
# Reset contrast
|
|
406
|
+
# -------------------------
|
|
407
|
+
|
|
408
|
+
self.reset_contrast_btn = QtWidgets.QPushButton("Reset")
|
|
409
|
+
|
|
410
|
+
self.reset_contrast_btn.clicked.connect(self.on_reset_contrast)
|
|
411
|
+
|
|
412
|
+
image_layout.addRow(self.reset_contrast_btn)
|
|
413
|
+
|
|
414
|
+
image_action = QtWidgets.QWidgetAction(image_menu)
|
|
415
|
+
image_action.setDefaultWidget(image_panel)
|
|
416
|
+
|
|
417
|
+
image_menu.addAction(image_action)
|
|
418
|
+
|
|
419
|
+
image_btn.setMenu(image_menu)
|
|
420
|
+
|
|
421
|
+
toolbar.addWidget(image_btn)
|
|
324
422
|
|
|
325
423
|
# ============================================================
|
|
326
|
-
#
|
|
424
|
+
# CLICKS
|
|
327
425
|
# ============================================================
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
426
|
+
|
|
427
|
+
toolbar.addSeparator()
|
|
428
|
+
|
|
429
|
+
clicks_btn = QtWidgets.QToolButton()
|
|
430
|
+
clicks_btn.setText("⚙ Clicks")
|
|
431
|
+
clicks_btn.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
|
432
|
+
|
|
433
|
+
clicks_menu = QtWidgets.QMenu(self)
|
|
434
|
+
|
|
435
|
+
clicks_panel = QtWidgets.QWidget()
|
|
436
|
+
clicks_layout = QtWidgets.QFormLayout(clicks_panel)
|
|
437
|
+
|
|
438
|
+
self.display_clicks_checkbox = QtWidgets.QCheckBox("Display clicks")
|
|
439
|
+
|
|
440
|
+
self.display_clicks_checkbox.setChecked(True)
|
|
441
|
+
|
|
442
|
+
self.display_clicks_checkbox.stateChanged.connect(
|
|
443
|
+
self.on_display_clicks_changed
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
clicks_layout.addRow(self.display_clicks_checkbox)
|
|
447
|
+
|
|
448
|
+
# Marker color
|
|
449
|
+
|
|
450
|
+
marker_widget = QtWidgets.QWidget()
|
|
451
|
+
marker_layout = QtWidgets.QHBoxLayout(marker_widget)
|
|
452
|
+
marker_layout.setContentsMargins(0, 0, 0, 0)
|
|
331
453
|
|
|
332
454
|
self.color_btn = QtWidgets.QPushButton("Color")
|
|
333
455
|
self.color_btn.clicked.connect(self.choose_color)
|
|
334
456
|
|
|
335
457
|
self.color_preview = QtWidgets.QLabel(" ")
|
|
458
|
+
|
|
336
459
|
self.color_preview.setStyleSheet(
|
|
337
460
|
"background-color: rgb(255,0,0); border: 1px solid black;"
|
|
338
461
|
)
|
|
339
462
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
color_layout.addWidget(self.color_preview)
|
|
343
|
-
|
|
344
|
-
color_widget.setLayout(color_layout)
|
|
463
|
+
marker_layout.addWidget(self.color_btn)
|
|
464
|
+
marker_layout.addWidget(self.color_preview)
|
|
345
465
|
|
|
346
|
-
|
|
466
|
+
clicks_layout.addRow("Marker", marker_widget)
|
|
347
467
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
# ============================================================
|
|
351
|
-
# Marker size selector
|
|
352
|
-
# ============================================================
|
|
353
|
-
size_widget = QtWidgets.QWidget()
|
|
354
|
-
size_layout = QtWidgets.QHBoxLayout()
|
|
355
|
-
size_layout.setContentsMargins(0, 0, 0, 0)
|
|
468
|
+
# Marker size
|
|
356
469
|
|
|
357
470
|
self.size_selector = QtWidgets.QComboBox()
|
|
471
|
+
|
|
358
472
|
self.size_selector.addItems(
|
|
359
473
|
[
|
|
360
474
|
"0.2",
|
|
@@ -374,77 +488,198 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
374
488
|
"100",
|
|
375
489
|
]
|
|
376
490
|
)
|
|
491
|
+
|
|
377
492
|
self.size_selector.setCurrentText("8")
|
|
493
|
+
|
|
378
494
|
self.size_selector.currentTextChanged.connect(self.on_marker_size_changed)
|
|
379
495
|
|
|
380
|
-
|
|
381
|
-
|
|
496
|
+
clicks_layout.addRow(
|
|
497
|
+
"Size",
|
|
498
|
+
self.size_selector,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
clicks_action = QtWidgets.QWidgetAction(clicks_menu)
|
|
502
|
+
clicks_action.setDefaultWidget(clicks_panel)
|
|
503
|
+
|
|
504
|
+
clicks_menu.addAction(clicks_action)
|
|
382
505
|
|
|
383
|
-
|
|
506
|
+
clicks_btn.setMenu(clicks_menu)
|
|
384
507
|
|
|
385
|
-
toolbar.addWidget(
|
|
508
|
+
toolbar.addWidget(clicks_btn)
|
|
509
|
+
|
|
510
|
+
# ============================================================
|
|
511
|
+
# CROSSHAIR
|
|
512
|
+
# ============================================================
|
|
386
513
|
|
|
387
514
|
toolbar.addSeparator()
|
|
388
515
|
|
|
389
|
-
|
|
516
|
+
crosshair_btn = QtWidgets.QToolButton()
|
|
517
|
+
crosshair_btn.setText("⚙ Crosshair")
|
|
518
|
+
crosshair_btn.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
|
519
|
+
|
|
520
|
+
crosshair_menu = QtWidgets.QMenu(self)
|
|
521
|
+
|
|
522
|
+
crosshair_panel = QtWidgets.QWidget()
|
|
523
|
+
crosshair_layout = QtWidgets.QFormLayout(crosshair_panel)
|
|
524
|
+
|
|
525
|
+
crosshair_widget = QtWidgets.QWidget()
|
|
526
|
+
crosshair_widget_layout = QtWidgets.QHBoxLayout(crosshair_widget)
|
|
527
|
+
|
|
528
|
+
crosshair_widget_layout.setContentsMargins(
|
|
529
|
+
0,
|
|
530
|
+
0,
|
|
531
|
+
0,
|
|
532
|
+
0,
|
|
533
|
+
)
|
|
390
534
|
|
|
535
|
+
self.crosshair_color_btn = QtWidgets.QPushButton("Color")
|
|
536
|
+
|
|
537
|
+
self.crosshair_color_btn.clicked.connect(self.crosshair_choose_color)
|
|
538
|
+
|
|
539
|
+
self.crosshair_color_preview = QtWidgets.QLabel(" ")
|
|
540
|
+
|
|
541
|
+
self.crosshair_color_preview.setStyleSheet(
|
|
542
|
+
"background-color: rgb(255,0,0); border: 1px solid black;"
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
crosshair_widget_layout.addWidget(self.crosshair_color_btn)
|
|
546
|
+
|
|
547
|
+
crosshair_widget_layout.addWidget(self.crosshair_color_preview)
|
|
548
|
+
|
|
549
|
+
crosshair_layout.addRow(
|
|
550
|
+
"Color",
|
|
551
|
+
crosshair_widget,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
crosshair_action = QtWidgets.QWidgetAction(crosshair_menu)
|
|
555
|
+
|
|
556
|
+
crosshair_action.setDefaultWidget(crosshair_panel)
|
|
557
|
+
|
|
558
|
+
crosshair_menu.addAction(crosshair_action)
|
|
559
|
+
|
|
560
|
+
crosshair_btn.setMenu(crosshair_menu)
|
|
561
|
+
|
|
562
|
+
toolbar.addWidget(crosshair_btn)
|
|
563
|
+
|
|
564
|
+
# ============================================================
|
|
565
|
+
# LOGS
|
|
566
|
+
# ============================================================
|
|
391
567
|
toolbar.addSeparator()
|
|
392
568
|
|
|
569
|
+
clear_log_btn = QtWidgets.QToolButton()
|
|
570
|
+
clear_log_btn.setText("Clear logs")
|
|
571
|
+
clear_log_btn.clicked.connect(self.clear_logs)
|
|
572
|
+
|
|
573
|
+
toolbar.addWidget(clear_log_btn)
|
|
574
|
+
|
|
393
575
|
# ============================================================
|
|
394
|
-
#
|
|
576
|
+
# Update pipeline
|
|
395
577
|
# ============================================================
|
|
396
578
|
|
|
397
|
-
def
|
|
579
|
+
def update(self):
|
|
398
580
|
r"""
|
|
399
|
-
|
|
581
|
+
Refresh UI.
|
|
400
582
|
"""
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
self._append_log(f"Precision mode: {'INT' if INT else 'FLOAT'}")
|
|
404
|
-
self.update()
|
|
583
|
+
if not self.initialization_done:
|
|
584
|
+
return
|
|
405
585
|
|
|
406
|
-
|
|
586
|
+
self.update_groups()
|
|
587
|
+
self.update_table()
|
|
588
|
+
self.update_viewer()
|
|
589
|
+
|
|
590
|
+
def _format_value(self, v):
|
|
591
|
+
if v is None:
|
|
592
|
+
return ""
|
|
593
|
+
|
|
594
|
+
if self.click_manager.use_int_precision:
|
|
595
|
+
return str(int(round(v)))
|
|
596
|
+
|
|
597
|
+
return f"{v:.3f}"
|
|
598
|
+
|
|
599
|
+
def update_viewer(self):
|
|
407
600
|
r"""
|
|
408
|
-
|
|
601
|
+
Render image + clicks.
|
|
409
602
|
"""
|
|
603
|
+
img = self.image.astype(np.float32)
|
|
604
|
+
imax = np.iinfo(self.image.dtype).max
|
|
605
|
+
dm = self.display_min_pc * imax / 100
|
|
606
|
+
dM = self.display_max_pc * imax / 100
|
|
607
|
+
b = self.beta_pc * imax / 100
|
|
410
608
|
|
|
411
|
-
|
|
609
|
+
img = np.clip(img, dm, dM)
|
|
412
610
|
|
|
413
|
-
|
|
611
|
+
img = (img - dm) / max(1, dM - dm) * imax
|
|
612
|
+
img = img * self.alpha + b
|
|
414
613
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
614
|
+
img = np.clip(np.round(img), 0, imax).astype(self.image.dtype)
|
|
615
|
+
|
|
616
|
+
if self._image_has_changed or self._colormap_has_changed:
|
|
617
|
+
colormap = self.get_selected_colormap()
|
|
618
|
+
|
|
619
|
+
if colormap is not None:
|
|
620
|
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
|
621
|
+
img = cv2.applyColorMap(gray, colormap)
|
|
622
|
+
|
|
623
|
+
# conversion unique pour Qt
|
|
624
|
+
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
625
|
+
|
|
626
|
+
h, w, ch = rgb.shape
|
|
627
|
+
bytes_per_line = ch * w
|
|
628
|
+
|
|
629
|
+
qimg = QtGui.QImage(
|
|
630
|
+
rgb.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888
|
|
419
631
|
)
|
|
420
|
-
|
|
421
|
-
msg.setStandardButtons(QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)
|
|
422
|
-
msg.setDefaultButton(QtWidgets.QMessageBox.Yes)
|
|
632
|
+
pix = QtGui.QPixmap.fromImage(qimg)
|
|
423
633
|
|
|
424
|
-
|
|
634
|
+
self.viewer.set_image(pix)
|
|
635
|
+
self._image_has_changed = False
|
|
636
|
+
self._colormap_has_changed = False
|
|
425
637
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
self.click_manager.to_half_shift_on()
|
|
429
|
-
else:
|
|
430
|
-
self.click_manager.to_half_shift_off()
|
|
638
|
+
# redraw clicks
|
|
639
|
+
self.viewer.clear_markers()
|
|
431
640
|
|
|
432
|
-
self.
|
|
433
|
-
self._append_log(f"Half-Shift mode: {half_shift}")
|
|
434
|
-
self.update()
|
|
641
|
+
pts = self.click_manager.extract_group()
|
|
435
642
|
|
|
436
|
-
|
|
643
|
+
if self.show_clicks:
|
|
644
|
+
for x, y in pts:
|
|
645
|
+
if x is None or y is None:
|
|
646
|
+
continue
|
|
647
|
+
self.viewer.add_marker((x, y), self.marker_color, self.marker_size)
|
|
648
|
+
|
|
649
|
+
def update_table(self):
|
|
437
650
|
r"""
|
|
438
|
-
|
|
651
|
+
Update table with current group points.
|
|
439
652
|
"""
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
self.
|
|
653
|
+
pts = self.click_manager.extract_group()
|
|
654
|
+
|
|
655
|
+
if self.click_manager.use_float_precision:
|
|
656
|
+
self.table.setHorizontalHeaderLabels(["Index", "X (float)", "Y (float)"])
|
|
657
|
+
else:
|
|
658
|
+
self.table.setHorizontalHeaderLabels(["Index", "X (int)", "Y (int)"])
|
|
659
|
+
|
|
660
|
+
self.table.setRowCount(len(pts))
|
|
661
|
+
|
|
662
|
+
for i, (x, y) in enumerate(pts):
|
|
663
|
+
self.table.setItem(i, 0, QtWidgets.QTableWidgetItem(str(i)))
|
|
664
|
+
self.table.setItem(i, 1, QtWidgets.QTableWidgetItem(self._format_value(x)))
|
|
665
|
+
self.table.setItem(i, 2, QtWidgets.QTableWidgetItem(self._format_value(y)))
|
|
666
|
+
|
|
667
|
+
def update_groups(self):
|
|
668
|
+
r"""
|
|
669
|
+
Sync group selector.
|
|
670
|
+
"""
|
|
671
|
+
self.group_selector.blockSignals(True)
|
|
672
|
+
self.group_selector.clear()
|
|
673
|
+
|
|
674
|
+
for g in self.click_manager.groups:
|
|
675
|
+
self.group_selector.addItem(g)
|
|
676
|
+
|
|
677
|
+
self.group_selector.setCurrentText(self.click_manager.current_group)
|
|
678
|
+
self.group_selector.blockSignals(False)
|
|
443
679
|
|
|
444
680
|
# ============================================================
|
|
445
681
|
# Image
|
|
446
682
|
# ============================================================
|
|
447
|
-
|
|
448
683
|
def set_image(self, image: Optional[np.ndarray]):
|
|
449
684
|
r"""
|
|
450
685
|
Set image to display.
|
|
@@ -529,99 +764,60 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
529
764
|
|
|
530
765
|
self.update()
|
|
531
766
|
|
|
532
|
-
|
|
533
|
-
# Update pipeline
|
|
534
|
-
# ============================================================
|
|
535
|
-
|
|
536
|
-
def update(self):
|
|
537
|
-
r"""
|
|
538
|
-
Refresh UI.
|
|
767
|
+
def on_colormap_changed(self, index):
|
|
539
768
|
"""
|
|
540
|
-
|
|
541
|
-
return
|
|
542
|
-
|
|
543
|
-
self.update_groups()
|
|
544
|
-
self.update_table()
|
|
545
|
-
self.update_viewer()
|
|
546
|
-
|
|
547
|
-
def _format_value(self, v):
|
|
548
|
-
if v is None:
|
|
549
|
-
return ""
|
|
550
|
-
|
|
551
|
-
if self.click_manager.use_int_precision:
|
|
552
|
-
return str(int(round(v)))
|
|
553
|
-
|
|
554
|
-
return f"{v:.3f}"
|
|
555
|
-
|
|
556
|
-
def update_viewer(self):
|
|
557
|
-
r"""
|
|
558
|
-
Render image + clicks.
|
|
769
|
+
Called when the user selects a different colormap.
|
|
559
770
|
"""
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if colormap is not None:
|
|
565
|
-
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
|
566
|
-
img = cv2.applyColorMap(gray, colormap)
|
|
567
|
-
|
|
568
|
-
# conversion unique pour Qt
|
|
569
|
-
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
771
|
+
self._colormap_has_changed = True
|
|
772
|
+
self._append_log(f"Colormap changed to: {self.colormap_selector.currentText()}")
|
|
773
|
+
self.update()
|
|
570
774
|
|
|
571
|
-
|
|
572
|
-
|
|
775
|
+
def get_selected_colormap(self):
|
|
776
|
+
"""
|
|
777
|
+
Get the selected colormap for displaying the image.
|
|
573
778
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
779
|
+
Returns
|
|
780
|
+
-------
|
|
781
|
+
str
|
|
782
|
+
The selected colormap.
|
|
783
|
+
"""
|
|
784
|
+
colormap_map = {
|
|
785
|
+
"Default": None,
|
|
786
|
+
"Gray": cv2.COLORMAP_BONE,
|
|
787
|
+
"Hot": cv2.COLORMAP_HOT,
|
|
788
|
+
"Jet": cv2.COLORMAP_JET,
|
|
789
|
+
"Rainbow": cv2.COLORMAP_RAINBOW,
|
|
790
|
+
"Cool": cv2.COLORMAP_COOL,
|
|
791
|
+
"Spring": cv2.COLORMAP_SPRING,
|
|
792
|
+
}
|
|
793
|
+
return colormap_map.get(self.colormap_selector.currentText(), None)
|
|
578
794
|
|
|
579
|
-
|
|
580
|
-
self._image_has_changed = False
|
|
581
|
-
self._colormap_has_changed = False
|
|
795
|
+
def on_contrast_changed(self):
|
|
582
796
|
|
|
583
|
-
|
|
584
|
-
self.
|
|
797
|
+
self.alpha = self.alpha_slider.value() / 100
|
|
798
|
+
self.beta_pc = self.beta_slider.value()
|
|
799
|
+
self.display_min_pc = self.min_slider.value()
|
|
800
|
+
self.display_max_pc = self.max_slider.value()
|
|
585
801
|
|
|
586
|
-
|
|
802
|
+
self.alpha_value_label.setText(f"{self.alpha:.2f}")
|
|
803
|
+
self.beta_value_label.setText(f"{self.beta_pc}%")
|
|
804
|
+
self.min_value_label.setText(f"{self.display_min_pc}%")
|
|
805
|
+
self.max_value_label.setText(f"{self.display_max_pc}%")
|
|
587
806
|
|
|
588
|
-
|
|
589
|
-
|
|
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)
|
|
807
|
+
self._image_has_changed = True
|
|
808
|
+
self.update()
|
|
594
809
|
|
|
595
|
-
def
|
|
596
|
-
r"""
|
|
597
|
-
Update table with current group points.
|
|
810
|
+
def on_reset_contrast(self):
|
|
598
811
|
"""
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if self.click_manager.use_float_precision:
|
|
602
|
-
self.table.setHorizontalHeaderLabels(["Index", "X (float)", "Y (float)"])
|
|
603
|
-
else:
|
|
604
|
-
self.table.setHorizontalHeaderLabels(["Index", "X (int)", "Y (int)"])
|
|
605
|
-
|
|
606
|
-
self.table.setRowCount(len(pts))
|
|
607
|
-
|
|
608
|
-
for i, (x, y) in enumerate(pts):
|
|
609
|
-
self.table.setItem(i, 0, QtWidgets.QTableWidgetItem(str(i)))
|
|
610
|
-
self.table.setItem(i, 1, QtWidgets.QTableWidgetItem(self._format_value(x)))
|
|
611
|
-
self.table.setItem(i, 2, QtWidgets.QTableWidgetItem(self._format_value(y)))
|
|
612
|
-
|
|
613
|
-
def update_groups(self):
|
|
614
|
-
r"""
|
|
615
|
-
Sync group selector.
|
|
812
|
+
Reset contrast settings.
|
|
616
813
|
"""
|
|
617
|
-
self.
|
|
618
|
-
self.
|
|
814
|
+
self.alpha_slider.setValue(100)
|
|
815
|
+
self.beta_slider.setValue(0)
|
|
619
816
|
|
|
620
|
-
|
|
621
|
-
|
|
817
|
+
self.min_slider.setValue(0)
|
|
818
|
+
self.max_slider.setValue(100)
|
|
622
819
|
|
|
623
|
-
self.
|
|
624
|
-
self.group_selector.blockSignals(False)
|
|
820
|
+
self.on_contrast_changed()
|
|
625
821
|
|
|
626
822
|
# ============================================================
|
|
627
823
|
# Click handling
|
|
@@ -635,6 +831,7 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
635
831
|
return
|
|
636
832
|
|
|
637
833
|
self.click_manager.add_click(x, y)
|
|
834
|
+
self._append_log(f"Click processed: {(x, y)}")
|
|
638
835
|
self._is_saved = False
|
|
639
836
|
self.update()
|
|
640
837
|
|
|
@@ -646,6 +843,7 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
646
843
|
return
|
|
647
844
|
|
|
648
845
|
self.click_manager.add_click(None, None)
|
|
846
|
+
self._append_log(f"Click processed: {(None, None)}")
|
|
649
847
|
self._is_saved = False
|
|
650
848
|
self.update()
|
|
651
849
|
|
|
@@ -689,6 +887,53 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
689
887
|
self._is_saved = False
|
|
690
888
|
self.update()
|
|
691
889
|
|
|
890
|
+
def on_precision_changed(self, state):
|
|
891
|
+
r"""
|
|
892
|
+
Toggle integer/float precision mode.
|
|
893
|
+
"""
|
|
894
|
+
INT = state == QtCore.Qt.Checked
|
|
895
|
+
self.click_manager.precision_mode = "int" if INT else "float"
|
|
896
|
+
self._append_log(f"Precision mode: {'INT' if INT else 'FLOAT'}")
|
|
897
|
+
self.update()
|
|
898
|
+
|
|
899
|
+
def on_half_shift_changed(self, state):
|
|
900
|
+
r"""
|
|
901
|
+
Toggle half-shift mode.
|
|
902
|
+
"""
|
|
903
|
+
|
|
904
|
+
half_shift = state == QtCore.Qt.Checked
|
|
905
|
+
|
|
906
|
+
if self.click_manager.n_clicks > 0:
|
|
907
|
+
|
|
908
|
+
msg = QtWidgets.QMessageBox(self)
|
|
909
|
+
msg.setWindowTitle("Existing clicks detected")
|
|
910
|
+
msg.setText(
|
|
911
|
+
f"Do you want to shift all previous clicked points by 0.5 to match the new coordinates system ?"
|
|
912
|
+
)
|
|
913
|
+
msg.setInformativeText("No = keep clicks\nYes = shift clicks")
|
|
914
|
+
msg.setStandardButtons(QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)
|
|
915
|
+
msg.setDefaultButton(QtWidgets.QMessageBox.Yes)
|
|
916
|
+
|
|
917
|
+
choice = msg.exec_()
|
|
918
|
+
|
|
919
|
+
if choice == QtWidgets.QMessageBox.Yes:
|
|
920
|
+
if half_shift:
|
|
921
|
+
self.click_manager.to_half_shift_on()
|
|
922
|
+
else:
|
|
923
|
+
self.click_manager.to_half_shift_off()
|
|
924
|
+
|
|
925
|
+
self.viewer.half_shift = half_shift
|
|
926
|
+
self._append_log(f"Half-Shift mode: {half_shift}")
|
|
927
|
+
self.update()
|
|
928
|
+
|
|
929
|
+
def on_display_clicks_changed(self, state):
|
|
930
|
+
r"""
|
|
931
|
+
Toggle dispaly clicks.
|
|
932
|
+
"""
|
|
933
|
+
self.show_clicks = state == QtCore.Qt.Checked
|
|
934
|
+
self._append_log(f"Display clicks: {self.show_clicks}")
|
|
935
|
+
self.update()
|
|
936
|
+
|
|
692
937
|
# ============================================================
|
|
693
938
|
# Design
|
|
694
939
|
# ============================================================
|
|
@@ -703,6 +948,8 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
703
948
|
f"background-color: {color.name()}; border: 1px solid black;"
|
|
704
949
|
)
|
|
705
950
|
|
|
951
|
+
self._append_log(f"Crosshair color set to: {color.name()}")
|
|
952
|
+
|
|
706
953
|
def choose_color(self):
|
|
707
954
|
color = QtWidgets.QColorDialog.getColor(self.marker_color, self)
|
|
708
955
|
|
|
@@ -714,6 +961,8 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
714
961
|
f"background-color: {color.name()}; border: 1px solid black;"
|
|
715
962
|
)
|
|
716
963
|
|
|
964
|
+
self._append_log(f"Marker color set to: {color.name()}")
|
|
965
|
+
|
|
717
966
|
def on_marker_size_changed(self, value: str):
|
|
718
967
|
r"""
|
|
719
968
|
Update marker size.
|
|
@@ -724,47 +973,19 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
724
973
|
except ValueError:
|
|
725
974
|
self.marker_size = 8
|
|
726
975
|
|
|
727
|
-
self._append_log(f"Marker size set to {self.marker_size}")
|
|
976
|
+
self._append_log(f"Marker size set to: {self.marker_size}")
|
|
728
977
|
|
|
729
978
|
self.update()
|
|
730
979
|
|
|
731
|
-
def on_colormap_changed(self, index):
|
|
732
|
-
"""
|
|
733
|
-
Called when the user selects a different colormap.
|
|
734
|
-
"""
|
|
735
|
-
self._append_log(f"Colormap changed to: {self.colormap_selector.currentText()}")
|
|
736
|
-
self._colormap_has_changed = True
|
|
737
|
-
self.update()
|
|
738
|
-
|
|
739
|
-
def get_selected_colormap(self):
|
|
740
|
-
"""
|
|
741
|
-
Get the selected colormap for displaying the image.
|
|
742
|
-
|
|
743
|
-
Returns
|
|
744
|
-
-------
|
|
745
|
-
str
|
|
746
|
-
The selected colormap.
|
|
747
|
-
"""
|
|
748
|
-
colormap_map = {
|
|
749
|
-
"Default": None,
|
|
750
|
-
"Gray": cv2.COLORMAP_BONE,
|
|
751
|
-
"Hot": cv2.COLORMAP_HOT,
|
|
752
|
-
"Jet": cv2.COLORMAP_JET,
|
|
753
|
-
"Rainbow": cv2.COLORMAP_RAINBOW,
|
|
754
|
-
"Cool": cv2.COLORMAP_COOL,
|
|
755
|
-
"Spring": cv2.COLORMAP_SPRING,
|
|
756
|
-
}
|
|
757
|
-
return colormap_map.get(self.colormap_selector.currentText(), None)
|
|
758
|
-
|
|
759
980
|
# ============================================================
|
|
760
981
|
# Groups
|
|
761
982
|
# ============================================================
|
|
762
|
-
|
|
763
983
|
def on_group_changed(self, name):
|
|
764
984
|
r"""
|
|
765
985
|
Switch active group.
|
|
766
986
|
"""
|
|
767
987
|
self.click_manager.set_group(name)
|
|
988
|
+
self._append_log(f"Group switch to : {name}")
|
|
768
989
|
self.update()
|
|
769
990
|
|
|
770
991
|
def on_add_group(self):
|
|
@@ -863,7 +1084,7 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
863
1084
|
self.output_path = file_path
|
|
864
1085
|
|
|
865
1086
|
if self.output_path is not None:
|
|
866
|
-
self._append_log(f"Output CSV path setted to
|
|
1087
|
+
self._append_log(f"Output CSV path setted to: {self.output_path}")
|
|
867
1088
|
|
|
868
1089
|
# -------------------------------------------------
|
|
869
1090
|
# Save
|
|
@@ -928,7 +1149,6 @@ class ClickImageApp(QtWidgets.QMainWindow):
|
|
|
928
1149
|
# ============================================================
|
|
929
1150
|
# Utils
|
|
930
1151
|
# ============================================================
|
|
931
|
-
|
|
932
1152
|
def _append_log(self, msg: str):
|
|
933
1153
|
r"""
|
|
934
1154
|
Append log message.
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyclickimage
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -677,10 +677,10 @@ License: GNU GENERAL PUBLIC LICENSE
|
|
|
677
677
|
the library. If this is what you want to do, use the GNU Lesser General
|
|
678
678
|
Public License instead of this License. But first, please read
|
|
679
679
|
<https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
680
|
-
Project-URL: Homepage, https://github.com/Artezaru/
|
|
681
|
-
Project-URL: Documentation, https://Artezaru.github.io/
|
|
682
|
-
Project-URL: Source, https://github.com/Artezaru/
|
|
683
|
-
Project-URL: Tracker, https://github.com/Artezaru/
|
|
680
|
+
Project-URL: Homepage, https://github.com/Artezaru/pyclickimage
|
|
681
|
+
Project-URL: Documentation, https://Artezaru.github.io/pyclickimage
|
|
682
|
+
Project-URL: Source, https://github.com/Artezaru/pyclickimage
|
|
683
|
+
Project-URL: Tracker, https://github.com/Artezaru/pyclickimage/issues
|
|
684
684
|
Classifier: Programming Language :: Python :: 3
|
|
685
685
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
686
686
|
Classifier: Operating System :: OS Independent
|
|
@@ -26,10 +26,10 @@ dependencies = [
|
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
29
|
-
Homepage = "https://github.com/Artezaru/
|
|
30
|
-
Documentation = "https://Artezaru.github.io/
|
|
31
|
-
Source = "https://github.com/Artezaru/
|
|
32
|
-
Tracker = "https://github.com/Artezaru/
|
|
29
|
+
Homepage = "https://github.com/Artezaru/pyclickimage"
|
|
30
|
+
Documentation = "https://Artezaru.github.io/pyclickimage"
|
|
31
|
+
Source = "https://github.com/Artezaru/pyclickimage"
|
|
32
|
+
Tracker = "https://github.com/Artezaru/pyclickimage/issues"
|
|
33
33
|
|
|
34
34
|
[tool.setuptools.packages.find]
|
|
35
35
|
where = ["."]
|
|
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
|