Skip to content

Commit e7353a2

Browse files
Manuel Lopez Antequerafacebook-github-bot
authored andcommitted
Annotation UI: New features + docs (#744)
Summary: - Support for annotation on CAD models (.FBX files) - "Native" support for rigs: rig definition files are used to define rigs, instead of manually defining them using a command-line argument. - Finally some documentation Pull Request resolved: #744 Reviewed By: paulinus Differential Revision: D28181412 Pulled By: mlopezantequera fbshipit-source-id: e69b9755f57fd46fb996bbcf720fd4c9a1a054f2
1 parent 398fe61 commit e7353a2

21 files changed

Lines changed: 1763 additions & 292 deletions

annotation_gui_gcp/GUI.py

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# pyre-fixme[16]: Module `matplotlib` has no attribute `use`.
1313
matplotlib.use("TkAgg")
1414

15+
from .cad_viewer.cad_view import CadView
1516
from .image_sequence_view import ImageSequenceView
1617
from .orthophoto_view import OrthoPhotoView
1718

@@ -20,23 +21,29 @@
2021

2122
class Gui:
2223
def __init__(
23-
self, parent, gcp_manager, image_manager, sequence_groups=(), ortho_paths=()
24+
self,
25+
parent,
26+
gcp_manager,
27+
image_manager,
28+
rig_groups=None,
29+
ortho_paths=(),
30+
cad_paths=(),
2431
):
2532
self.parent = parent
2633
self.gcp_manager = gcp_manager
2734
self.image_manager = image_manager
2835
self.curr_point = None
2936
self.quick_save_filename = None
3037
self.shot_std = {}
31-
self.sequence_groups = sequence_groups
38+
self.rig_groups = rig_groups if rig_groups else {}
3239
self.path = self.gcp_manager.path
3340

3441
parent.bind_all("q", lambda event: self.go_to_worst_gcp())
3542
parent.bind_all("z", lambda event: self.toggle_zoom_all_views())
3643
parent.bind_all("x", lambda event: self.toggle_sticky_zoom())
3744
parent.bind_all("a", lambda event: self.go_to_current_gcp())
38-
self.get_reconstruction_options()
39-
self.create_ui(ortho_paths)
45+
self.reconstruction_options = self.get_reconstruction_options()
46+
self.create_ui(ortho_paths, cad_paths)
4047
parent.lift()
4148

4249
p_default_gcp = self.path + "/ground_control_points.json"
@@ -46,9 +53,8 @@ def __init__(
4653

4754
def get_reconstruction_options(self):
4855
p_recs = self.path + "/reconstruction.json"
49-
print(p_recs)
5056
if not os.path.exists(p_recs):
51-
return {}
57+
return ["NONE", "NONE"]
5258
data = dataset.DataSet(self.path)
5359
recs = data.load_reconstruction()
5460
options = []
@@ -61,30 +67,38 @@ def get_reconstruction_options(self):
6167
)
6268
options.append(str_repr)
6369
options.append("None (3d-to-2d)")
64-
self.reconstruction_options = options
70+
return options
6571

66-
def create_ui(self, ortho_paths):
72+
def create_ui(self, ortho_paths, cad_paths):
6773
tools_frame = tk.Frame(self.parent)
6874
tools_frame.pack(side="left", expand=0, fill=tk.Y)
6975
self.create_tools(tools_frame)
70-
self.create_sequence_views(show_ortho_track=len(ortho_paths) > 0)
76+
has_views_that_need_tracking = len(ortho_paths) > 0 or len(cad_paths) > 0
77+
self.create_sequence_views(show_track_checkbox=has_views_that_need_tracking)
7178
self.ortho_views = []
7279
if ortho_paths:
7380
v = self.sequence_views[0]
7481
k = v.current_image
7582
latlon = v.latlons[k]
7683
self.create_ortho_views(ortho_paths, latlon["lat"], latlon["lon"])
84+
85+
self.cad_views = [
86+
CadView(self, cad_path, 5000 + ix) for ix, cad_path in enumerate(cad_paths)
87+
]
88+
7789
self.parent.update_idletasks()
7890
# self.arrange_ui_onerow()
7991

8092
def rec_ix_changed(self, *args):
8193
# Load analysis for the new reconstruction pair if it exists
82-
ix_a = self.reconstruction_options.index(self.rec_a.get())
83-
ix_b = self.reconstruction_options.index(self.rec_b.get())
84-
if ix_b == len(self.reconstruction_options) - 1:
85-
ix_b = None
86-
print(f"Loading analysis results for {self.rec_a.get()} vs {self.rec_b.get()}")
87-
self.load_analysis_results(ix_a, ix_b)
94+
self.ix_a = self.reconstruction_options.index(self.rec_a.get())
95+
self.ix_b = self.reconstruction_options.index(self.rec_b.get())
96+
if self.ix_b == len(self.reconstruction_options) - 1:
97+
self.ix_b = None
98+
print(
99+
f"Loading analysis results for #{self.ix_a}:{self.rec_a.get()} vs #{self.ix_b}:{self.rec_b.get()}"
100+
)
101+
self.load_analysis_results(self.ix_a, self.ix_b)
88102
for view in self.sequence_views:
89103
view.populate_image_list()
90104

@@ -144,13 +158,15 @@ def create_tools(self, parent):
144158
analysis_frame.pack(side="top")
145159

146160
options = self.reconstruction_options
161+
self.ix_a = 0
147162
self.rec_a = tk.StringVar(parent)
148163
self.rec_a.set(options[0])
149164
self.rec_a.trace("w", self.rec_ix_changed)
150165
w = tk.OptionMenu(analysis_frame, self.rec_a, *options[:-1])
151166
w.pack(side="top", fill=tk.X)
152167
w.config(width=width)
153168

169+
self.ix_b = None
154170
self.rec_b = tk.StringVar(parent)
155171
self.rec_b.set(options[1])
156172
self.rec_b.trace("w", self.rec_ix_changed)
@@ -161,7 +177,11 @@ def create_tools(self, parent):
161177
analysis_buttons_frame = tk.Frame(analysis_frame)
162178
analysis_buttons_frame.pack(side="top")
163179
button = tk.Button(
164-
analysis_buttons_frame, text="Fast", command=self.analyze_fast
180+
analysis_buttons_frame, text="Rigid", command=self.analyze_rigid
181+
)
182+
button.pack(side="left")
183+
button = tk.Button(
184+
analysis_buttons_frame, text="Flex", command=self.analyze_flex
165185
)
166186
button.pack(side="left")
167187
button = tk.Button(analysis_buttons_frame, text="Full", command=self.analyze)
@@ -183,29 +203,26 @@ def create_ortho_views(self, ortho_paths, lat, lon):
183203
ortho_p,
184204
init_lat=lat,
185205
init_lon=lon,
186-
is_geo_reference=ortho_p is ortho_paths[0],
206+
is_the_geo_reference=ortho_p is ortho_paths[0],
187207
)
188208
self.ortho_views.append(v)
189209

