Realcat commited on
Commit
7acaad7
β€’
1 Parent(s): 9705edb

update: interface

Browse files
Files changed (5) hide show
  1. app.py +10 -382
  2. common/app_class.py +403 -0
  3. common/config.yaml +108 -0
  4. common/utils.py +67 -196
  5. common/viz.py +79 -0
app.py CHANGED
@@ -1,385 +1,6 @@
1
  import argparse
2
  from pathlib import Path
3
- import numpy as np
4
- from typing import Dict, Any, Optional, Tuple, List, Union
5
- import gradio as gr
6
- from common.utils import (
7
- matcher_zoo,
8
- ransac_zoo,
9
- change_estimate_geom,
10
- run_matching,
11
- gen_examples,
12
- GRADIO_VERSION,
13
- DEFAULT_RANSAC_METHOD,
14
- DEFAULT_SETTING_GEOMETRY,
15
- DEFAULT_RANSAC_REPROJ_THRESHOLD,
16
- DEFAULT_RANSAC_CONFIDENCE,
17
- DEFAULT_RANSAC_MAX_ITER,
18
- DEFAULT_MATCHING_THRESHOLD,
19
- DEFAULT_SETTING_MAX_FEATURES,
20
- DEFAULT_DEFAULT_KEYPOINT_THRESHOLD,
21
- )
22
-
23
- DESCRIPTION = """
24
- # Image Matching WebUI
25
- This Space demonstrates [Image Matching WebUI](https://github.com/Vincentqyw/image-matching-webui) by vincent qin. Feel free to play with it, or duplicate to run image matching without a queue!
26
- <br/>
27
- πŸ”Ž For more details about supported local features and matchers, please refer to https://github.com/Vincentqyw/image-matching-webui
28
-
29
- πŸš€ All algorithms run on CPU for inference, causing slow speeds and high latency. For faster inference, please download the [source code](https://github.com/Vincentqyw/image-matching-webui) for local deployment.
30
-
31
- πŸ› Your feedback is valuable to me. Please do not hesitate to report any bugs [here](https://github.com/Vincentqyw/image-matching-webui/issues).
32
- """
33
-
34
-
35
- def ui_change_imagebox(choice):
36
- """
37
- Updates the image box with the given choice.
38
-
39
- Args:
40
- choice (list): The list of image sources to be displayed in the image box.
41
-
42
- Returns:
43
- dict: A dictionary containing the updated value, sources, and type for the image box.
44
- """
45
- ret_dict = {
46
- "value": None, # The updated value of the image box
47
- "__type__": "update", # The type of update for the image box
48
- }
49
- if GRADIO_VERSION > "3":
50
- return {
51
- **ret_dict,
52
- "sources": choice, # The list of image sources to be displayed
53
- }
54
- else:
55
- return {
56
- **ret_dict,
57
- "source": choice, # The list of image sources to be displayed
58
- }
59
-
60
-
61
- def ui_reset_state(
62
- *args: Any,
63
- ) -> Tuple[
64
- Optional[np.ndarray],
65
- Optional[np.ndarray],
66
- float,
67
- int,
68
- float,
69
- str,
70
- Dict[str, Any],
71
- Dict[str, Any],
72
- str,
73
- Optional[np.ndarray],
74
- Optional[np.ndarray],
75
- Optional[np.ndarray],
76
- Dict[str, Any],
77
- Dict[str, Any],
78
- Optional[np.ndarray],
79
- Dict[str, Any],
80
- str,
81
- int,
82
- float,
83
- int,
84
- ]:
85
- """
86
- Reset the state of the UI.
87
-
88
- Returns:
89
- tuple: A tuple containing the initial values for the UI state.
90
- """
91
- key: str = list(matcher_zoo.keys())[0] # Get the first key from matcher_zoo
92
- return (
93
- None, # image0: Optional[np.ndarray]
94
- None, # image1: Optional[np.ndarray]
95
- DEFAULT_MATCHING_THRESHOLD, # matching_threshold: float
96
- DEFAULT_SETTING_MAX_FEATURES, # max_features: int
97
- DEFAULT_DEFAULT_KEYPOINT_THRESHOLD, # keypoint_threshold: float
98
- key, # matcher: str
99
- ui_change_imagebox("upload"), # input image0: Dict[str, Any]
100
- ui_change_imagebox("upload"), # input image1: Dict[str, Any]
101
- "upload", # match_image_src: str
102
- None, # keypoints: Optional[np.ndarray]
103
- None, # raw matches: Optional[np.ndarray]
104
- None, # ransac matches: Optional[np.ndarray]
105
- {}, # matches result info: Dict[str, Any]
106
- {}, # matcher config: Dict[str, Any]
107
- None, # warped image: Optional[np.ndarray]
108
- {}, # geometry result: Dict[str, Any]
109
- DEFAULT_RANSAC_METHOD, # ransac_method: str
110
- DEFAULT_RANSAC_REPROJ_THRESHOLD, # ransac_reproj_threshold: float
111
- DEFAULT_RANSAC_CONFIDENCE, # ransac_confidence: float
112
- DEFAULT_RANSAC_MAX_ITER, # ransac_max_iter: int
113
- DEFAULT_SETTING_GEOMETRY, # geometry: str
114
- )
115
-
116
-
117
- # "footer {visibility: hidden}"
118
- def run(server_name="0.0.0.0", server_port=7860):
119
- """
120
- Runs the application.
121
-
122
- Args:
123
- config (dict): A dictionary containing configuration parameters for the application.
124
-
125
- Returns:
126
- None
127
- """
128
- with gr.Blocks() as app:
129
- # gr.Markdown(DESCRIPTION)
130
- with gr.Row():
131
- with gr.Column(scale=1):
132
- gr.Image(
133
- str(Path(__file__).parent / "assets/logo.webp"),
134
- elem_id="logo-img",
135
- show_label=False,
136
- show_share_button=False,
137
- show_download_button=False,
138
- )
139
- with gr.Column(scale=3):
140
- gr.Markdown(DESCRIPTION)
141
- with gr.Row(equal_height=False):
142
- with gr.Column():
143
- with gr.Row():
144
- matcher_list = gr.Dropdown(
145
- choices=list(matcher_zoo.keys()),
146
- value="disk+lightglue",
147
- label="Matching Model",
148
- interactive=True,
149
- )
150
- match_image_src = gr.Radio(
151
- (
152
- ["upload", "webcam", "clipboard"]
153
- if GRADIO_VERSION > "3"
154
- else ["upload", "webcam", "canvas"]
155
- ),
156
- label="Image Source",
157
- value="upload",
158
- )
159
- with gr.Row():
160
- input_image0 = gr.Image(
161
- label="Image 0",
162
- type="numpy",
163
- image_mode="RGB",
164
- height=300 if GRADIO_VERSION > "3" else None,
165
- interactive=True,
166
- )
167
- input_image1 = gr.Image(
168
- label="Image 1",
169
- type="numpy",
170
- image_mode="RGB",
171
- height=300 if GRADIO_VERSION > "3" else None,
172
- interactive=True,
173
- )
174
-
175
- with gr.Row():
176
- button_reset = gr.Button(value="Reset")
177
- button_run = gr.Button(value="Run Match", variant="primary")
178
-
179
- with gr.Accordion("Advanced Setting", open=False):
180
- with gr.Accordion("Matching Setting", open=True):
181
- with gr.Row():
182
- match_setting_threshold = gr.Slider(
183
- minimum=0.0,
184
- maximum=1,
185
- step=0.001,
186
- label="Match thres.",
187
- value=0.1,
188
- )
189
- match_setting_max_features = gr.Slider(
190
- minimum=10,
191
- maximum=10000,
192
- step=10,
193
- label="Max features",
194
- value=1000,
195
- )
196
- # TODO: add line settings
197
- with gr.Row():
198
- detect_keypoints_threshold = gr.Slider(
199
- minimum=0,
200
- maximum=1,
201
- step=0.001,
202
- label="Keypoint thres.",
203
- value=0.015,
204
- )
205
- detect_line_threshold = gr.Slider(
206
- minimum=0.1,
207
- maximum=1,
208
- step=0.01,
209
- label="Line thres.",
210
- value=0.2,
211
- )
212
- # matcher_lists = gr.Radio(
213
- # ["NN-mutual", "Dual-Softmax"],
214
- # label="Matcher mode",
215
- # value="NN-mutual",
216
- # )
217
- with gr.Accordion("RANSAC Setting", open=True):
218
- with gr.Row(equal_height=False):
219
- ransac_method = gr.Dropdown(
220
- choices=ransac_zoo.keys(),
221
- value=DEFAULT_RANSAC_METHOD,
222
- label="RANSAC Method",
223
- interactive=True,
224
- )
225
- ransac_reproj_threshold = gr.Slider(
226
- minimum=0.0,
227
- maximum=12,
228
- step=0.01,
229
- label="Ransac Reproj threshold",
230
- value=8.0,
231
- )
232
- ransac_confidence = gr.Slider(
233
- minimum=0.0,
234
- maximum=1,
235
- step=0.00001,
236
- label="Ransac Confidence",
237
- value=DEFAULT_RANSAC_CONFIDENCE,
238
- )
239
- ransac_max_iter = gr.Slider(
240
- minimum=0.0,
241
- maximum=100000,
242
- step=100,
243
- label="Ransac Iterations",
244
- value=DEFAULT_RANSAC_MAX_ITER,
245
- )
246
-
247
- with gr.Accordion("Geometry Setting", open=False):
248
- with gr.Row(equal_height=False):
249
- choice_estimate_geom = gr.Radio(
250
- ["Fundamental", "Homography"],
251
- label="Reconstruct Geometry",
252
- value=DEFAULT_SETTING_GEOMETRY,
253
- )
254
-
255
- # collect inputs
256
- inputs = [
257
- input_image0,
258
- input_image1,
259
- match_setting_threshold,
260
- match_setting_max_features,
261
- detect_keypoints_threshold,
262
- matcher_list,
263
- ransac_method,
264
- ransac_reproj_threshold,
265
- ransac_confidence,
266
- ransac_max_iter,
267
- choice_estimate_geom,
268
- ]
269
-
270
- # Add some examples
271
- with gr.Row():
272
- # Example inputs
273
- gr.Examples(
274
- examples=gen_examples(),
275
- inputs=inputs,
276
- outputs=[],
277
- fn=run_matching,
278
- cache_examples=False,
279
- label=(
280
- "Examples (click one of the images below to Run"
281
- " Match)"
282
- ),
283
- )
284
- with gr.Accordion("Open for More!", open=False):
285
- gr.Markdown(
286
- f"""
287
- <h3>Supported Algorithms</h3>
288
- {", ".join(matcher_zoo.keys())}
289
- """
290
- )
291
-
292
- with gr.Column():
293
- output_keypoints = gr.Image(label="Keypoints", type="numpy")
294
- output_matches_raw = gr.Image(label="Raw Matches", type="numpy")
295
- output_matches_ransac = gr.Image(
296
- label="Ransac Matches", type="numpy"
297
- )
298
- with gr.Accordion(
299
- "Open for More: Matches Statistics", open=False
300
- ):
301
- matches_result_info = gr.JSON(label="Matches Statistics")
302
- matcher_info = gr.JSON(label="Match info")
303
-
304
- with gr.Accordion("Open for More: Warped Image", open=False):
305
- output_wrapped = gr.Image(
306
- label="Wrapped Pair", type="numpy"
307
- )
308
- with gr.Accordion(
309
- "Open for More: Geometry info", open=False
310
- ):
311
- geometry_result = gr.JSON(
312
- label="Reconstructed Geometry"
313
- )
314
-
315
- # callbacks
316
- match_image_src.change(
317
- fn=ui_change_imagebox,
318
- inputs=match_image_src,
319
- outputs=input_image0,
320
- )
321
- match_image_src.change(
322
- fn=ui_change_imagebox,
323
- inputs=match_image_src,
324
- outputs=input_image1,
325
- )
326
-
327
- # collect outputs
328
- outputs = [
329
- output_keypoints,
330
- output_matches_raw,
331
- output_matches_ransac,
332
- matches_result_info,
333
- matcher_info,
334
- geometry_result,
335
- output_wrapped,
336
- ]
337
- # button callbacks
338
- button_run.click(fn=run_matching, inputs=inputs, outputs=outputs)
339
-
340
- # Reset images
341
- reset_outputs = [
342
- input_image0,
343
- input_image1,
344
- match_setting_threshold,
345
- match_setting_max_features,
346
- detect_keypoints_threshold,
347
- matcher_list,
348
- input_image0,
349
- input_image1,
350
- match_image_src,
351
- output_keypoints,
352
- output_matches_raw,
353
- output_matches_ransac,
354
- matches_result_info,
355
- matcher_info,
356
- output_wrapped,
357
- geometry_result,
358
- ransac_method,
359
- ransac_reproj_threshold,
360
- ransac_confidence,
361
- ransac_max_iter,
362
- choice_estimate_geom,
363
- ]
364
- button_reset.click(
365
- fn=ui_reset_state, inputs=inputs, outputs=reset_outputs
366
- )
367
-
368
- # estimate geo
369
- choice_estimate_geom.change(
370
- fn=change_estimate_geom,
371
- inputs=[
372
- input_image0,
373
- input_image1,
374
- geometry_result,
375
- choice_estimate_geom,
376
- ],
377
- outputs=[output_wrapped, geometry_result],
378
- )
379
- app.queue().launch(
380
- server_name=server_name, server_port=server_port, share=False
381
- )
382
-
383
 
