kopia lustrzana https://github.com/OpenDroneMap/ODM
Porównaj commity
33 Commity
Autor | SHA1 | Data |
---|---|---|
Piero Toffanin | ae6726e536 | |
Piero Toffanin | 6da366f806 | |
Piero Toffanin | e4e27c21f2 | |
Piero Toffanin | f9136f7a0d | |
idimitrovski | a2d9eccad5 | |
Piero Toffanin | 424d9e28a0 | |
Andrew Harvey | a0fbd71d41 | |
Piero Toffanin | 6084d1dca0 | |
Piero Toffanin | aef4182cf9 | |
Piero Toffanin | 6c0fe6e79d | |
Piero Toffanin | 17dfc7599a | |
Piero Toffanin | a70e7445ad | |
Piero Toffanin | 981bf88b48 | |
Piero Toffanin | ad63392e1a | |
Piero Toffanin | 77f8ffc8cd | |
Piero Toffanin | 4d7cf32a8c | |
Stephen Mather | 5a439c0ab6 | |
Piero Toffanin | ffcda0dc57 | |
Stephen Mather | 2c6fd1dd9f | |
Sylvain POULAIN | cb3229a3d4 | |
Piero Toffanin | fc9c94880f | |
kielnino | b204a2eb98 | |
Piero Toffanin | d9f77bea54 | |
kielnino | 10947ecddf | |
kielnino | f7c7044823 | |
Piero Toffanin | ae50133886 | |
Piero Toffanin | 9fd3bf3edd | |
Piero Toffanin | fb85b754fb | |
Piero Toffanin | 30f89c068c | |
Piero Toffanin | 260b4ef864 | |
Piero Toffanin | fb5d88366e | |
Piero Toffanin | f793627402 | |
Piero Toffanin | 9183218f1b |
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
3.3.4
|
3.5.1
|
||||||
|
|
|
@ -127,6 +127,9 @@ installreqs() {
|
||||||
installdepsfromsnapcraft build openmvs
|
installdepsfromsnapcraft build openmvs
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# edt requires numpy to build
|
||||||
|
pip install --ignore-installed numpy==1.23.1
|
||||||
pip install --ignore-installed -r requirements.txt
|
pip install --ignore-installed -r requirements.txt
|
||||||
#if [ ! -z "$GPU_INSTALL" ]; then
|
#if [ ! -z "$GPU_INSTALL" ]; then
|
||||||
#fi
|
#fi
|
||||||
|
|
|
@ -60,14 +60,14 @@ rerun_stages = {
|
||||||
'orthophoto_no_tiled': 'odm_orthophoto',
|
'orthophoto_no_tiled': 'odm_orthophoto',
|
||||||
'orthophoto_png': 'odm_orthophoto',
|
'orthophoto_png': 'odm_orthophoto',
|
||||||
'orthophoto_resolution': 'odm_orthophoto',
|
'orthophoto_resolution': 'odm_orthophoto',
|
||||||
'pc_classify': 'odm_dem',
|
'pc_classify': 'odm_georeferencing',
|
||||||
'pc_copc': 'odm_georeferencing',
|
'pc_copc': 'odm_georeferencing',
|
||||||
'pc_csv': 'odm_georeferencing',
|
'pc_csv': 'odm_georeferencing',
|
||||||
'pc_ept': 'odm_georeferencing',
|
'pc_ept': 'odm_georeferencing',
|
||||||
'pc_filter': 'openmvs',
|
'pc_filter': 'openmvs',
|
||||||
'pc_las': 'odm_georeferencing',
|
'pc_las': 'odm_georeferencing',
|
||||||
'pc_quality': 'opensfm',
|
'pc_quality': 'opensfm',
|
||||||
'pc_rectify': 'odm_dem',
|
'pc_rectify': 'odm_georeferencing',
|
||||||
'pc_sample': 'odm_filterpoints',
|
'pc_sample': 'odm_filterpoints',
|
||||||
'pc_skip_geometric': 'openmvs',
|
'pc_skip_geometric': 'openmvs',
|
||||||
'primary_band': 'dataset',
|
'primary_band': 'dataset',
|
||||||
|
@ -217,7 +217,7 @@ def config(argv=None, parser=None):
|
||||||
parser.add_argument('--feature-type',
|
parser.add_argument('--feature-type',
|
||||||
metavar='<string>',
|
metavar='<string>',
|
||||||
action=StoreValue,
|
action=StoreValue,
|
||||||
default='sift',
|
default='dspsift',
|
||||||
choices=['akaze', 'dspsift', 'hahog', 'orb', 'sift'],
|
choices=['akaze', 'dspsift', 'hahog', 'orb', 'sift'],
|
||||||
help=('Choose the algorithm for extracting keypoints and computing descriptors. '
|
help=('Choose the algorithm for extracting keypoints and computing descriptors. '
|
||||||
'Can be one of: %(choices)s. Default: '
|
'Can be one of: %(choices)s. Default: '
|
||||||
|
@ -485,7 +485,7 @@ def config(argv=None, parser=None):
|
||||||
metavar='<positive float>',
|
metavar='<positive float>',
|
||||||
action=StoreValue,
|
action=StoreValue,
|
||||||
type=float,
|
type=float,
|
||||||
default=2.5,
|
default=5,
|
||||||
help='Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering. '
|
help='Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering. '
|
||||||
'Default: %(default)s')
|
'Default: %(default)s')
|
||||||
|
|
||||||
|
@ -838,7 +838,7 @@ def config(argv=None, parser=None):
|
||||||
type=float,
|
type=float,
|
||||||
action=StoreValue,
|
action=StoreValue,
|
||||||
metavar='<positive float>',
|
metavar='<positive float>',
|
||||||
default=10,
|
default=3,
|
||||||
help='Set a value in meters for the GPS Dilution of Precision (DOP) '
|
help='Set a value in meters for the GPS Dilution of Precision (DOP) '
|
||||||
'information for all images. If your images are tagged '
|
'information for all images. If your images are tagged '
|
||||||
'with high precision GPS information (RTK), this value will be automatically '
|
'with high precision GPS information (RTK), this value will be automatically '
|
||||||
|
|
|
@ -12,6 +12,9 @@ class GeoFile:
|
||||||
|
|
||||||
with open(self.geo_path, 'r') as f:
|
with open(self.geo_path, 'r') as f:
|
||||||
contents = f.read().strip()
|
contents = f.read().strip()
|
||||||
|
|
||||||
|
# Strip eventual BOM characters
|
||||||
|
contents = contents.replace('\ufeff', '')
|
||||||
|
|
||||||
lines = list(map(str.strip, contents.split('\n')))
|
lines = list(map(str.strip, contents.split('\n')))
|
||||||
if lines:
|
if lines:
|
||||||
|
|
|
@ -279,9 +279,10 @@ def obj2glb(input_obj, output_glb, rtc=(None, None), draco_compression=True, _in
|
||||||
)
|
)
|
||||||
|
|
||||||
gltf.extensionsRequired = ['KHR_materials_unlit']
|
gltf.extensionsRequired = ['KHR_materials_unlit']
|
||||||
|
gltf.extensionsUsed = ['KHR_materials_unlit']
|
||||||
|
|
||||||
if rtc != (None, None) and len(rtc) >= 2:
|
if rtc != (None, None) and len(rtc) >= 2:
|
||||||
gltf.extensionsUsed = ['CESIUM_RTC', 'KHR_materials_unlit']
|
gltf.extensionsUsed.append('CESIUM_RTC')
|
||||||
gltf.extensions = {
|
gltf.extensions = {
|
||||||
'CESIUM_RTC': {
|
'CESIUM_RTC': {
|
||||||
'center': [float(rtc[0]), float(rtc[1]), 0.0]
|
'center': [float(rtc[0]), float(rtc[1]), 0.0]
|
||||||
|
|
|
@ -187,7 +187,7 @@ def screened_poisson_reconstruction(inPointCloud, outMesh, depth = 8, samples =
|
||||||
if threads < 1:
|
if threads < 1:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
log.ODM_WARNING("PoissonRecon failed with %s threads, let's retry with %s..." % (threads, threads // 2))
|
log.ODM_WARNING("PoissonRecon failed with %s threads, let's retry with %s..." % (threads * 2, threads))
|
||||||
|
|
||||||
|
|
||||||
# Cleanup and reduce vertex count if necessary
|
# Cleanup and reduce vertex count if necessary
|
||||||
|
|
|
@ -64,7 +64,6 @@ class OSFMContext:
|
||||||
"Check that the images have enough overlap, "
|
"Check that the images have enough overlap, "
|
||||||
"that there are enough recognizable features "
|
"that there are enough recognizable features "
|
||||||
"and that the images are in focus. "
|
"and that the images are in focus. "
|
||||||
"You could also try to increase the --min-num-features parameter."
|
|
||||||
"The program will now exit.")
|
"The program will now exit.")
|
||||||
|
|
||||||
if rolling_shutter_correct:
|
if rolling_shutter_correct:
|
||||||
|
@ -794,3 +793,12 @@ def get_all_submodel_paths(submodels_path, *all_paths):
|
||||||
result.append([os.path.join(submodels_path, f, ap) for ap in all_paths])
|
result.append([os.path.join(submodels_path, f, ap) for ap in all_paths])
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def is_submodel(opensfm_root):
|
||||||
|
# A bit hackish, but works without introducing additional markers / flags
|
||||||
|
# Look at the path of the opensfm directory and see if "submodel_" is part of it
|
||||||
|
parts = os.path.abspath(opensfm_root).split(os.path.sep)
|
||||||
|
|
||||||
|
return (len(parts) >= 2 and parts[-2][:9] == "submodel_") or \
|
||||||
|
os.path.isfile(os.path.join(opensfm_root, "split_merge_stop_at_reconstruction.txt")) or \
|
||||||
|
os.path.isfile(os.path.join(opensfm_root, "features", "empty"))
|
|
@ -430,7 +430,8 @@ class ODM_Photo:
|
||||||
camera_projection = camera_projection.lower()
|
camera_projection = camera_projection.lower()
|
||||||
|
|
||||||
# Parrot Sequoia's "fisheye" model maps to "fisheye_opencv"
|
# Parrot Sequoia's "fisheye" model maps to "fisheye_opencv"
|
||||||
if camera_projection == "fisheye" and self.camera_make.lower() == "parrot" and self.camera_model.lower() == "sequoia":
|
# or better yet, replace all fisheye with fisheye_opencv, but wait to change API signature
|
||||||
|
if camera_projection == "fisheye":
|
||||||
camera_projection = "fisheye_opencv"
|
camera_projection = "fisheye_opencv"
|
||||||
|
|
||||||
if camera_projection in projections:
|
if camera_projection in projections:
|
||||||
|
|
|
@ -9,6 +9,8 @@ from opendm.concurrency import parallel_map
|
||||||
from opendm.utils import double_quote
|
from opendm.utils import double_quote
|
||||||
from opendm.boundary import as_polygon, as_geojson
|
from opendm.boundary import as_polygon, as_geojson
|
||||||
from opendm.dem.pdal import run_pipeline
|
from opendm.dem.pdal import run_pipeline
|
||||||
|
from opendm.opc import classify
|
||||||
|
from opendm.dem import commands
|
||||||
|
|
||||||
def ply_info(input_ply):
|
def ply_info(input_ply):
|
||||||
if not os.path.exists(input_ply):
|
if not os.path.exists(input_ply):
|
||||||
|
@ -274,6 +276,32 @@ def merge_ply(input_point_cloud_files, output_file, dims=None):
|
||||||
system.run(' '.join(cmd))
|
system.run(' '.join(cmd))
|
||||||
|
|
||||||
def post_point_cloud_steps(args, tree, rerun=False):
|
def post_point_cloud_steps(args, tree, rerun=False):
|
||||||
|
# Classify and rectify before generating derivate files
|
||||||
|
if args.pc_classify:
|
||||||
|
pc_classify_marker = os.path.join(tree.odm_georeferencing, 'pc_classify_done.txt')
|
||||||
|
|
||||||
|
if not io.file_exists(pc_classify_marker) or rerun:
|
||||||
|
log.ODM_INFO("Classifying {} using Simple Morphological Filter (1/2)".format(tree.odm_georeferencing_model_laz))
|
||||||
|
commands.classify(tree.odm_georeferencing_model_laz,
|
||||||
|
args.smrf_scalar,
|
||||||
|
args.smrf_slope,
|
||||||
|
args.smrf_threshold,
|
||||||
|
args.smrf_window
|
||||||
|
)
|
||||||
|
|
||||||
|
log.ODM_INFO("Classifying {} using OpenPointClass (2/2)".format(tree.odm_georeferencing_model_laz))
|
||||||
|
classify(tree.odm_georeferencing_model_laz, args.max_concurrency)
|
||||||
|
|
||||||
|
with open(pc_classify_marker, 'w') as f:
|
||||||
|
f.write('Classify: smrf\n')
|
||||||
|
f.write('Scalar: {}\n'.format(args.smrf_scalar))
|
||||||
|
f.write('Slope: {}\n'.format(args.smrf_slope))
|
||||||
|
f.write('Threshold: {}\n'.format(args.smrf_threshold))
|
||||||
|
f.write('Window: {}\n'.format(args.smrf_window))
|
||||||
|
|
||||||
|
if args.pc_rectify:
|
||||||
|
commands.rectify(tree.odm_georeferencing_model_laz)
|
||||||
|
|
||||||
# XYZ point cloud output
|
# XYZ point cloud output
|
||||||
if args.pc_csv:
|
if args.pc_csv:
|
||||||
log.ODM_INFO("Creating CSV file (XYZ format)")
|
log.ODM_INFO("Creating CSV file (XYZ format)")
|
||||||
|
|
|
@ -19,6 +19,7 @@ RS_DATABASE = {
|
||||||
|
|
||||||
'dji fc220': 64, # DJI Mavic Pro (Platinum)
|
'dji fc220': 64, # DJI Mavic Pro (Platinum)
|
||||||
'hasselblad l1d-20c': lambda p: 47 if p.get_capture_megapixels() < 17 else 56, # DJI Mavic 2 Pro (at 16:10 => 16.8MP 47ms, at 3:2 => 19.9MP 56ms. 4:3 has 17.7MP with same image height as 3:2 which can be concluded as same sensor readout)
|
'hasselblad l1d-20c': lambda p: 47 if p.get_capture_megapixels() < 17 else 56, # DJI Mavic 2 Pro (at 16:10 => 16.8MP 47ms, at 3:2 => 19.9MP 56ms. 4:3 has 17.7MP with same image height as 3:2 which can be concluded as same sensor readout)
|
||||||
|
'hasselblad l2d-20c': 16.6, # DJI Mavic 3 (not enterprise version)
|
||||||
|
|
||||||
'dji fc3582': lambda p: 26 if p.get_capture_megapixels() < 48 else 60, # DJI Mini 3 pro (at 48MP readout is 60ms, at 12MP it's 26ms)
|
'dji fc3582': lambda p: 26 if p.get_capture_megapixels() < 48 else 60, # DJI Mini 3 pro (at 48MP readout is 60ms, at 12MP it's 26ms)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ from opendm import log
|
||||||
from opendm import io
|
from opendm import io
|
||||||
from opendm import system
|
from opendm import system
|
||||||
from opendm import context
|
from opendm import context
|
||||||
|
from opendm import multispectral
|
||||||
|
|
||||||
from opendm.progress import progressbc
|
from opendm.progress import progressbc
|
||||||
from opendm.photo import ODM_Photo
|
from opendm.photo import ODM_Photo
|
||||||
|
@ -45,19 +46,51 @@ class ODM_Reconstruction(object):
|
||||||
band_photos[p.band_name].append(p)
|
band_photos[p.band_name].append(p)
|
||||||
|
|
||||||
bands_count = len(band_photos)
|
bands_count = len(band_photos)
|
||||||
if bands_count >= 2 and bands_count <= 8:
|
|
||||||
|
# Band name with the minimum number of photos
|
||||||
|
max_band_name = None
|
||||||
|
max_photos = -1
|
||||||
|
for band_name in band_photos:
|
||||||
|
if len(band_photos[band_name]) > max_photos:
|
||||||
|
max_band_name = band_name
|
||||||
|
max_photos = len(band_photos[band_name])
|
||||||
|
|
||||||
|
if bands_count >= 2 and bands_count <= 10:
|
||||||
# Validate that all bands have the same number of images,
|
# Validate that all bands have the same number of images,
|
||||||
# otherwise this is not a multi-camera setup
|
# otherwise this is not a multi-camera setup
|
||||||
img_per_band = len(band_photos[p.band_name])
|
img_per_band = len(band_photos[max_band_name])
|
||||||
for band in band_photos:
|
|
||||||
if len(band_photos[band]) != img_per_band:
|
|
||||||
log.ODM_ERROR("Multi-camera setup detected, but band \"%s\" (identified from \"%s\") has only %s images (instead of %s), perhaps images are missing or are corrupted. Please include all necessary files to process all bands and try again." % (band, band_photos[band][0].filename, len(band_photos[band]), img_per_band))
|
|
||||||
raise RuntimeError("Invalid multi-camera images")
|
|
||||||
|
|
||||||
mc = []
|
mc = []
|
||||||
for band_name in band_indexes:
|
for band_name in band_indexes:
|
||||||
mc.append({'name': band_name, 'photos': band_photos[band_name]})
|
mc.append({'name': band_name, 'photos': band_photos[band_name]})
|
||||||
|
|
||||||
|
filter_missing = False
|
||||||
|
for band in band_photos:
|
||||||
|
if len(band_photos[band]) < img_per_band:
|
||||||
|
log.ODM_WARNING("Multi-camera setup detected, but band \"%s\" (identified from \"%s\") has only %s images (instead of %s), perhaps images are missing or are corrupted." % (band, band_photos[band][0].filename, len(band_photos[band]), len(band_photos[max_band_name])))
|
||||||
|
filter_missing = True
|
||||||
|
|
||||||
|
if filter_missing:
|
||||||
|
# Calculate files to ignore
|
||||||
|
_, p2s = multispectral.compute_band_maps(mc, max_band_name)
|
||||||
|
|
||||||
|
max_files_per_band = 0
|
||||||
|
|
||||||
|
for filename in p2s:
|
||||||
|
max_files_per_band = max(max_files_per_band, len(p2s[filename]))
|
||||||
|
|
||||||
|
for filename in p2s:
|
||||||
|
if len(p2s[filename]) < max_files_per_band:
|
||||||
|
photos_to_remove = p2s[filename] + [p for p in self.photos if p.filename == filename]
|
||||||
|
for photo in photos_to_remove:
|
||||||
|
log.ODM_WARNING("Excluding %s" % photo.filename)
|
||||||
|
|
||||||
|
self.photos = [p for p in self.photos if p != photo]
|
||||||
|
for i in range(len(mc)):
|
||||||
|
mc[i]['photos'] = [p for p in mc[i]['photos'] if p != photo]
|
||||||
|
|
||||||
|
log.ODM_INFO("New image count: %s" % len(self.photos))
|
||||||
|
|
||||||
# We enforce a normalized band order for all bands that we can identify
|
# We enforce a normalized band order for all bands that we can identify
|
||||||
# and rely on the manufacturer's band_indexes as a fallback for all others
|
# and rely on the manufacturer's band_indexes as a fallback for all others
|
||||||
normalized_band_order = {
|
normalized_band_order = {
|
||||||
|
@ -94,7 +127,7 @@ class ODM_Reconstruction(object):
|
||||||
|
|
||||||
for c, d in enumerate(mc):
|
for c, d in enumerate(mc):
|
||||||
log.ODM_INFO(f"Band {c + 1}: {d['name']}")
|
log.ODM_INFO(f"Band {c + 1}: {d['name']}")
|
||||||
|
|
||||||
return mc
|
return mc
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -54,8 +54,10 @@ class SrtFileParser:
|
||||||
if not self.gps_data:
|
if not self.gps_data:
|
||||||
for d in self.data:
|
for d in self.data:
|
||||||
lat, lon, alt = d.get('latitude'), d.get('longitude'), d.get('altitude')
|
lat, lon, alt = d.get('latitude'), d.get('longitude'), d.get('altitude')
|
||||||
|
if alt is None:
|
||||||
|
alt = 0
|
||||||
tm = d.get('start')
|
tm = d.get('start')
|
||||||
|
|
||||||
if lat is not None and lon is not None:
|
if lat is not None and lon is not None:
|
||||||
if self.ll_to_utm is None:
|
if self.ll_to_utm is None:
|
||||||
self.ll_to_utm, self.utm_to_ll = location.utm_transformers_from_ll(lon, lat)
|
self.ll_to_utm, self.utm_to_ll = location.utm_transformers_from_ll(lon, lat)
|
||||||
|
@ -127,6 +129,20 @@ class SrtFileParser:
|
||||||
# 00:00:35,000 --> 00:00:36,000
|
# 00:00:35,000 --> 00:00:36,000
|
||||||
# F/6.3, SS 60, ISO 100, EV 0, RTK (120.083799, 30.213635, 28), HOME (120.084146, 30.214243, 103.55m), D 75.36m, H 76.19m, H.S 0.30m/s, V.S 0.00m/s, F.PRY (-5.3°, 2.1°, 28.3°), G.PRY (-40.0°, 0.0°, 28.2°)
|
# F/6.3, SS 60, ISO 100, EV 0, RTK (120.083799, 30.213635, 28), HOME (120.084146, 30.214243, 103.55m), D 75.36m, H 76.19m, H.S 0.30m/s, V.S 0.00m/s, F.PRY (-5.3°, 2.1°, 28.3°), G.PRY (-40.0°, 0.0°, 28.2°)
|
||||||
|
|
||||||
|
# DJI Unknown Model #1
|
||||||
|
# 1
|
||||||
|
# 00:00:00,000 --> 00:00:00,033
|
||||||
|
# <font size="28">SrtCnt : 1, DiffTime : 33ms
|
||||||
|
# 2024-01-18 10:23:26.397
|
||||||
|
# [iso : 150] [shutter : 1/5000.0] [fnum : 170] [ev : 0] [ct : 5023] [color_md : default] [focal_len : 240] [dzoom_ratio: 10000, delta:0],[latitude: -22.724555] [longitude: -47.602414] [rel_alt: 0.300 abs_alt: 549.679] </font>
|
||||||
|
|
||||||
|
# DJI Mavic 2 Zoom
|
||||||
|
# 1
|
||||||
|
# 00:00:00,000 --> 00:00:00,041
|
||||||
|
# <font size="36">FrameCnt : 1, DiffTime : 41ms
|
||||||
|
# 2023-07-15 11:55:16,320,933
|
||||||
|
# [iso : 100] [shutter : 1/400.0] [fnum : 280] [ev : 0] [ct : 5818] [color_md : default] [focal_len : 240] [latitude : 0.000000] [longtitude : 0.000000] [altitude: 0.000000] </font>
|
||||||
|
|
||||||
with open(self.filename, 'r') as f:
|
with open(self.filename, 'r') as f:
|
||||||
|
|
||||||
iso = None
|
iso = None
|
||||||
|
@ -197,12 +213,14 @@ class SrtFileParser:
|
||||||
|
|
||||||
latitude = match_single([
|
latitude = match_single([
|
||||||
("latitude: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
("latitude: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
||||||
|
("latitude : ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
||||||
("GPS \([\d\.\-]+,? ([\d\.\-]+),? [\d\.\-]+\)", lambda v: float(v) if v != 0 else None),
|
("GPS \([\d\.\-]+,? ([\d\.\-]+),? [\d\.\-]+\)", lambda v: float(v) if v != 0 else None),
|
||||||
("RTK \([-+]?\d+\.\d+, (-?\d+\.\d+), -?\d+\)", lambda v: float(v) if v != 0 else None),
|
("RTK \([-+]?\d+\.\d+, (-?\d+\.\d+), -?\d+\)", lambda v: float(v) if v != 0 else None),
|
||||||
], line)
|
], line)
|
||||||
|
|
||||||
longitude = match_single([
|
longitude = match_single([
|
||||||
("longitude: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
("longitude: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
||||||
|
("longtitude : ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
||||||
("GPS \(([\d\.\-]+),? [\d\.\-]+,? [\d\.\-]+\)", lambda v: float(v) if v != 0 else None),
|
("GPS \(([\d\.\-]+),? [\d\.\-]+,? [\d\.\-]+\)", lambda v: float(v) if v != 0 else None),
|
||||||
("RTK \((-?\d+\.\d+), [-+]?\d+\.\d+, -?\d+\)", lambda v: float(v) if v != 0 else None),
|
("RTK \((-?\d+\.\d+), [-+]?\d+\.\d+, -?\d+\)", lambda v: float(v) if v != 0 else None),
|
||||||
], line)
|
], line)
|
||||||
|
@ -211,4 +229,5 @@ class SrtFileParser:
|
||||||
("altitude: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
("altitude: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
||||||
("GPS \([\d\.\-]+,? [\d\.\-]+,? ([\d\.\-]+)\)", lambda v: float(v) if v != 0 else None),
|
("GPS \([\d\.\-]+,? [\d\.\-]+,? ([\d\.\-]+)\)", lambda v: float(v) if v != 0 else None),
|
||||||
("RTK \([-+]?\d+\.\d+, [-+]?\d+\.\d+, (-?\d+)\)", lambda v: float(v) if v != 0 else None),
|
("RTK \([-+]?\d+\.\d+, [-+]?\d+\.\d+, (-?\d+)\)", lambda v: float(v) if v != 0 else None),
|
||||||
|
("abs_alt: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
|
||||||
], line)
|
], line)
|
|
@ -110,7 +110,7 @@ class ODMMvsTexStage(types.ODM_Stage):
|
||||||
|
|
||||||
mvs_tmp_dir = os.path.join(r['out_dir'], 'tmp')
|
mvs_tmp_dir = os.path.join(r['out_dir'], 'tmp')
|
||||||
|
|
||||||
# Make sure tmp directory is empty
|
# mvstex creates a tmp directory, so make sure it is empty
|
||||||
if io.dir_exists(mvs_tmp_dir):
|
if io.dir_exists(mvs_tmp_dir):
|
||||||
log.ODM_INFO("Removing old tmp directory {}".format(mvs_tmp_dir))
|
log.ODM_INFO("Removing old tmp directory {}".format(mvs_tmp_dir))
|
||||||
shutil.rmtree(mvs_tmp_dir)
|
shutil.rmtree(mvs_tmp_dir)
|
||||||
|
|
|
@ -12,7 +12,6 @@ from opendm.cropper import Cropper
|
||||||
from opendm import pseudogeo
|
from opendm import pseudogeo
|
||||||
from opendm.tiles.tiler import generate_dem_tiles
|
from opendm.tiles.tiler import generate_dem_tiles
|
||||||
from opendm.cogeo import convert_to_cogeo
|
from opendm.cogeo import convert_to_cogeo
|
||||||
from opendm.opc import classify
|
|
||||||
|
|
||||||
class ODMDEMStage(types.ODM_Stage):
|
class ODMDEMStage(types.ODM_Stage):
|
||||||
def process(self, args, outputs):
|
def process(self, args, outputs):
|
||||||
|
@ -35,7 +34,6 @@ class ODMDEMStage(types.ODM_Stage):
|
||||||
ignore_resolution=ignore_resolution and args.ignore_gsd,
|
ignore_resolution=ignore_resolution and args.ignore_gsd,
|
||||||
has_gcp=reconstruction.has_gcp())
|
has_gcp=reconstruction.has_gcp())
|
||||||
|
|
||||||
log.ODM_INFO('Classify: ' + str(args.pc_classify))
|
|
||||||
log.ODM_INFO('Create DSM: ' + str(args.dsm))
|
log.ODM_INFO('Create DSM: ' + str(args.dsm))
|
||||||
log.ODM_INFO('Create DTM: ' + str(args.dtm))
|
log.ODM_INFO('Create DTM: ' + str(args.dtm))
|
||||||
log.ODM_INFO('DEM input file {0} found: {1}'.format(dem_input, str(pc_model_found)))
|
log.ODM_INFO('DEM input file {0} found: {1}'.format(dem_input, str(pc_model_found)))
|
||||||
|
@ -45,34 +43,9 @@ class ODMDEMStage(types.ODM_Stage):
|
||||||
if not io.dir_exists(odm_dem_root):
|
if not io.dir_exists(odm_dem_root):
|
||||||
system.mkdir_p(odm_dem_root)
|
system.mkdir_p(odm_dem_root)
|
||||||
|
|
||||||
if args.pc_classify and pc_model_found:
|
|
||||||
pc_classify_marker = os.path.join(odm_dem_root, 'pc_classify_done.txt')
|
|
||||||
|
|
||||||
if not io.file_exists(pc_classify_marker) or self.rerun():
|
|
||||||
log.ODM_INFO("Classifying {} using Simple Morphological Filter (1/2)".format(dem_input))
|
|
||||||
commands.classify(dem_input,
|
|
||||||
args.smrf_scalar,
|
|
||||||
args.smrf_slope,
|
|
||||||
args.smrf_threshold,
|
|
||||||
args.smrf_window
|
|
||||||
)
|
|
||||||
|
|
||||||
log.ODM_INFO("Classifying {} using OpenPointClass (2/2)".format(dem_input))
|
|
||||||
classify(dem_input, args.max_concurrency)
|
|
||||||
|
|
||||||
with open(pc_classify_marker, 'w') as f:
|
|
||||||
f.write('Classify: smrf\n')
|
|
||||||
f.write('Scalar: {}\n'.format(args.smrf_scalar))
|
|
||||||
f.write('Slope: {}\n'.format(args.smrf_slope))
|
|
||||||
f.write('Threshold: {}\n'.format(args.smrf_threshold))
|
|
||||||
f.write('Window: {}\n'.format(args.smrf_window))
|
|
||||||
|
|
||||||
progress = 20
|
progress = 20
|
||||||
self.update_progress(progress)
|
self.update_progress(progress)
|
||||||
|
|
||||||
if args.pc_rectify:
|
|
||||||
commands.rectify(dem_input)
|
|
||||||
|
|
||||||
# Do we need to process anything here?
|
# Do we need to process anything here?
|
||||||
if (args.dsm or args.dtm) and pc_model_found:
|
if (args.dsm or args.dtm) and pc_model_found:
|
||||||
dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif')
|
dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif')
|
||||||
|
@ -120,7 +93,7 @@ class ODMDEMStage(types.ODM_Stage):
|
||||||
if args.cog:
|
if args.cog:
|
||||||
convert_to_cogeo(dem_geotiff_path, max_workers=args.max_concurrency)
|
convert_to_cogeo(dem_geotiff_path, max_workers=args.max_concurrency)
|
||||||
|
|
||||||
progress += 30
|
progress += 40
|
||||||
self.update_progress(progress)
|
self.update_progress(progress)
|
||||||
else:
|
else:
|
||||||
log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root)
|
log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root)
|
||||||
|
|
|
@ -7,6 +7,7 @@ from opendm import context
|
||||||
from opendm import types
|
from opendm import types
|
||||||
from opendm import gsd
|
from opendm import gsd
|
||||||
from opendm import orthophoto
|
from opendm import orthophoto
|
||||||
|
from opendm.osfm import is_submodel
|
||||||
from opendm.concurrency import get_max_memory_mb
|
from opendm.concurrency import get_max_memory_mb
|
||||||
from opendm.cutline import compute_cutline
|
from opendm.cutline import compute_cutline
|
||||||
from opendm.utils import double_quote
|
from opendm.utils import double_quote
|
||||||
|
@ -114,6 +115,7 @@ class ODMOrthoPhotoStage(types.ODM_Stage):
|
||||||
|
|
||||||
# Cutline computation, before cropping
|
# Cutline computation, before cropping
|
||||||
# We want to use the full orthophoto, not the cropped one.
|
# We want to use the full orthophoto, not the cropped one.
|
||||||
|
submodel_run = is_submodel(tree.opensfm)
|
||||||
if args.orthophoto_cutline:
|
if args.orthophoto_cutline:
|
||||||
cutline_file = os.path.join(tree.odm_orthophoto, "cutline.gpkg")
|
cutline_file = os.path.join(tree.odm_orthophoto, "cutline.gpkg")
|
||||||
|
|
||||||
|
@ -122,15 +124,18 @@ class ODMOrthoPhotoStage(types.ODM_Stage):
|
||||||
cutline_file,
|
cutline_file,
|
||||||
args.max_concurrency,
|
args.max_concurrency,
|
||||||
scale=0.25)
|
scale=0.25)
|
||||||
|
|
||||||
orthophoto.compute_mask_raster(tree.odm_orthophoto_tif, cutline_file,
|
if submodel_run:
|
||||||
os.path.join(tree.odm_orthophoto, "odm_orthophoto_cut.tif"),
|
orthophoto.compute_mask_raster(tree.odm_orthophoto_tif, cutline_file,
|
||||||
blend_distance=20, only_max_coords_feature=True)
|
os.path.join(tree.odm_orthophoto, "odm_orthophoto_cut.tif"),
|
||||||
|
blend_distance=20, only_max_coords_feature=True)
|
||||||
|
else:
|
||||||
|
log.ODM_INFO("Not a submodel run, skipping mask raster generation")
|
||||||
|
|
||||||
orthophoto.post_orthophoto_steps(args, bounds_file_path, tree.odm_orthophoto_tif, tree.orthophoto_tiles, resolution)
|
orthophoto.post_orthophoto_steps(args, bounds_file_path, tree.odm_orthophoto_tif, tree.orthophoto_tiles, resolution)
|
||||||
|
|
||||||
# Generate feathered orthophoto also
|
# Generate feathered orthophoto also
|
||||||
if args.orthophoto_cutline:
|
if args.orthophoto_cutline and submodel_run:
|
||||||
orthophoto.feather_raster(tree.odm_orthophoto_tif,
|
orthophoto.feather_raster(tree.odm_orthophoto_tif,
|
||||||
os.path.join(tree.odm_orthophoto, "odm_orthophoto_feathered.tif"),
|
os.path.join(tree.odm_orthophoto, "odm_orthophoto_feathered.tif"),
|
||||||
blend_distance=20
|
blend_distance=20
|
||||||
|
|
|
@ -94,7 +94,7 @@ class ODMOpenMVSStage(types.ODM_Stage):
|
||||||
extra_config.append("--ignore-mask-label 0")
|
extra_config.append("--ignore-mask-label 0")
|
||||||
|
|
||||||
with open(densify_ini_file, 'w+') as f:
|
with open(densify_ini_file, 'w+') as f:
|
||||||
f.write("Optimize = 7\n")
|
f.write("Optimize = 7\nMin Views Filter = 1\n")
|
||||||
|
|
||||||
def run_densify():
|
def run_densify():
|
||||||
system.run('"%s" "%s" %s' % (context.omvs_densify_path,
|
system.run('"%s" "%s" %s' % (context.omvs_densify_path,
|
||||||
|
@ -110,7 +110,7 @@ class ODMOpenMVSStage(types.ODM_Stage):
|
||||||
log.ODM_WARNING("OpenMVS failed with GPU, is your graphics card driver up to date? Falling back to CPU.")
|
log.ODM_WARNING("OpenMVS failed with GPU, is your graphics card driver up to date? Falling back to CPU.")
|
||||||
gpu_config = ["--cuda-device -2"]
|
gpu_config = ["--cuda-device -2"]
|
||||||
run_densify()
|
run_densify()
|
||||||
elif (e.errorCode == 137 or e.errorCode == 3221226505) and not pc_tile:
|
elif (e.errorCode == 137 or e.errorCode == 143 or e.errorCode == 3221226505) and not pc_tile:
|
||||||
log.ODM_WARNING("OpenMVS ran out of memory, we're going to turn on tiling to see if we can process this.")
|
log.ODM_WARNING("OpenMVS ran out of memory, we're going to turn on tiling to see if we can process this.")
|
||||||
pc_tile = True
|
pc_tile = True
|
||||||
config.append("--fusion-mode 1")
|
config.append("--fusion-mode 1")
|
||||||
|
@ -127,7 +127,7 @@ class ODMOpenMVSStage(types.ODM_Stage):
|
||||||
|
|
||||||
subscene_densify_ini_file = os.path.join(tree.openmvs, 'subscene-config.ini')
|
subscene_densify_ini_file = os.path.join(tree.openmvs, 'subscene-config.ini')
|
||||||
with open(subscene_densify_ini_file, 'w+') as f:
|
with open(subscene_densify_ini_file, 'w+') as f:
|
||||||
f.write("Optimize = 0\nEstimation Geometric Iters = 0\n")
|
f.write("Optimize = 0\nEstimation Geometric Iters = 0\nMin Views Filter = 1\n")
|
||||||
|
|
||||||
config = [
|
config = [
|
||||||
"--sub-scene-area 660000", # 8000
|
"--sub-scene-area 660000", # 8000
|
||||||
|
@ -223,7 +223,7 @@ class ODMOpenMVSStage(types.ODM_Stage):
|
||||||
try:
|
try:
|
||||||
system.run('"%s" %s' % (context.omvs_densify_path, ' '.join(config + gpu_config + extra_config)))
|
system.run('"%s" %s' % (context.omvs_densify_path, ' '.join(config + gpu_config + extra_config)))
|
||||||
except system.SubprocessException as e:
|
except system.SubprocessException as e:
|
||||||
if e.errorCode == 137 or e.errorCode == 3221226505:
|
if e.errorCode == 137 or e.errorCode == 143 or e.errorCode == 3221226505:
|
||||||
log.ODM_WARNING("OpenMVS filtering ran out of memory, visibility checks will be skipped.")
|
log.ODM_WARNING("OpenMVS filtering ran out of memory, visibility checks will be skipped.")
|
||||||
skip_filtering()
|
skip_filtering()
|
||||||
else:
|
else:
|
||||||
|
|
Ładowanie…
Reference in New Issue