Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat_annotating_videos #711

Open
wants to merge 74 commits into
base: feat_annotation_ui
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
61d58fd
New features for the annotation UI
Sep 16, 2020
7297670
Summary of analysis, better 'sticky' zoom behavior.
Sep 21, 2020
d653a90
Add 'group by reconstruction' arg
Sep 25, 2020
be62ac3
fix: copy shot metadata
paulinus Sep 25, 2020
e7ec8e4
chore: simplify shot copy
paulinus Sep 25, 2020
33b2297
chore: add sequence_database.json to berlin dataset
Sep 30, 2020
a2915d9
feat: better summary of run_ba.py in terminal
Oct 5, 2020
15f8614
feat: minor improvements to annotation UI:
Oct 5, 2020
5b58985
feat: counter for the annotations of each GCP
Oct 5, 2020
09150ef
fix: jump to gcp now works without 'Analyze' being pressed
Oct 5, 2020
b12d0ea
feat: resect images with enough GCP annotations
paulinus Oct 6, 2020
681136c
chore: only run one-way BA for 3d-to-2d analysis.
Oct 6, 2020
6713620
misc fixes:
Oct 6, 2020
4a39128
Merge branch 'master' into feat_annotation_ui_resect
Oct 8, 2020
66667bf
Merge branch 'master' into feat_annotation_ui_resect
Oct 9, 2020
5323b74
fix: 'Auto GCP' works on all reconstructions
Oct 9, 2020
5edd49b
fix: fix cameras during 3d-to-2d bundle adjustment
Oct 9, 2020
c883f77
Merge branch 'master' into feat_annotation_ui
Oct 13, 2020
ff66b1d
feat: preload images with multiple processes
Oct 13, 2020
b13db83
feat: Improved text labels in annotation UI
Oct 13, 2020
3c6841c
fix: no more 'jumpy' frame lists.
Oct 13, 2020
573c48c
feat: Always display reprojection for selected GCP
Oct 13, 2020
63b6da8
Merge remote-tracking branch 'origin/master' into feat_annotation_ui
Oct 14, 2020
2b3a25d
feat: run_ba saves the number of localized images
Oct 14, 2020
f713d12
chore: Refactor the annotation UI
Oct 15, 2020
298ae76
feat: Orthophoto views
Oct 16, 2020
1116516
feat: Propose ortho views based on GPS
Oct 19, 2020
986564d
fix: display annotation counts on start
Oct 19, 2020
39a8576
Merge branch 'master' into feat_annotation_ui
Oct 19, 2020
7d43b3d
chore: formatting of run_ba.py
Oct 20, 2020
21f00f6
feat: Allow picking the reconstructions to analyze
Oct 20, 2020
444b670
chore: docs and output text
Oct 20, 2020
282dacc
chore: moved pixel rescaling to parent View
Oct 21, 2020
35b8221
fix: convert auto-gcps from numpy to list
Nov 6, 2020
813e0fa
feat: split same-reconstruction sequences in views
Nov 11, 2020
d0eaddd
fix: crash if groups_from_sequence_database={}
Nov 18, 2020
532d8b5
feat: Images can be rotated in the UI
Nov 20, 2020
9c1abc6
feat: arg for strict checking of missing files
Nov 30, 2020
6495c76
fix: sort by filename as well
Dec 1, 2020
fa989da
Merge remote-tracking branch 'origin/master' into feat_annotation_ui
Dec 2, 2020
e0ad036
fix: load replaces instead of merging
Dec 2, 2020
1003051
feat: fast analysis mode (skip BA)
Dec 2, 2020
5a9fa35
fix: gracefully ignore missing shots_std file
Dec 2, 2020
95c1903
feat: 'fast' analysis will re-use bundle-adjusted reconstructions
Dec 3, 2020
eaa512b
fix: orthophoto annotations were wrongly saved
Dec 4, 2020
7e7bb19
fix: better search for candidate orthophotos
Dec 7, 2020
d04b487
feat: add orthophoto coverage cache
Dec 7, 2020
e170b05
fix: warn if GCP doesn't have enough annotations to triangulate
Dec 7, 2020
d019131
fix: always keep at least 2 reconstructions
Jan 7, 2021
3c31b59
save GPC file also with the directory name
Jan 8, 2021
12c025d
don't initialize window positions
Jan 12, 2021
2510f22
Save analysis metrics separately for each pair
Jan 19, 2021
6a9b1d4
build: use c++14
paulinus Jan 19, 2021
c1a6186
use sys.exit()
Jan 20, 2021
030e37d
chore: clearer error messages
Jan 28, 2021
8a54482
Merge remote-tracking branch 'origin/master' into feat_annotation_ui
Jan 28, 2021
a7a8293
save results per rec. pair + simple GCP ids
Jan 28, 2021
cced2de
Merge remote-tracking branch 'origin/master' into feat_annotation_ui
Feb 4, 2021
3f5f882
feat: more informative message when jumping to worst GCP
Feb 4, 2021
abfa80c
fix: remove reprojections after confirming
Feb 4, 2021
1a78452
fix: UI launches without reconstruction.json
Feb 8, 2021
4a1254f
fix: UI launches without reconstruction.json
Feb 8, 2021
a76fd4b
fix: build on fedora
Feb 11, 2021
17420cd
fix: missing rasterio requirement for UI
Feb 11, 2021
4ec6257
Read sequences from different folders
Feb 18, 2021
4635670
Different frame rates for different sequences
Feb 21, 2021
6d0c7cf
skip_frames moved to json
Feb 22, 2021
e96a3d0
networkx 1.11 won't work with python3.9
Feb 23, 2021
3da3ae0
Merge branch 'master' into feat_annotation_ui
Feb 24, 2021
881ac0f
Merge pull request #1 from mapillary/feat_annotation_ui
YonatanSimson Feb 24, 2021
9d4a6cd
Adding sequence graph
Feb 25, 2021
339bfac
Code clean up
Feb 25, 2021
39f71e0
Removing print
Feb 25, 2021
f23d3ae
Improving the plot
Feb 28, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions annotation_gui_gcp/GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