190-
def create_sequence_views(self, show_ortho_track):
210+
def create_sequence_views(self, show_track_checkbox):
191211
self.sequence_views = []
192212
for sequence_key, image_keys in self.image_manager.seqs.items():
193-
v = ImageSequenceView(self, sequence_key, image_keys, show_ortho_track)
213+
v = ImageSequenceView(self, sequence_key, image_keys, show_track_checkbox)
194214
self.sequence_views.append(v)
195215

196-
def analyze_fast(self):
197-
self.analyze(fast=True)
216+
def analyze_rigid(self):
217+
self.analyze(rigid=True, covariance=False)
198218

199-
def analyze(self, fast=False):
219+
def analyze_flex(self):
220+
self.analyze(rigid=False, covariance=False)
221+
222+
def analyze(self, rigid=False, covariance=True):
200223
t = time.time() - os.path.getmtime(self.path + "/ground_control_points.json")
201224
ix_a = self.reconstruction_options.index(self.rec_a.get())
202225
ix_b = self.reconstruction_options.index(self.rec_b.get())
203-
if ix_a == ix_b:
204-
print(
205-
"Please select different reconstructions in the drop-down menus"
206-
" before running the analysis"
207-
)
208-
return
209226
if t > 30:
210227
print(
211228
"Please save to ground_control_points.json before running the analysis"
@@ -224,8 +241,11 @@ def analyze(self, fast=False):
224241
else:
225242
ix_b = None
226243

227-
if fast:
228-
args.extend(["--fast"])
244+
if rigid:
245+
args.extend(["--rigid"])
246+
247+
if covariance:
248+
args.extend(["--covariance"])
229249

230250
# Call the run_ba script
231251
subprocess.run(args)
@@ -261,15 +281,15 @@ def load_gcps(self, filename=None):
261281
return
262282
self.quick_save_filename = filename
263283
self.gcp_manager.load_from_file(filename)
264-
for view in self.sequence_views:
284+
for view in self.sequence_views + self.ortho_views + self.cad_views:
265285
view.display_points()
266286
view.populate_image_list()
267287
self.populate_gcp_list()
268288

269289
def add_gcp(self):
270-
self.curr_point = self.gcp_manager.add_point()
290+
new_gcp = self.gcp_manager.add_point()
271291
self.populate_gcp_list()
272-
return self.curr_point
292+
self.update_active_gcp(new_gcp)
273293

274294
def toggle_sticky_zoom(self):
275295
if self.sticky_zoom.get():
@@ -321,31 +341,27 @@ def remove_gcp(self):
321341
to_be_removed_point = self.curr_point
322342
if not to_be_removed_point:
323343
return
324-
self.curr_point = None
325-
326344
self.gcp_manager.remove_gcp(to_be_removed_point)
327-
for view in self.sequence_views:
345+
self.populate_gcp_list()
346+
self.update_active_gcp(None)
347+
348+
def update_active_gcp(self, new_active_gcp):
349+
self.curr_point = new_active_gcp
350+
for view in self.sequence_views + self.ortho_views + self.cad_views:
328351
view.display_points()
352+
if self.curr_point:
353+
view.highlight_gcp_reprojection(self.curr_point, zoom=False)
329354

330-
self.populate_gcp_list()
355+
self.update_gcp_list_highlight()
331356

332357
def onclick_gcp_list(self, event):
333358
widget = event.widget
334359
selection = widget.curselection()
335360
if not selection:
336361
return
337362
value = widget.get(int(selection[0]))
338-
if value == "none":
339-
self.curr_point = None
340-
else:
341-
self.curr_point = value.split(" ")[1]
342-
343-
for view in self.sequence_views:
344-
view.display_points()
345-
if self.curr_point:
346-
view.highlight_gcp_reprojection(self.curr_point, zoom=False)
347-
348-
self.update_gcp_list_highlight()
363+
curr_point = value.split(" ")[1] if value != "none" else None
364+
self.update_active_gcp(curr_point)
349365

350366
def save_gcps(self):
351367
if self.quick_save_filename is None:
@@ -393,7 +409,7 @@ def go_to_worst_gcp(self):
393409
if len(self.gcp_manager.gcp_reprojections) == 0:
394410
print("No GCP reprojections available. Can't jump to worst GCP")
395411
return
396-
worst_gcp = self.gcp_manager.compute_gcp_errors()[0]
412+
worst_gcp = self.gcp_manager.get_worst_gcp()
397413
if worst_gcp is None:
398414
return
399415

@@ -419,5 +435,5 @@ def clear_latlon_sources(self, view):
419435
v.is_latlon_source.set(False)
420436

421437
def refocus_overhead_views(self, lat, lon):
422-
for view in self.ortho_views:
438+
for view in self.ortho_views + self.cad_views:
423439
view.refocus(lat, lon)

0 commit comments

Comments
 (0)