Skip to content

Commit c0e1895

Browse files
authored
Merge branch 'main' into ceres-loss-function
2 parents 887bc34 + 8b50138 commit c0e1895

File tree

19 files changed

+189
-114
lines changed

19 files changed

+189
-114
lines changed

.github/workflows/conda.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Conda CI
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
7+
build-test:
8+
name: Build conda environment and run tests
9+
10+
strategy:
11+
matrix:
12+
os: [ubuntu-24.04, ubuntu-22.04, macos-latest]
13+
14+
runs-on: ${{ matrix.os }}
15+
16+
steps:
17+
- uses: actions/checkout@v2
18+
with:
19+
submodules: true
20+
21+
- name: Setup Miniconda
22+
uses: conda-incubator/setup-miniconda@v3
23+
with:
24+
environment-file: conda.yml
25+
activate-environment: opensfm
26+
27+
- name: Build OpenSfM
28+
shell: bash -l {0}
29+
run: python setup.py build
30+
31+
- name: Run C++ tests
32+
shell: bash -l {0}
33+
run: cd cmake_build && ctest
34+
35+
- name: Run Python tests
36+
shell: bash -l {0}
37+
run: python -m pytest

conda.yml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
name: opensfm
22
dependencies:
3-
- python=3.8
3+
- python=3.10
44
- cmake=3.31
55
- make
66
- libxcrypt
7-
- opencv
7+
- libopencv *=headless*
8+
- py-opencv
89
- ceres-solver=2.1
910
- conda-forge::llvm-openmp
1011
- conda-forge::cxx-compiler
1112
- pip
1213
- pip:
13-
- cloudpickle==0.4.0
14-
- exifread==2.1.2
15-
- flask==2.3.2
16-
- fpdf2==2.4.6
17-
- joblib==0.14.1
18-
- matplotlib
19-
- networkx==2.5
20-
- numpy>=1.19
21-
- Pillow>=8.1.1
22-
- pyproj>=1.9.5.1
23-
- pytest==6.2.0
24-
- python-dateutil>=2.7
25-
- pyyaml>5.4
26-
- scipy>=1.10.0
14+
- cloudpickle==3.1.1
15+
- ExifRead==3.3.1
16+
- Flask==3.1.1
17+
- fpdf2==2.8.3
18+
- joblib==1.5.1
19+
- matplotlib==3.5.1
20+
- networkx==3.4.2
21+
- numpy==1.21.5
22+
- Pillow==9.0.1
23+
- pyproj>=3.3.0
24+
- pytest==8.4.0
25+
- python-dateutil==2.8.1
26+
- PyYAML>=5.4.1
27+
- scipy==1.8.0
2728
- Sphinx==4.2.0
28-
- six
29-
- xmltodict==0.10.2
29+
- xmltodict==0.14.2
3030
- wheel
3131
- sphinx_rtd_theme

opensfm/features_processing.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import math
55
import queue
6+
import sys
67
import threading
78
from timeit import default_timer as timer
89
from typing import Any, cast, Dict, List, Optional, Tuple, Union
@@ -16,11 +17,23 @@
1617

1718
logger: logging.Logger = logging.getLogger(__name__)
1819