matplotlib.use("TkAgg")

from .image_sequence_view import ImageSequenceView
from .orthophoto_view import OrthoPhotoView
from image_sequence_view import ImageSequenceView
from orthophoto_view import OrthoPhotoView

FONT = "TkFixedFont"

Expand All @@ -34,7 +34,7 @@ def __init__(
master.bind_all("z", lambda event: self.toggle_zoom_all_views())
master.bind_all("x", lambda event: self.toggle_sticky_zoom())
master.bind_all("a", lambda event: self.go_to_current_gcp())
self.get_reconstruction_options()
self.reconstruction_options = self.get_reconstruction_options()
self.create_ui(ortho_paths)
master.lift()

Expand All @@ -47,7 +47,7 @@ def get_reconstruction_options(self):
p_recs = self.path + "/reconstruction.json"
print(p_recs)
if not os.path.exists(p_recs):
return {}
return ["NONE", "NONE"]
data = dataset.DataSet(self.path)
recs = data.load_reconstruction()
options = []
Expand All @@ -60,7 +60,7 @@ def get_reconstruction_options(self):
)
options.append(str_repr)
options.append("None (3d-to-2d)")
self.reconstruction_options = options
return options

def create_ui(self, ortho_paths):
tools_frame = tk.Frame(self.master)
Expand Down Expand Up @@ -392,7 +392,7 @@ def go_to_worst_gcp(self):
if len(self.gcp_manager.gcp_reprojections) == 0:
print("No GCP reprojections available. Can't jump to worst GCP")
return
worst_gcp = self.gcp_manager.compute_gcp_errors()[0]
worst_gcp = self.gcp_manager.get_worst_gcp()
if worst_gcp is None:
return

Expand Down
Empty file added annotation_gui_gcp/__init__.py
Empty file.
18 changes: 7 additions & 11 deletions annotation_gui_gcp/gcp_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ def load_from_file(self, file_path):
self.points[point["id"]] = point["observations"]
latlon = point.get("position")
if latlon:
if "altitude" in latlon:
raise NotImplementedError("Not supported: altitude in GCPs")
self.latlons[point["id"]] = latlon