384
  if __name__ == "__main__":
385
  parser = argparse.ArgumentParser()
@@ -395,6 +16,13 @@ if __name__ == "__main__":
395
  default=7860,
396
  help="server port",
397
  )
398
-
 
 
 
 
 
399
  args = parser.parse_args()
400
- run(args.server_name, args.server_port)
 
 
 
1
  import argparse
2
  from pathlib import Path
3
+ from common.app_class import ImageMatchingApp
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  if __name__ == "__main__":
6
  parser = argparse.ArgumentParser()
 
16
  default=7860,
17
  help="server port",
18
  )
19
+ parser.add_argument(
20
+ "--config",
21
+ type=str,
22
+ default=Path(__file__).parent / "common/config.yaml",
23
+ help="config file",
24
+ )
25
  args = parser.parse_args()
26
+ ImageMatchingApp(
27
+ args.server_name, args.server_port, config=args.config
28
+ ).run()
common/app_class.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import numpy as np
3
+ import gradio as gr
4
+ from pathlib import Path
5
+ from typing import Dict, Any, Optional, Tuple, List, Union
6
+ from common.utils import (
7
+ ransac_zoo,
8
+ change_estimate_geom,
9
+ load_config,
10
+ get_matcher_zoo,
11
+ run_matching,
12
+ gen_examples,
13
+ GRADIO_VERSION,
14
+ )
15
+
16
+ DESCRIPTION = """
17
+ # Image Matching WebUI
18
+ This Space demonstrates [Image Matching WebUI](https://github.com/Vincentqyw/image-matching-webui) by vincent qin. Feel free to play with it, or duplicate to run image matching without a queue!
19
+ <br/>
20
+ πŸ”Ž For more details about supported local features and matchers, please refer to https://github.com/Vincentqyw/image-matching-webui
21
+
22
+ πŸš€ All algorithms run on CPU for inference, causing slow speeds and high latency. For faster inference, please download the [source code](https://github.com/Vincentqyw/image-matching-webui) for local deployment.
23
+
24
+ πŸ› Your feedback is valuable to me. Please do not hesitate to report any bugs [here](https://github.com/Vincentqyw/image-matching-webui/issues).
25
+ """
26
+
27
+
28
+ class ImageMatchingApp:
29
+ def __init__(self, server_name="0.0.0.0", server_port=7860, **kwargs):
30
+ self.server_name = server_name
31
+ self.server_port = server_port
32
+ self.config_path = kwargs.get(
33
+ "config", Path(__file__).parent / "config.yaml"
34
+ )
35
+ self.cfg = load_config(self.config_path)
36
+ self.matcher_zoo = get_matcher_zoo(self.cfg["matcher_zoo"])
37
+ # self.ransac_zoo = get_ransac_zoo(self.cfg["ransac_zoo"])
38
+ self.app = None
39
+ self.init_interface()
40
+ # print all the keys
41
+
42
+ def init_interface(self):
43
+ with gr.Blocks() as self.app:
44
+ with gr.Row():
45
+ with gr.Column(scale=1):
46
+ gr.Image(
47
+ str(Path(__file__).parent.parent / "assets/logo.webp"),
48
+ elem_id="logo-img",
49
+ show_label=False,
50
+ show_share_button=False,
51
+ show_download_button=False,
52
+ )
53
+ with gr.Column(scale=3):
54
+ gr.Markdown(DESCRIPTION)
55
+ with gr.Row(equal_height=False):
56
+ with gr.Column():
57
+ with gr.Row():
58
+ matcher_list = gr.Dropdown(
59
+ choices=list(self.matcher_zoo.keys()),
60
+ value="disk+lightglue",
61
+ label="Matching Model",
62
+ interactive=True,
63
+ )
64
+ match_image_src = gr.Radio(
65
+ (
66
+ ["upload", "webcam", "clipboard"]
67
+ if GRADIO_VERSION > "3"
68
+ else ["upload", "webcam", "canvas"]
69
+ ),
70
+ label="Image Source",
71
+ value="upload",
72
+ )
73
+ with gr.Row():
74
+ input_image0 = gr.Image(
75
+ label="Image 0",
76
+ type="numpy",
77
+ image_mode="RGB",
78
+ height=300 if GRADIO_VERSION > "3" else None,
79
+ interactive=True,
80
+ )
81
+ input_image1 = gr.Image(
82
+ label="Image 1",
83
+ type="numpy",
84
+ image_mode="RGB",
85
+ height=300 if GRADIO_VERSION > "3" else None,
86
+ interactive=True,
87
+ )
88
+
89
+ with gr.Row():
90
+ button_reset = gr.Button(value="Reset")
91
+ button_run = gr.Button(
92
+ value="Run Match", variant="primary"
93
+ )
94
+
95
+ with gr.Accordion("Advanced Setting", open=False):
96
+ with gr.Accordion("Matching Setting", open=True):
97
+ with gr.Row():
98
+ match_setting_threshold = gr.Slider(
99
+ minimum=0.0,
100
+ maximum=1,
101
+ step=0.001,
102
+ label="Match thres.",
103
+ value=0.1,
104
+ )
105
+ match_setting_max_features = gr.Slider(
106
+ minimum=10,
107
+ maximum=10000,
108
+ step=10,
109
+ label="Max features",
110
+ value=1000,
111
+ )
112
+ # TODO: add line settings
113
+ with gr.Row():
114
+ detect_keypoints_threshold = gr.Slider(
115
+ minimum=0,
116
+ maximum=1,
117
+ step=0.001,
118
+ label="Keypoint thres.",
119
+ value=0.015,
120
+ )
121
+ detect_line_threshold = gr.Slider(
122
+ minimum=0.1,
123
+ maximum=1,
124
+ step=0.01,
125
+ label="Line thres.",
126
+ value=0.2,
127
+ )
128
+ # matcher_lists = gr.Radio(
129
+ # ["NN-mutual", "Dual-Softmax"],
130
+ # label="Matcher mode",
131
+ # value="NN-mutual",
132
+ # )
133
+ with gr.Accordion("RANSAC Setting", open=True):
134
+ with gr.Row(equal_height=False):
135
+ ransac_method = gr.Dropdown(
136
+ choices=ransac_zoo.keys(),
137
+ value=self.cfg["defaults"]["ransac_method"],
138
+ label="RANSAC Method",
139
+ interactive=True,
140
+ )
141
+ ransac_reproj_threshold = gr.Slider(
142
+ minimum=0.0,
143
+ maximum=12,
144
+ step=0.01,
145
+ label="Ransac Reproj threshold",
146
+ value=8.0,
147
+ )
148
+ ransac_confidence = gr.Slider(
149
+ minimum=0.0,
150
+ maximum=1,
151
+ step=0.00001,
152
+ label="Ransac Confidence",
153
+ value=self.cfg["defaults"]["ransac_confidence"],
154
+ )
155
+ ransac_max_iter = gr.Slider(
156
+ minimum=0.0,
157
+ maximum=100000,
158
+ step=100,
159
+ label="Ransac Iterations",
160
+ value=self.cfg["defaults"]["ransac_max_iter"],
161
+ )
162
+
163
+ with gr.Accordion("Geometry Setting", open=False):
164
+ with gr.Row(equal_height=False):
165
+ choice_estimate_geom = gr.Radio(
166
+ ["Fundamental", "Homography"],
167
+ label="Reconstruct Geometry",
168
+ value=self.cfg["defaults"][
169
+ "setting_geometry"
170
+ ],
171
+ )
172
+
173
+ # collect inputs
174
+ inputs = [
175
+ input_image0,
176
+ input_image1,
177
+ match_setting_threshold,
178
+ match_setting_max_features,
179
+ detect_keypoints_threshold,
180
+ matcher_list,
181
+ ransac_method,
182
+ ransac_reproj_threshold,
183
+ ransac_confidence,
184
+ ransac_max_iter,
185
+ choice_estimate_geom,
186
+ gr.State(self.matcher_zoo),
187
+ ]
188
+
189
+ # Add some examples
190
+ with gr.Row():
191
+ # Example inputs
192
+ gr.Examples(
193
+ examples=gen_examples(),
194
+ inputs=inputs,
195
+ outputs=[],
196
+ fn=run_matching,
197
+ cache_examples=False,
198
+ label=(
199
+ "Examples (click one of the images below to Run"
200
+ " Match)"
201
+ ),
202
+ )
203
+ with gr.Accordion("Open for More!", open=False):
204
+ gr.Markdown(
205
+ f"""
206
+ <h3>Supported Algorithms</h3>
207
+ {", ".join(self.matcher_zoo.keys())}
208
+ """
209
+ )
210
+
211
+ with gr.Column():
212
+ output_keypoints = gr.Image(label="Keypoints", type="numpy")
213
+ output_matches_raw = gr.Image(
214
+ label="Raw Matches", type="numpy"
215
+ )
216
+ output_matches_ransac = gr.Image(
217
+ label="Ransac Matches", type="numpy"
218
+ )
219
+ with gr.Accordion(
220
+ "Open for More: Matches Statistics", open=False
221
+ ):
222
+ matches_result_info = gr.JSON(
223
+ label="Matches Statistics"
224
+ )
225
+ matcher_info = gr.JSON(label="Match info")
226
+
227
+ with gr.Accordion(
228
+ "Open for More: Warped Image", open=False
229
+ ):
230
+ output_wrapped = gr.Image(
231
+ label="Wrapped Pair", type="numpy"
232
+ )
233
+ with gr.Accordion(
234
+ "Open for More: Geometry info", open=False
235
+ ):
236
+ geometry_result = gr.JSON(
237
+ label="Reconstructed Geometry"
238
+ )
239
+
240
+ # callbacks
241
+ match_image_src.change(
242
+ fn=self.ui_change_imagebox,
243
+ inputs=match_image_src,
244
+ outputs=input_image0,
245
+ )
246
+ match_image_src.change(
247
+ fn=self.ui_change_imagebox,
248
+ inputs=match_image_src,
249
+ outputs=input_image1,
250
+ )
251
+
252
+ # collect outputs
253
+ outputs = [
254
+ output_keypoints,
255
+ output_matches_raw,
256
+ output_matches_ransac,
257
+ matches_result_info,
258
+ matcher_info,
259
+ geometry_result,
260
+ output_wrapped,
261
+ ]
262
+ # button callbacks
263
+ button_run.click(
264
+ fn=run_matching, inputs=inputs, outputs=outputs
265
+ )
266
+
267
+ # Reset images
268
+ reset_outputs = [
269
+ input_image0,
270
+ input_image1,
271
+ match_setting_threshold,
272
+ match_setting_max_features,
273
+ detect_keypoints_threshold,
274
+ matcher_list,
275
+ input_image0,
276
+ input_image1,
277
+ match_image_src,
278
+ output_keypoints,
279
+ output_matches_raw,
280
+ output_matches_ransac,
281
+ matches_result_info,
282
+ matcher_info,
283
+ output_wrapped,
284
+ geometry_result,
285
+ ransac_method,
286
+ ransac_reproj_threshold,
287
+ ransac_confidence,
288
+ ransac_max_iter,
289
+ choice_estimate_geom,
290
+ ]
291
+ button_reset.click(
292
+ fn=self.ui_reset_state, inputs=inputs, outputs=reset_outputs
293
+ )
294
+
295
+ # estimate geo
296
+ choice_estimate_geom.change(
297
+ fn=change_estimate_geom,
298
+ inputs=[
299
+ input_image0,
300
+ input_image1,
301
+ geometry_result,
302
+ choice_estimate_geom,
303
+ ],
304
+ outputs=[output_wrapped, geometry_result],
305
+ )
306
+
307
+ def run(self):
308
+ self.app.queue().launch(
309
+ server_name=self.server_name,
310
+ server_port=self.server_port,
311
+ share=False,
312
+ )
313
+
314
+ def ui_change_imagebox(self, choice):
315
+ """
316
+ Updates the image box with the given choice.
317
+
318
+ Args:
319
+ choice (list): The list of image sources to be displayed in the image box.
320
+
321
+ Returns:
322
+ dict: A dictionary containing the updated value, sources, and type for the image box.
323
+ """
324
+ ret_dict = {
325
+ "value": None, # The updated value of the image box
326
+ "__type__": "update", # The type of update for the image box
327
+ }
328
+ if GRADIO_VERSION > "3":
329
+ return {
330
+ **ret_dict,
331
+ "sources": choice, # The list of image sources to be displayed
332
+ }
333
+ else:
334
+ return {
335
+ **ret_dict,
336
+ "source": choice, # The list of image sources to be displayed
337
+ }
338
+
339
+ def ui_reset_state(
340
+ self,
341
+ *args: Any,
342
+ ) -> Tuple[
343
+ Optional[np.ndarray],
344
+ Optional[np.ndarray],
345
+ float,
346
+ int,
347
+ float,
348
+ str,
349
+ Dict[str, Any],
350
+ Dict[str, Any],
351
+ str,
352
+ Optional[np.ndarray],
353
+ Optional[np.ndarray],
354
+ Optional[np.ndarray],
355
+ Dict[str, Any],
356
+ Dict[str, Any],
357
+ Optional[np.ndarray],
358
+ Dict[str, Any],
359
+ str,
360
+ int,
361
+ float,
362
+ int,
363
+ ]:
364
+ """
365
+ Reset the state of the UI.
366
+
367
+ Returns:
368
+ tuple: A tuple containing the initial values for the UI state.
369
+ """
370
+ key: str = list(self.matcher_zoo.keys())[
371
+ 0
372
+ ] # Get the first key from matcher_zoo
373
+ return (
374
+ None, # image0: Optional[np.ndarray]
375
+ None, # image1: Optional[np.ndarray]
376
+ self.cfg["defaults"][
377
+ "match_threshold"
378
+ ], # matching_threshold: float
379
+ self.cfg["defaults"]["max_keypoints"], # max_features: int
380
+ self.cfg["defaults"][
381
+ "keypoint_threshold"
382
+ ], # keypoint_threshold: float
383
+ key, # matcher: str
384
+ self.ui_change_imagebox("upload"), # input image0: Dict[str, Any]
385
+ self.ui_change_imagebox("upload"), # input image1: Dict[str, Any]
386
+ "upload", # match_image_src: str
387
+ None, # keypoints: Optional[np.ndarray]
388
+ None, # raw matches: Optional[np.ndarray]
389
+ None, # ransac matches: Optional[np.ndarray]
390
+ {}, # matches result info: Dict[str, Any]
391
+ {}, # matcher config: Dict[str, Any]
392
+ None, # warped image: Optional[np.ndarray]
393
+ {}, # geometry result: Dict[str, Any]
394
+ self.cfg["defaults"]["ransac_method"], # ransac_method: str
395
+ self.cfg["defaults"][
396
+ "ransac_reproj_threshold"
397
+ ], # ransac_reproj_threshold: float
398
+ self.cfg["defaults"][
399
+ "ransac_confidence"
400
+ ], # ransac_confidence: float
401
+ self.cfg["defaults"]["ransac_max_iter"], # ransac_max_iter: int
402
+ self.cfg["defaults"]["setting_geometry"], # geometry: str
403
+ )
common/config.yaml ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ server:
2
+ name: "0.0.0.0"
3
+ port: 7860
4
+
5
+ defaults:
6
+ setting_threshold: 0.1
7
+ max_keypoints: 2000
8
+ keypoint_threshold: 0.05
9
+ enable_ransac: true
10
+ ransac_method: USAC_MAGSAC
11
+ ransac_reproj_threshold: 8
12
+ ransac_confidence: 0.999
13
+ ransac_max_iter: 10000
14
+ ransac_num_samples: 4
15
+ match_threshold: 0.2
16
+ setting_geometry: Homography
17
+
18
+ matcher_zoo:
19
+ roma:
20
+ matcher: roma
21
+ dense: true
22
+ loftr:
23
+ matcher: loftr
24
+ dense: true
25
+ topicfm:
26
+ matcher: topicfm
27
+ dense: true
28
+ aspanformer:
29
+ matcher: aspanformer
30
+ dense: true
31
+ dedode:
32
+ matcher: Dual-Softmax
33
+ feature: dedode
34
+ dense: false
35
+ superpoint+superglue:
36
+ matcher: superglue
37
+ feature: superpoint_max
38
+ dense: false
39
+ superpoint+lightglue:
40
+ matcher: superpoint-lightglue
41
+ feature: superpoint_max
42
+ dense: false
43
+ disk:
44
+ matcher: NN-mutual
45
+ feature: disk
46
+ dense: false
47
+ disk+dualsoftmax:
48
+ matcher: Dual-Softmax
49
+ feature: disk
50
+ dense: false
51
+ superpoint+dualsoftmax:
52
+ matcher: Dual-Softmax
53
+ feature: superpoint_max
54
+ dense: false
55
+ disk+lightglue:
56
+ matcher: disk-lightglue
57
+ feature: disk
58
+ dense: false
59
+ superpoint+mnn:
60
+ matcher: NN-mutual
61
+ feature: superpoint_max
62
+ dense: false
63
+ sift+sgmnet:
64
+ matcher: sgmnet
65
+ feature: sift
66
+ dense: false
67
+ sosnet:
68
+ matcher: NN-mutual
69
+ feature: sosnet
70
+ dense: false
71
+ hardnet:
72
+ matcher: NN-mutual
73
+ feature: hardnet
74
+ dense: false
75
+ d2net:
76
+ matcher: NN-mutual
77
+ feature: d2net-ss
78
+ dense: false
79
+ rord:
80
+ matcher: NN-mutual
81
+ feature: rord
82
+ dense: false
83
+ alike:
84
+ matcher: NN-mutual
85
+ feature: alike
86
+ dense: false
87
+ lanet:
88
+ matcher: NN-mutual
89
+ feature: lanet
90
+ dense: false
91
+ r2d2:
92
+ matcher: NN-mutual
93
+ feature: r2d2
94
+ dense: false
95
+ darkfeat:
96
+ matcher: NN-mutual
97
+ feature: darkfeat
98
+ dense: false
99
+ sift:
100
+ matcher: NN-mutual
101
+ feature: sift
102
+ dense: false
103
+ gluestick:
104
+ matcher: gluestick
105
+ dense: true
106
+ sold2:
107
+ matcher: sold2
108
+ dense: true
common/utils.py CHANGED
@@ -1,20 +1,27 @@
1
  import os
 
 