19-
ProcessQueue = queue.Queue[
20-
Optional[
21-
Tuple[str, NDArray, Optional[NDArray], Optional[NDArray], DataSetBase, bool]
20+
21+
if sys.version_info >= (3, 9):
22+
ProcessQueue = queue.Queue[
23+
Optional[
24+
Tuple[
25+
str,
26+
NDArray,
27+
Optional[NDArray],
28+
Optional[NDArray],
29+
DataSetBase,
30+
bool,
31+
]
32+
]
2233
]
23-
]
34+
else:
35+
ProcessQueue = queue.Queue
36+
2437
ProducerArgs = Tuple[
2538
ProcessQueue,
2639
DataSetBase,

opensfm/large/metadataset.py

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
# pyre-unsafe
1+
# pyre-strict
22
import glob
33
import os
44
import os.path
55
import shutil
66
import sys
7+
from typing import Any, Dict, Generator, List, Tuple
78

89
import numpy as np
10+
from numpy.typing import NDArray
911
from opensfm import config, io
1012
from opensfm.dataset import DataSet
1113

1214

1315
class MetaDataSet:
14-
def __init__(self, data_path):
16+
def __init__(self, data_path: str) -> None:
1517
"""
1618
Create meta dataset instance for large scale reconstruction.
1719
1820
:param data_path: Path to directory containing meta dataset
1921
"""
20-
self.data_path = os.path.abspath(data_path)
22+
self.data_path: str = os.path.abspath(data_path)
2123

2224
config_file = os.path.join(self.data_path, "config.yaml")
23-
self.config = config.load_config(config_file)
25+
self.config: Dict[str, Any] = config.load_config(config_file)
2426

2527
self._image_list_file_name = "image_list_with_gps.tsv"
2628
self._clusters_file_name = "clusters.npz"
@@ -32,42 +34,42 @@ def __init__(self, data_path):
3234

3335
io.mkdir_p(self._submodels_path())
3436

35-
def _submodels_path(self):
37+
def _submodels_path(self) -> str:
3638
return os.path.join(self.data_path, self.config["submodels_relpath"])
3739

38-
def _submodel_path(self, i):
40+
def _submodel_path(self, i: int) -> str:
3941
"""Path to submodel i folder."""
4042
template = self.config["submodel_relpath_template"]
4143
return os.path.join(self.data_path, template % i)
4244

43-
def _submodel_images_path(self, i):
45+
def _submodel_images_path(self, i: int) -> str:
4446
"""Path to submodel i images folder."""
4547
template = self.config["submodel_images_relpath_template"]
4648
return os.path.join(self.data_path, template % i)
4749

48-
def _image_groups_path(self):
50+
def _image_groups_path(self) -> str:
4951
return os.path.join(self.data_path, "image_groups.txt")
5052

51-
def _image_list_path(self):
53+
def _image_list_path(self) -> str:
5254
return os.path.join(self._submodels_path(), self._image_list_file_name)
5355

54-
def _clusters_path(self):
56+
def _clusters_path(self) -> str:
5557
return os.path.join(self._submodels_path(), self._clusters_file_name)
5658

57-
def _clusters_with_neighbors_path(self):
59+
def _clusters_with_neighbors_path(self) -> str:
5860
return os.path.join(
5961
self._submodels_path(), self._clusters_with_neighbors_file_name
6062
)
6163

62-
def _clusters_with_neighbors_geojson_path(self):
64+
def _clusters_with_neighbors_geojson_path(self) -> str:
6365
return os.path.join(
6466
self._submodels_path(), self._clusters_with_neighbors_geojson_file_name
6567
)
6668

67-
def _clusters_geojson_path(self):
69+
def _clusters_geojson_path(self) -> str:
6870
return os.path.join(self._submodels_path(), self._clusters_geojson_file_name)
6971

70-
def _create_symlink(self, base_path, file_path):
72+
def _create_symlink(self, base_path: str, file_path: str) -> None:
7173
src = os.path.join(self.data_path, file_path)
7274
dst = os.path.join(base_path, file_path)
7375

@@ -104,59 +106,61 @@ def _create_symlink(self, base_path, file_path):
104106
os.path.join(*[".."] * subfolders, os.path.relpath(src, base_path)), dst
105107
)
106108

107-
def image_groups_exists(self):
109+
def image_groups_exists(self) -> bool:
108110
return os.path.isfile(self._image_groups_path())
109111

110-
def load_image_groups(self):
112+
def load_image_groups(self) -> Generator[List[str], None, None]:
111113
with open(self._image_groups_path()) as fin:
112114
for line in fin:
113115
yield line.split()
114116

115-
def image_list_exists(self):
117+
def image_list_exists(self) -> bool:
116118
return os.path.isfile(self._image_list_path())
117119

118-
def create_image_list(self, ills):
120+
def create_image_list(self, ills: List[Tuple[str, float, float]]) -> None:
119121
with io.open_wt(self._image_list_path()) as csvfile:
120122
for image, lat, lon in ills:
121123
csvfile.write("{}\t{}\t{}\n".format(image, lat, lon))
122124

123-
def images_with_gps(self):
125+
def images_with_gps(self) -> Generator[Tuple[str, float, float], None, None]:
124126
with io.open_rt(self._image_list_path()) as csvfile:
125127
for line in csvfile:
126128
image, lat, lon = line.split("\t")
127129
yield image, float(lat), float(lon)
128130

129-
def save_clusters(self, images, positions, labels, centers):
131+
def save_clusters(
132+
self, images: NDArray, positions: NDArray, labels: NDArray, centers: NDArray
133+
) -> None:
130134
filepath = self._clusters_path()
131135
np.savez_compressed(
132136
filepath, images=images, positions=positions, labels=labels, centers=centers
133137
)
134138

135-
def load_clusters(self):
139+
def load_clusters(self) -> Tuple[NDArray, NDArray, NDArray, NDArray]:
136140
c = np.load(self._clusters_path())
137141
images = c["images"].ravel()
138142
labels = c["labels"].ravel()
139143
return images, c["positions"], labels, c["centers"]
140144

141-
def save_clusters_with_neighbors(self, clusters):
145+
def save_clusters_with_neighbors(self, clusters: List[List[int]]) -> None:
142146
filepath = self._clusters_with_neighbors_path()
143147
np.savez_compressed(filepath, clusters=np.array(clusters, dtype=object))
144148

145-
def load_clusters_with_neighbors(self):
149+
def load_clusters_with_neighbors(self) -> NDArray:
146150
c = np.load(self._clusters_with_neighbors_path(), allow_pickle=True)
147151
return c["clusters"]
148152

149-
def save_cluster_with_neighbors_geojson(self, geojson):
153+
def save_cluster_with_neighbors_geojson(self, geojson: Dict[str, Any]) -> None:
150154
filepath = self._clusters_with_neighbors_geojson_path()
151155
with io.open_wt(filepath) as fout:
152156
io.json_dump(geojson, fout)
153157

154-
def save_clusters_geojson(self, geojson):
158+
def save_clusters_geojson(self, geojson: Dict[str, Any]) -> None:
155159
filepath = self._clusters_geojson_path()
156160
with io.open_wt(filepath) as fout:
157161
io.json_dump(geojson, fout)
158162

159-
def remove_submodels(self):
163+
def remove_submodels(self) -> None:
160164
sm = self._submodels_path()
161165
paths = [
162166
os.path.join(sm, o)
@@ -166,7 +170,7 @@ def remove_submodels(self):
166170
for path in paths:
167171
shutil.rmtree(path)
168172

169-
def create_submodels(self, clusters):
173+
def create_submodels(self, clusters: NDArray) -> None:
170174
data = DataSet(self.data_path)
171175
for i, cluster in enumerate(clusters):
172176
# create sub model dirs
@@ -216,7 +220,7 @@ def create_submodels(self, clusters):
216220
for filepath in filepaths:
217221
self._create_symlink(submodel_path, filepath)
218222

219-
def get_submodel_paths(self):
223+
def get_submodel_paths(self) -> List[str]:
220224
submodel_paths = []
221225
for i in range(999999):
222226
submodel_path = self._submodel_path(i)

0 commit comments

Comments
 (0)