def write_to_file(self, filename):
Expand Down Expand Up @@ -87,26 +85,24 @@ def add_point_observation(self, point_id, shot_id, projection, latlon=None):
}
)

def compute_gcp_errors(self):
error_avg = {}
def get_worst_gcp(self):
worst_gcp_error = 0
worst_gcp = None
shot_worst_gcp = None
for gcp_id in self.points:
error_avg[gcp_id] = 0
for gcp_id in self.gcp_reprojections:
if gcp_id not in self.points:
continue
for shot_id in self.gcp_reprojections[gcp_id]:
err = self.gcp_reprojections[gcp_id][shot_id]["error"]
error_avg[gcp_id] += err
if err > worst_gcp_error:
worst_gcp_error = err
shot_worst_gcp = shot_id
worst_gcp = gcp_id
error_avg[gcp_id] /= len(self.gcp_reprojections[gcp_id])

return worst_gcp, shot_worst_gcp, worst_gcp_error, error_avg
errors_worst_gcp = [
x["error"] for x in self.gcp_reprojections[worst_gcp].values()
]
n = len(errors_worst_gcp)
print(f"Worst GCP: {worst_gcp} unconfirmed in {n} images")
return worst_gcp

def shot_with_max_gcp_error(self, image_keys, gcp):
# Return they key with most reprojection error for this GCP
Expand Down
3 changes: 3 additions & 0 deletions annotation_gui_gcp/image_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def load_image(path):
new_w = int(round(rgb.size[0] / scale))
new_h = int(round(rgb.size[1] / scale))
rgb = rgb.resize((new_w, new_h), resample=Image.BILINEAR)

if rgb.mode == 'L':
rgb = rgb.convert("RGB")
# Matplotlib will transform to rgba when plotting
return _rgb_to_rgba(np.asarray(rgb))

Expand Down
4 changes: 2 additions & 2 deletions annotation_gui_gcp/image_sequence_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import numpy as np
from matplotlib import pyplot as plt

from .geometry import get_all_track_observations, get_tracks_visible_in_image
from .view import View
from geometry import get_all_track_observations, get_tracks_visible_in_image
from view import View


class ImageSequenceView(View):
Expand Down
6 changes: 3 additions & 3 deletions annotation_gui_gcp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

from opensfm import dataset, io

from . import GUI
from .gcp_manager import GroundControlPointManager
from .image_manager import ImageManager
import GUI
from gcp_manager import GroundControlPointManager
from image_manager import ImageManager


def parse_args():
Expand Down
4 changes: 2 additions & 2 deletions annotation_gui_gcp/orthophoto_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import rasterio.warp
from opensfm import features

from .orthophoto_manager import OrthoPhotoManager
from .view import View
from orthophoto_manager import OrthoPhotoManager
from view import View


class OrthoPhotoView(View):
Expand Down
1 change: 1 addition & 0 deletions annotation_gui_gcp/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
matplotlib
rasterio
1 change: 1 addition & 0 deletions bin/opensfm
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ else
PYTHON=python
fi

echo "${PYTHON} ${DIR}/opensfm_main.py $@"
"$PYTHON" "$DIR"/opensfm_main.py "$@"
8 changes: 5 additions & 3 deletions bin/opensfm_run_all
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
"$DIR"/opensfm match_features "$1"
"$DIR"/opensfm create_tracks "$1"
"$DIR"/opensfm reconstruct "$1"
"$DIR"/opensfm mesh "$1"
"$DIR"/opensfm undistort "$1"
"$DIR"/opensfm compute_depthmaps "$1"
# "$DIR"/opensfm mesh "$1"
# "$DIR"/opensfm undistort "$1"
# "$DIR"/opensfm compute_depthmaps "$1"
"$DIR"/opensfm compute_statistics "$1"
"$DIR"/opensfm export_report "$1"
1 change: 1 addition & 0 deletions opensfm/actions/compute_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def run_dataset(data):