2
  import random
3
  import numpy as np
4
- import torch
5
- import cv2
6
  import gradio as gr
7
  from pathlib import Path
8
- from typing import Dict, Any, Optional, Tuple, List, Union
9
  from itertools import combinations
 
10
  from hloc import matchers, extractors, logger
11
  from hloc.utils.base_model import dynamic_load
12
  from hloc import match_dense, match_features, extract_features
13
  from hloc.utils.viz import add_text, plot_keypoints
14
- from .viz import draw_matches, fig2im, plot_images, plot_color_line_matches
 
 
 
 
 
 
15
 
16
  device = "cuda" if torch.cuda.is_available() else "cpu"
17
 
 
18
  DEFAULT_SETTING_THRESHOLD = 0.1
19
  DEFAULT_SETTING_MAX_FEATURES = 2000
20
  DEFAULT_DEFAULT_KEYPOINT_THRESHOLD = 0.01
@@ -27,6 +34,58 @@ DEFAULT_MIN_NUM_MATCHES = 4
27
  DEFAULT_MATCHING_THRESHOLD = 0.2
28
  DEFAULT_SETTING_GEOMETRY = "Homography"
29
  GRADIO_VERSION = gr.__version__.split(".")[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
 
32
  def get_model(match_conf: Dict[str, Any]):
@@ -83,7 +142,7 @@ def gen_examples():
83
  return [pairs[i] for i in selected]
84
 
85
  # image pair path
86
- path = Path(__file__).parent.parent / "datasets/sacre_coeur/mapping"
87
  pairs = gen_images_pairs(str(path), len(example_matchers))
88
  match_setting_threshold = DEFAULT_SETTING_THRESHOLD
89
  match_setting_max_features = DEFAULT_SETTING_MAX_FEATURES
@@ -343,85 +402,6 @@ def change_estimate_geom(
343
  return None, None
344
 
345
 
346
- def display_matches(
347
- pred: Dict[str, np.ndarray], titles: List[str] = [], dpi: int = 300
348
- ) -> Tuple[np.ndarray, int]:
349
- """
350
- Displays the matches between two images.
351
-
352
- Args:
353
- pred: Dictionary containing the original images and the matches.
354
- titles: Optional titles for the plot.
355
- dpi: Resolution of the plot.
356
-
357
- Returns:
358
- The resulting concatenated plot and the number of inliers.
359
- """
360
- img0 = pred["image0_orig"]
361
- img1 = pred["image1_orig"]
362
-
363
- num_inliers = 0
364
- if (
365
- "keypoints0_orig" in pred
366
- and "keypoints1_orig" in pred
367
- and pred["keypoints0_orig"] is not None
368
- and pred["keypoints1_orig"] is not None
369
- ):
370
- mkpts0 = pred["keypoints0_orig"]
371
- mkpts1 = pred["keypoints1_orig"]
372
- num_inliers = len(mkpts0)
373
- if "mconf" in pred:
374
- mconf = pred["mconf"]
375
- else:
376
- mconf = np.ones(len(mkpts0))
377
- fig_mkpts = draw_matches(
378
- mkpts0,
379
- mkpts1,
380
- img0,
381
- img1,
382
- mconf,
383
- dpi=dpi,
384
- titles=titles,
385
- )
386
- fig = fig_mkpts
387
- if (
388
- "line0_orig" in pred
389
- and "line1_orig" in pred
390
- and pred["line0_orig"] is not None
391
- and pred["line1_orig"] is not None
392
- ):
393
- # lines
394
- mtlines0 = pred["line0_orig"]
395
- mtlines1 = pred["line1_orig"]
396
- num_inliers = len(mtlines0)
397
- fig_lines = plot_images(
398
- [img0.squeeze(), img1.squeeze()],
399
- ["Image 0 - matched lines", "Image 1 - matched lines"],
400
- dpi=300,
401
- )
402
- fig_lines = plot_color_line_matches([mtlines0, mtlines1], lw=2)
403
- fig_lines = fig2im(fig_lines)
404
-
405
- # keypoints
406
- mkpts0 = pred.get("line_keypoints0_orig")
407
- mkpts1 = pred.get("line_keypoints1_orig")
408
-
409
- if mkpts0 is not None and mkpts1 is not None:
410
- num_inliers = len(mkpts0)
411
- if "mconf" in pred:
412
- mconf = pred["mconf"]
413
- else:
414
- mconf = np.ones(len(mkpts0))
415
- fig_mkpts = draw_matches(mkpts0, mkpts1, img0, img1, mconf, dpi=300)
416
- fig_lines = cv2.resize(
417
- fig_lines, (fig_mkpts.shape[1], fig_mkpts.shape[0])
418
- )
419
- fig = np.concatenate([fig_mkpts, fig_lines], axis=0)
420
- else:
421
- fig = fig_lines
422
- return fig, num_inliers
423
-
424
-
425
  def run_matching(
426
  image0: np.ndarray,
427
  image1: np.ndarray,
@@ -434,6 +414,7 @@ def run_matching(
434
  ransac_confidence: float = DEFAULT_RANSAC_CONFIDENCE,
435
  ransac_max_iter: int = DEFAULT_RANSAC_MAX_ITER,
436
  choice_estimate_geom: str = DEFAULT_SETTING_GEOMETRY,
 
437
  ) -> Tuple[
438
  np.ndarray,
439
  np.ndarray,
@@ -477,7 +458,7 @@ def run_matching(
477
  output_matches_ransac = None
478
 
479
  model = matcher_zoo[key]
480
- match_conf = model["config"]
481
  # update match config
482
  match_conf["model"]["match_threshold"] = match_threshold
483
  match_conf["model"]["max_keypoints"] = extract_max_keypoints
@@ -490,7 +471,7 @@ def run_matching(
490
  del matcher
491
  extract_conf = None
492
  else:
493
- extract_conf = model["config_feature"]
494
  # update extract config
495
  extract_conf["model"]["max_keypoints"] = extract_max_keypoints
496
  extract_conf["model"]["keypoint_threshold"] = keypoint_threshold
@@ -587,113 +568,3 @@ ransac_zoo = {
587
  "USAC_ACCURATE": cv2.USAC_ACCURATE,
588
  "USAC_PARALLEL": cv2.USAC_PARALLEL,
589
  }
590
-
591
- # Matchers collections
592
- matcher_zoo = {
593
- # 'dedode-sparse': {
594
- # 'config': match_dense.confs['dedode_sparse'],
595
- # 'dense': True # dense mode, we need 2 images
596
- # },
597
- "roma": {"config": match_dense.confs["roma"], "dense": True},
598
- "loftr": {"config": match_dense.confs["loftr"], "dense": True},
599
- "topicfm": {"config": match_dense.confs["topicfm"], "dense": True},
600
- "aspanformer": {"config": match_dense.confs["aspanformer"], "dense": True},
601
- "dedode": {
602
- "config": match_features.confs["Dual-Softmax"],
603
- "config_feature": extract_features.confs["dedode"],
604
- "dense": False,
605
- },
606
- "superpoint+superglue": {
607
- "config": match_features.confs["superglue"],
608
- "config_feature": extract_features.confs["superpoint_max"],
609
- "dense": False,
610
- },
611
- "superpoint+lightglue": {
612
- "config": match_features.confs["superpoint-lightglue"],
613
- "config_feature": extract_features.confs["superpoint_max"],
614
- "dense": False,
615
- },
616
- "disk": {
617
- "config": match_features.confs["NN-mutual"],
618
- "config_feature": extract_features.confs["disk"],
619
- "dense": False,
620
- },
621
- "disk+dualsoftmax": {
622
- "config": match_features.confs["Dual-Softmax"],
623
- "config_feature": extract_features.confs["disk"],
624
- "dense": False,
625
- },
626
- "superpoint+dualsoftmax": {
627
- "config": match_features.confs["Dual-Softmax"],
628
- "config_feature": extract_features.confs["superpoint_max"],
629
- "dense": False,
630
- },
631
- "disk+lightglue": {
632
- "config": match_features.confs["disk-lightglue"],
633
- "config_feature": extract_features.confs["disk"],
634
- "dense": False,
635
- },
636
- "superpoint+mnn": {
637
- "config": match_features.confs["NN-mutual"],
638
- "config_feature": extract_features.confs["superpoint_max"],
639
- "dense": False,
640
- },
641
- "sift+sgmnet": {
642
- "config": match_features.confs["sgmnet"],
643
- "config_feature": extract_features.confs["sift"],
644
- "dense": False,
645
- },
646
- "sosnet": {
647
- "config": match_features.confs["NN-mutual"],
648
- "config_feature": extract_features.confs["sosnet"],
649
- "dense": False,
650
- },
651
- "hardnet": {
652
- "config": match_features.confs["NN-mutual"],
653
- "config_feature": extract_features.confs["hardnet"],
654
- "dense": False,
655
- },
656
- "d2net": {
657
- "config": match_features.confs["NN-mutual"],
658
- "config_feature": extract_features.confs["d2net-ss"],
659
- "dense": False,
660
- },
661
- "rord": {
662
- "config": match_features.confs["NN-mutual"],
663
- "config_feature": extract_features.confs["rord"],
664
- "dense": False,
665
- },
666
- # "d2net-ms": {
667
- # "config": match_features.confs["NN-mutual"],
668
- # "config_feature": extract_features.confs["d2net-ms"],
669
- # "dense": False,
670
- # },
671
- "alike": {
672
- "config": match_features.confs["NN-mutual"],
673
- "config_feature": extract_features.confs["alike"],
674
- "dense": False,
675
- },
676
- "lanet": {
677
- "config": match_features.confs["NN-mutual"],
678
- "config_feature": extract_features.confs["lanet"],
679
- "dense": False,
680
- },
681
- "r2d2": {
682
- "config": match_features.confs["NN-mutual"],
683
- "config_feature": extract_features.confs["r2d2"],
684
- "dense": False,
685
- },
686
- "darkfeat": {
687
- "config": match_features.confs["NN-mutual"],
688
- "config_feature": extract_features.confs["darkfeat"],
689
- "dense": False,
690
- },
691
- "sift": {
692
- "config": match_features.confs["NN-mutual"],
693
- "config_feature": extract_features.confs["sift"],
694
- "dense": False,
695
- },
696
- "gluestick": {"config": match_dense.confs["gluestick"], "dense": True},
697
- "sold2": {"config": match_dense.confs["sold2"], "dense": True},
698
- # "DKMv3": {"config": match_dense.confs["dkm"], "dense": True},
699
- }
 
1
  import os
2
+ import cv2
3
+ import torch
4
  import random
5
  import numpy as np
 
 
6
  import gradio as gr
7
  from pathlib import Path
 
8
  from itertools import combinations
9
+ from typing import Callable, Dict, Any, Optional, Tuple, List, Union
10
  from hloc import matchers, extractors, logger
11
  from hloc.utils.base_model import dynamic_load
12
  from hloc import match_dense, match_features, extract_features
13
  from hloc.utils.viz import add_text, plot_keypoints
14
+ from .viz import (
15
+ draw_matches,
16
+ fig2im,
17
+ plot_images,
18
+ display_matches,
19
+ plot_color_line_matches,
20
+ )
21
 
22
  device = "cuda" if torch.cuda.is_available() else "cpu"
23
 
24
+ ROOT = Path(__file__).parent.parent
25
  DEFAULT_SETTING_THRESHOLD = 0.1
26
  DEFAULT_SETTING_MAX_FEATURES = 2000
27
  DEFAULT_DEFAULT_KEYPOINT_THRESHOLD = 0.01
 
34
  DEFAULT_MATCHING_THRESHOLD = 0.2
35
  DEFAULT_SETTING_GEOMETRY = "Homography"
36
  GRADIO_VERSION = gr.__version__.split(".")[0]
37
+ MATCHER_ZOO = None
38
+
39
+
40
+ def load_config(config_name: str) -> Dict[str, Any]:
41
+ """
42
+ Load a YAML configuration file.
43
+
44
+ Args:
45
+ config_name: The path to the YAML configuration file.
46
+
47
+ Returns:
48
+ The configuration dictionary, with string keys and arbitrary values.
49
+ """
50
+ import yaml
51
+
52
+ with open(config_name, "r") as stream:
53
+ try:
54
+ config: Dict[str, Any] = yaml.safe_load(stream)
55
+ except yaml.YAMLError as exc:
56
+ logger.error(exc)
57
+ return config
58
+
59
+
60
+ def get_matcher_zoo(
61
+ matcher_zoo: Dict[str, Dict[str, Union[str, bool]]]
62
+ ) -> Dict[str, Dict[str, Union[Callable, bool]]]:
63
+ """
64
+ Restore matcher configurations from a dictionary.
65
+
66
+ Args:
67
+ matcher_zoo: A dictionary with the matcher configurations,
68
+ where the configuration is a dictionary as loaded from a YAML file.
69
+
70
+ Returns:
71
+ A dictionary with the matcher configurations, where the configuration is
72
+ a function or a function instead of a string.
73
+ """
74
+ matcher_zoo_restored = {}
75
+ for k, v in matcher_zoo.items():
76
+ dense = v["dense"]
77
+ if dense:
78
+ matcher_zoo_restored[k] = {
79
+ "matcher": match_dense.confs.get(v["matcher"]),
80
+ "dense": dense,
81
+ }
82
+ else:
83
+ matcher_zoo_restored[k] = {
84
+ "feature": extract_features.confs.get(v["feature"]),
85
+ "matcher": match_features.confs.get(v["matcher"]),
86
+ "dense": dense,
87
+ }
88
+ return matcher_zoo_restored
89
 
90
 
91
  def get_model(match_conf: Dict[str, Any]):
 
142
  return [pairs[i] for i in selected]
143
 
144
  # image pair path
145
+ path = ROOT / "datasets/sacre_coeur/mapping"
146
  pairs = gen_images_pairs(str(path), len(example_matchers))
147
  match_setting_threshold = DEFAULT_SETTING_THRESHOLD
148
  match_setting_max_features = DEFAULT_SETTING_MAX_FEATURES
 
402
  return None, None
403
 
404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  def run_matching(
406
  image0: np.ndarray,
407
  image1: np.ndarray,
 
414
  ransac_confidence: float = DEFAULT_RANSAC_CONFIDENCE,
415
  ransac_max_iter: int = DEFAULT_RANSAC_MAX_ITER,
416
  choice_estimate_geom: str = DEFAULT_SETTING_GEOMETRY,
417
+ matcher_zoo: Dict[str, Any] = None,
418
  ) -> Tuple[
419
  np.ndarray,
420
  np.ndarray,
 
458
  output_matches_ransac = None
459
 
460
  model = matcher_zoo[key]
461
+ match_conf = model["matcher"]
462
  # update match config
463
  match_conf["model"]["match_threshold"] = match_threshold
464
  match_conf["model"]["max_keypoints"] = extract_max_keypoints
 
471
  del matcher
472
  extract_conf = None
473
  else:
474
+ extract_conf = model["feature"]
475
  # update extract config
476
  extract_conf["model"]["max_keypoints"] = extract_max_keypoints
477
  extract_conf["model"]["keypoint_threshold"] = keypoint_threshold
 
568
  "USAC_ACCURATE": cv2.USAC_ACCURATE,
569
  "USAC_PARALLEL": cv2.USAC_PARALLEL,
570
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
common/viz.py CHANGED
@@ -367,3 +367,82 @@ def draw_image_pairs(
367
  plt.close()
368
  else:
369
  return fig2im(fig)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  plt.close()
368
  else:
369
  return fig2im(fig)
370
+
371
+
372
+ def display_matches(
373
+ pred: Dict[str, np.ndarray], titles: List[str] = [], dpi: int = 300
374
+ ) -> Tuple[np.ndarray, int]:
375
+ """
376
+ Displays the matches between two images.
377
+
378
+ Args:
379
+ pred: Dictionary containing the original images and the matches.
380
+ titles: Optional titles for the plot.
381
+ dpi: Resolution of the plot.
382
+
383
+ Returns:
384
+ The resulting concatenated plot and the number of inliers.
385
+ """
386
+ img0 = pred["image0_orig"]
387
+ img1 = pred["image1_orig"]
388
+
389
+ num_inliers = 0
390
+ if (
391
+ "keypoints0_orig" in pred
392
+ and "keypoints1_orig" in pred
393
+ and pred["keypoints0_orig"] is not None
394
+ and pred["keypoints1_orig"] is not None
395
+ ):
396
+ mkpts0 = pred["keypoints0_orig"]
397
+ mkpts1 = pred["keypoints1_orig"]
398
+ num_inliers = len(mkpts0)
399
+ if "mconf" in pred:
400
+ mconf = pred["mconf"]
401
+ else:
402
+ mconf = np.ones(len(mkpts0))
403
+ fig_mkpts = draw_matches(
404
+ mkpts0,
405
+ mkpts1,
406
+ img0,
407
+ img1,
408
+ mconf,
409
+ dpi=dpi,
410
+ titles=titles,
411
+ )
412
+ fig = fig_mkpts
413
+ if (
414
+ "line0_orig" in pred
415
+ and "line1_orig" in pred
416
+ and pred["line0_orig"] is not None
417
+ and pred["line1_orig"] is not None
418
+ ):
419
+ # lines
420
+ mtlines0 = pred["line0_orig"]
421
+ mtlines1 = pred["line1_orig"]
422
+ num_inliers = len(mtlines0)
423
+ fig_lines = plot_images(
424
+ [img0.squeeze(), img1.squeeze()],
425
+ ["Image 0 - matched lines", "Image 1 - matched lines"],
426
+ dpi=300,
427
+ )
428
+ fig_lines = plot_color_line_matches([mtlines0, mtlines1], lw=2)
429
+ fig_lines = fig2im(fig_lines)
430
+
431
+ # keypoints
432
+ mkpts0 = pred.get("line_keypoints0_orig")
433
+ mkpts1 = pred.get("line_keypoints1_orig")
434
+
435
+ if mkpts0 is not None and mkpts1 is not None:
436
+ num_inliers = len(mkpts0)
437
+ if "mconf" in pred:
438
+ mconf = pred["mconf"]
439
+ else:
440
+ mconf = np.ones(len(mkpts0))
441
+ fig_mkpts = draw_matches(mkpts0, mkpts1, img0, img1, mconf, dpi=300)
442
+ fig_lines = cv2.resize(
443
+ fig_lines, (fig_mkpts.shape[1], fig_mkpts.shape[0])
444
+ )
445
+ fig = np.concatenate([fig_mkpts, fig_lines], axis=0)
446
+ else:
447
+ fig = fig_lines
448
+ return fig, num_inliers