stats_dict = stats.compute_all_statistics(data, tracks_manager, reconstructions)

stats.save_sequencegraph(data, reconstructions, output_path)
stats.save_residual_grids(data, tracks_manager, reconstructions, output_path)
stats.save_matchgraph(data, tracks_manager, reconstructions, output_path)
stats.save_heatmap(data, tracks_manager, reconstructions, output_path)
Expand Down
4 changes: 2 additions & 2 deletions opensfm/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,8 +488,8 @@ def invent_reference_lla(self, images=None):

if not wlat and not wlon:
for gcp in self.load_ground_control_points_impl(None):
lat += gcp.lla["latitude"]
lon += gcp.lla["longitude"]
lat += gcp.lla.get("latitude", 0.)
lon += gcp.lla.get("longitude", 0.)
wlat += 1
wlon += 1

Expand Down
58 changes: 57 additions & 1 deletion opensfm/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import math
import os
import statistics
from collections import defaultdict
from collections import defaultdict, OrderedDict
from functools import lru_cache

import matplotlib.cm as cm
Expand Down Expand Up @@ -380,6 +380,57 @@ def save_matchgraph(data, tracks_manager, reconstructions, output_path):
)


def load_sequence_database_from_file(data_path, fname="sequence_database.json"):
"""
Simply loads a sequence file and returns it.
This doesn't require an existing SfM reconstruction
"""
p_json = os.path.join(data_path, fname)
if not os.path.isfile(p_json):
return None
seq_dict = OrderedDict(io.json_load(open(p_json, "r")))

return seq_dict


def save_sequencegraph(data, reconstructions, output_path):
shot_sequences = {}
data_path = data.data_path
seq_dict = load_sequence_database_from_file(data_path)
sequences = list(seq_dict.keys())
image_xyz_per_sequences = {seq: [] for seq in sequences}

for i, rec in enumerate(reconstructions):
for shot_name, shot in rec.shots.items():
shot_sequence = None
shot_sequences[shot_name] = None
for seq, shot_list in seq_dict.items():
if shot_name in shot_list:
shot_sequence = seq
break
xyz = shot.pose.get_origin()
image_xyz_per_sequences[shot_sequence].append(xyz)

plt.clf()
cmap = cm.get_cmap("rainbow")

for seq, image_xyz in image_xyz_per_sequences.items():
xyz = np.array(image_xyz)
c = sequences.index(seq) / len(sequences)
plt.scatter(xyz[:, 1], xyz[:, 2], color=cmap(c), label=seq, s=2)
plt.legend()
plt.grid()
plt.xlabel('Y[m]')
plt.ylabel('Z[m]')
plt.axis('equal')

plt.savefig(
os.path.join(output_path, "sequencegraph.png"),
dpi=300,
bbox_inches="tight",
)


def save_topview(data, tracks_manager, reconstructions, output_path):
points = []
colors = []
Expand Down Expand Up @@ -410,6 +461,8 @@ def save_topview(data, tracks_manager, reconstructions, output_path):
if not shot.metadata.gps_position.has_value:
continue
gps = shot.metadata.gps_position.value
if gps[0] == gps[1] == gps[2] == 0.:
continue
all_x.append(gps[0])
all_y.append(gps[1])

Expand Down Expand Up @@ -506,6 +559,9 @@ def save_topview(data, tracks_manager, reconstructions, output_path):
if not shot.metadata.gps_position.has_value:
continue
gps = shot.metadata.gps_position.value
if gps[0] == gps[1] == gps[2] == 0.:
continue

gps_x, gps_y = int((gps[0] - low_x) / size_x * im_size_x), int(
(gps[1] - low_y) / size_y * im_size_y
)
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ exifread==2.1.2
fpdf2==2.1.0
joblib==0.14.1
matplotlib
networkx==1.11
networkx==2.5.0
numpy
Pillow==7.1.0
Pillow
pyproj>=1.9.5.1
pytest==3.0.7
python-dateutil==2.6.0
Expand Down