pull/1851/merge
s.zhong 2025-08-25 23:30:51 +09:00 zatwierdzone przez GitHub
commit d6ec39bea7
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
8 zmienionych plików z 186 dodań i 51 usunięć

56
opendm/georef.py 100644
Wyświetl plik

@ -0,0 +1,56 @@
import numpy as np
from numpy import ndarray
from typing import Tuple
from pyproj import Proj
from opensfm.geo import TopocentricConverter
def topocentric_to_georef(
reflat: float,
reflon: float,
refalt: float,
a_srs: str,
x: ndarray,
y: ndarray,
z: ndarray,
x_offset: float = 0,
y_offset: float = 0,
) -> Tuple[ndarray, ndarray, ndarray]:
reference = TopocentricConverter(reflat, reflon, refalt)
projection = Proj(a_srs)
lat, lon, alt = reference.to_lla(x, y, z)
easting, northing = projection(lon, lat)
return easting - x_offset, northing - y_offset, alt
class TopocentricToProj:
def __init__(self, reflat:float, reflon:float, refalt:float, a_srs:str):
self.reference = TopocentricConverter(reflat, reflon, refalt)
self.projection = Proj(a_srs)
def convert_array(self, arr:ndarray, offset_x:float=0, offset_y:float=0):
x, y, z = arr['X'], arr['Y'], arr['Z']
easting, northing, alt = self.convert_points(x, y, z, offset_x, offset_y)
arr['X'] = easting
arr['Y'] = northing
arr['Z'] = alt
return arr
def convert_points(self, x:ndarray, y:ndarray, z:ndarray, offset_x:float=0, offset_y:float=0):
lat, lon, alt = self.reference.to_lla(x, y, z)
easting, northing = self.projection(lon, lat)
return easting - offset_x, northing - offset_y, alt
def convert_obj(self, input_obj:str, output_obj:str, offset_x:float=0, offset_y:float=0):
with open(input_obj, 'r') as fin:
with open(output_obj, 'w') as fout:
lines = fin.readlines()
for line in lines:
if line.startswith("v "):
v = np.fromstring(line.strip()[2:] + " 1", sep=' ', dtype=float)
vt = self.convert_points(v[0], v[1], v[2], offset_x, offset_y)
fout.write("v " + " ".join(map(str, list(vt))) + '\n')
else:
fout.write(line)

Wyświetl plik

@ -633,9 +633,12 @@ class OSFMContext:
return result return result
def reference(self):
ds = DataSet(self.opensfm_project_path)
return ds.load_reference()
def name(self): def name(self):
return os.path.basename(os.path.abspath(self.path(".."))) return os.path.basename(os.path.abspath(self.path("..")))
def get_submodel_argv(args, submodels_path = None, submodel_name = None): def get_submodel_argv(args, submodels_path = None, submodel_name = None):
""" """

Wyświetl plik

@ -361,16 +361,20 @@ class ODM_Tree(object):
self.openmvs_model = os.path.join(self.openmvs, 'scene_dense_dense_filtered.ply') self.openmvs_model = os.path.join(self.openmvs, 'scene_dense_dense_filtered.ply')
# filter points # filter points
self.filtered_point_cloud_topo = os.path.join(self.odm_filterpoints, "point_cloud_topocentric.ply")
self.filtered_point_cloud = os.path.join(self.odm_filterpoints, "point_cloud.ply") self.filtered_point_cloud = os.path.join(self.odm_filterpoints, "point_cloud.ply")
self.filtered_point_cloud_stats = os.path.join(self.odm_filterpoints, "point_cloud_stats.json") self.filtered_point_cloud_stats = os.path.join(self.odm_filterpoints, "point_cloud_stats.json")
# odm_meshing # odm_meshing
self.odm_mesh_topo = os.path.join(self.odm_meshing, 'odm_mesh_topocentric.ply')
self.odm_mesh = os.path.join(self.odm_meshing, 'odm_mesh.ply') self.odm_mesh = os.path.join(self.odm_meshing, 'odm_mesh.ply')
self.odm_meshing_log = os.path.join(self.odm_meshing, 'odm_meshing_log.txt') self.odm_meshing_log = os.path.join(self.odm_meshing, 'odm_meshing_log.txt')
self.odm_25dmesh_topo = os.path.join(self.odm_meshing, 'odm_25dmesh_topocentric.ply')
self.odm_25dmesh = os.path.join(self.odm_meshing, 'odm_25dmesh.ply') self.odm_25dmesh = os.path.join(self.odm_meshing, 'odm_25dmesh.ply')
self.odm_25dmeshing_log = os.path.join(self.odm_meshing, 'odm_25dmeshing_log.txt') self.odm_25dmeshing_log = os.path.join(self.odm_meshing, 'odm_25dmeshing_log.txt')
# texturing # texturing
self.odm_textured_model_obj_topo = 'odm_textured_model_topocentric.obj'
self.odm_textured_model_obj = 'odm_textured_model_geo.obj' self.odm_textured_model_obj = 'odm_textured_model_geo.obj'
self.odm_textured_model_glb = 'odm_textured_model_geo.glb' self.odm_textured_model_glb = 'odm_textured_model_geo.glb'

Wyświetl plik

@ -33,7 +33,7 @@ class ODMMvsTexStage(types.ODM_Stage):
if not args.skip_3dmodel and (primary or args.use_3dmesh): if not args.skip_3dmodel and (primary or args.use_3dmesh):
nonloc.runs += [{ nonloc.runs += [{
'out_dir': os.path.join(tree.odm_texturing, subdir), 'out_dir': os.path.join(tree.odm_texturing, subdir),
'model': tree.odm_mesh, 'model': tree.odm_mesh_topo,
'nadir': False, 'nadir': False,
'primary': primary, 'primary': primary,
'nvm_file': nvm_file, 'nvm_file': nvm_file,
@ -43,7 +43,7 @@ class ODMMvsTexStage(types.ODM_Stage):
if not args.use_3dmesh: if not args.use_3dmesh:
nonloc.runs += [{ nonloc.runs += [{
'out_dir': os.path.join(tree.odm_25dtexturing, subdir), 'out_dir': os.path.join(tree.odm_25dtexturing, subdir),
'model': tree.odm_25dmesh, 'model': tree.odm_25dmesh_topo,
'nadir': True, 'nadir': True,
'primary': primary, 'primary': primary,
'nvm_file': nvm_file, 'nvm_file': nvm_file,
@ -69,9 +69,8 @@ class ODMMvsTexStage(types.ODM_Stage):
if not io.dir_exists(r['out_dir']): if not io.dir_exists(r['out_dir']):
system.mkdir_p(r['out_dir']) system.mkdir_p(r['out_dir'])
odm_textured_model_obj = os.path.join(r['out_dir'], tree.odm_textured_model_obj) odm_textured_model_obj = os.path.join(r['out_dir'], tree.odm_textured_model_obj_topo)
unaligned_obj = io.related_file_path(odm_textured_model_obj, postfix="_unaligned") unaligned_obj = io.related_file_path(odm_textured_model_obj, postfix="_unaligned")
if not io.file_exists(odm_textured_model_obj) or self.rerun(): if not io.file_exists(odm_textured_model_obj) or self.rerun():
log.ODM_INFO('Writing MVS Textured file in: %s' log.ODM_INFO('Writing MVS Textured file in: %s'
% odm_textured_model_obj) % odm_textured_model_obj)
@ -91,10 +90,12 @@ class ODMMvsTexStage(types.ODM_Stage):
if (r['nadir']): if (r['nadir']):
nadir = '--nadir_mode' nadir = '--nadir_mode'
# mvstex definitions # mvstex definitions
# mtl and texture files would be the same between topo and proj geomodel, so create with the final name
kwargs = { kwargs = {
'bin': context.mvstex_path, 'bin': context.mvstex_path,
'out_dir': os.path.join(r['out_dir'], "odm_textured_model_geo"), 'out_dir': os.path.join(r['out_dir'], 'odm_textured_model_geo'),
'model': r['model'], 'model': r['model'],
'dataTerm': 'gmi', 'dataTerm': 'gmi',
'outlierRemovalType': 'gauss_clamping', 'outlierRemovalType': 'gauss_clamping',
@ -127,6 +128,9 @@ class ODMMvsTexStage(types.ODM_Stage):
'{labelingFile} ' '{labelingFile} '
'{numThreads} ' '{numThreads} '
'{maxTextureSize} '.format(**kwargs)) '{maxTextureSize} '.format(**kwargs))
# update the obj file name to topo for further conversion
shutil.move(os.path.join(r['out_dir'], tree.odm_textured_model_obj), odm_textured_model_obj)
if r['primary'] and (not r['nadir'] or args.skip_3dmodel): if r['primary'] and (not r['nadir'] or args.skip_3dmodel):
# GlTF? # GlTF?
@ -149,7 +153,7 @@ class ODMMvsTexStage(types.ODM_Stage):
shutil.rmtree(packed_dir) shutil.rmtree(packed_dir)
try: try:
obj_pack(os.path.join(r['out_dir'], tree.odm_textured_model_obj), packed_dir, _info=log.ODM_INFO) obj_pack(os.path.join(r['out_dir'], tree.odm_textured_model_obj_topo), packed_dir, _info=log.ODM_INFO)
# Move packed/* into texturing folder # Move packed/* into texturing folder
system.delete_files(r['out_dir'], (".vec", )) system.delete_files(r['out_dir'], (".vec", ))

Wyświetl plik

@ -19,7 +19,7 @@ class ODMFilterPoints(types.ODM_Stage):
inputPointCloud = "" inputPointCloud = ""
# check if reconstruction was done before # check if reconstruction was done before
if not io.file_exists(tree.filtered_point_cloud) or self.rerun(): if not io.file_exists(tree.filtered_point_cloud_topo) or self.rerun():
if args.fast_orthophoto: if args.fast_orthophoto:
inputPointCloud = os.path.join(tree.opensfm, 'reconstruction.ply') inputPointCloud = os.path.join(tree.opensfm, 'reconstruction.ply')
else: else:
@ -49,14 +49,14 @@ class ODMFilterPoints(types.ODM_Stage):
else: else:
log.ODM_WARNING("Not a georeferenced reconstruction, will ignore --auto-boundary") log.ODM_WARNING("Not a georeferenced reconstruction, will ignore --auto-boundary")
point_cloud.filter(inputPointCloud, tree.filtered_point_cloud, tree.filtered_point_cloud_stats, point_cloud.filter(inputPointCloud, tree.filtered_point_cloud_topo, tree.filtered_point_cloud_stats,
standard_deviation=args.pc_filter, standard_deviation=args.pc_filter,
sample_radius=args.pc_sample, sample_radius=args.pc_sample,
boundary=boundary_offset(outputs.get('boundary'), reconstruction.get_proj_offset()), boundary=boundary_offset(outputs.get('boundary'), reconstruction.get_proj_offset()),
max_concurrency=args.max_concurrency) max_concurrency=args.max_concurrency)
# Quick check # Quick check
info = point_cloud.ply_info(tree.filtered_point_cloud) info = point_cloud.ply_info(tree.filtered_point_cloud_topo)
if info["vertex_count"] == 0: if info["vertex_count"] == 0:
extra_msg = '' extra_msg = ''
if 'boundary' in outputs: if 'boundary' in outputs:
@ -64,7 +64,7 @@ class ODMFilterPoints(types.ODM_Stage):
raise system.ExitException("Uh oh! We ended up with an empty point cloud. This means that the reconstruction did not succeed. Have you followed best practices for data acquisition? See https://docs.opendronemap.org/flying/%s" % extra_msg) raise system.ExitException("Uh oh! We ended up with an empty point cloud. This means that the reconstruction did not succeed. Have you followed best practices for data acquisition? See https://docs.opendronemap.org/flying/%s" % extra_msg)
else: else:
log.ODM_WARNING('Found a valid point cloud file in: %s' % log.ODM_WARNING('Found a valid point cloud file in: %s' %
tree.filtered_point_cloud) tree.filtered_point_cloud_topo)
if args.optimize_disk_space and inputPointCloud: if args.optimize_disk_space and inputPointCloud:
if os.path.isfile(inputPointCloud): if os.path.isfile(inputPointCloud):

Wyświetl plik

@ -9,6 +9,7 @@ import zipfile
import math import math
from collections import OrderedDict from collections import OrderedDict
from pyproj import CRS from pyproj import CRS
import pdal
from opendm import io from opendm import io
from opendm import log from opendm import log
@ -23,6 +24,7 @@ from opendm.osfm import OSFMContext
from opendm.boundary import as_polygon, export_to_bounds_files from opendm.boundary import as_polygon, export_to_bounds_files
from opendm.align import compute_alignment_matrix, transform_point_cloud, transform_obj from opendm.align import compute_alignment_matrix, transform_point_cloud, transform_obj
from opendm.utils import np_to_json from opendm.utils import np_to_json
from opendm.georef import TopocentricToProj
class ODMGeoreferencingStage(types.ODM_Stage): class ODMGeoreferencingStage(types.ODM_Stage):
def process(self, args, outputs): def process(self, args, outputs):
@ -113,19 +115,72 @@ class ODMGeoreferencingStage(types.ODM_Stage):
else: else:
log.ODM_WARNING("GCPs could not be loaded for writing to %s" % gcp_export_file) log.ODM_WARNING("GCPs could not be loaded for writing to %s" % gcp_export_file)
if reconstruction.is_georeferenced():
# prepare pipeline stage for topocentric to georeferenced conversion
octx = OSFMContext(tree.opensfm)
reference = octx.reference()
converter = TopocentricToProj(reference.lat, reference.lon, reference.alt, reconstruction.georef.proj4())
if not io.file_exists(tree.filtered_point_cloud) or self.rerun():
log.ODM_INFO("Georeferecing filtered point cloud")
if reconstruction.is_georeferenced():
pipeline = pdal.Reader.ply(tree.filtered_point_cloud_topo).pipeline()
pipeline.execute()
arr = pipeline.arrays[0]
arr = converter.convert_array(
arr,
reconstruction.georef.utm_east_offset,
reconstruction.georef.utm_north_offset
)
pipeline = pdal.Writer.ply(
filename = tree.filtered_point_cloud,
storage_mode = "little endian",
).pipeline(arr)
pipeline.execute()
else:
shutil.copy(tree.filtered_point_cloud_topo, tree.filtered_point_cloud)
def georefernce_textured_model(obj_in, obj_out):
log.ODM_INFO("Georeferecing textured model %s" % obj_in)
if not io.file_exists(obj_out) or self.rerun():
if reconstruction.is_georeferenced():
converter.convert_obj(
obj_in,
obj_out,
reconstruction.georef.utm_east_offset,
reconstruction.georef.utm_north_offset
)
else:
shutil.copy(obj_in, obj_out)
#TODO: maybe parallelize this
#TODO: gltf export? Should problably move the exporting process after this
for texturing in [tree.odm_texturing, tree.odm_25dtexturing]:
if reconstruction.multi_camera:
primary = get_primary_band_name(reconstruction.multi_camera, args.primary_band)
for band in reconstruction.multi_camera:
subdir = "" if band['name'] == primary else band['name'].lower()
obj_in = os.path.join(texturing, subdir, tree.odm_textured_model_obj_topo)
obj_out = os.path.join(texturing, subdir, tree.odm_textured_model_obj)
georefernce_textured_model(obj_in, obj_out)
else:
obj_in = os.path.join(texturing, tree.odm_textured_model_obj_topo)
obj_out = os.path.join(texturing, tree.odm_textured_model_obj)
transform_textured_model(obj_in, obj_out)
if not io.file_exists(tree.odm_georeferencing_model_laz) or self.rerun(): if not io.file_exists(tree.odm_georeferencing_model_laz) or self.rerun():
cmd = f'pdal translate -i "{tree.filtered_point_cloud}" -o \"{tree.odm_georeferencing_model_laz}\"' pipeline = pdal.Pipeline()
stages = ["ferry"] pipeline |= pdal.Reader.ply(tree.filtered_point_cloud)
params = [ pipeline |= pdal.Filter.ferry(dimensions="views => UserData")
'--filters.ferry.dimensions="views => UserData"'
]
if reconstruction.is_georeferenced(): if reconstruction.is_georeferenced():
log.ODM_INFO("Georeferencing point cloud") log.ODM_INFO("Georeferencing point cloud")
stages.append("transformation")
utmoffset = reconstruction.georef.utm_offset() utmoffset = reconstruction.georef.utm_offset()
pipeline |= pdal.Filter.transformation(
matrix=f"1 0 0 {utmoffset[0]} 0 1 0 {utmoffset[1]} 0 0 1 0 0 0 0 1"
)
# Establish appropriate las scale for export # Establish appropriate las scale for export
las_scale = 0.001 las_scale = 0.001
@ -148,27 +203,37 @@ class ODMGeoreferencingStage(types.ODM_Stage):
else: else:
log.ODM_INFO("No point_cloud_stats.json found. Using default las scale: %s" % las_scale) log.ODM_INFO("No point_cloud_stats.json found. Using default las scale: %s" % las_scale)
params += [ las_writer_def = {
f'--filters.transformation.matrix="1 0 0 {utmoffset[0]} 0 1 0 {utmoffset[1]} 0 0 1 0 0 0 0 1"', "filename": tree.odm_georeferencing_model_laz,
f'--writers.las.offset_x={reconstruction.georef.utm_east_offset}' , "a_srs": reconstruction.georef.proj4(),
f'--writers.las.offset_y={reconstruction.georef.utm_north_offset}', "offset_x": utmoffset[0],
f'--writers.las.scale_x={las_scale}', "offset_y": utmoffset[1],
f'--writers.las.scale_y={las_scale}', "offset_z": 0,
f'--writers.las.scale_z={las_scale}', "scale_x": las_scale,
'--writers.las.offset_z=0', "scale_y": las_scale,
f'--writers.las.a_srs="{reconstruction.georef.proj4()}"' # HOBU this should maybe be WKT "scale_z": las_scale,
] }
if reconstruction.has_gcp() and io.file_exists(gcp_geojson_zip_export_file): if reconstruction.has_gcp() and io.file_exists(gcp_geojson_zip_export_file):
if os.path.getsize(gcp_geojson_zip_export_file) <= 65535: if os.path.getsize(gcp_geojson_zip_export_file) <= 65535:
log.ODM_INFO("Embedding GCP info in point cloud") log.ODM_INFO("Embedding GCP info in point cloud")
params += [ las_writer_def["vlrs"] = json.dumps(
'--writers.las.vlrs="{\\\"filename\\\": \\\"%s\\\", \\\"user_id\\\": \\\"ODM\\\", \\\"record_id\\\": 2, \\\"description\\\": \\\"Ground Control Points (zip)\\\"}"' % gcp_geojson_zip_export_file.replace(os.sep, "/") {
] "filename": gcp_geojson_zip_export_file.replace(os.sep, "/"),
"user_id": "ODM",
"record_id": 2,
"description": "Ground Control Points (zip)"
}
)
else: else:
log.ODM_WARNING("Cannot embed GCP info in point cloud, %s is too large" % gcp_geojson_zip_export_file) log.ODM_WARNING("Cannot embed GCP info in point cloud, %s is too large" % gcp_geojson_zip_export_file)
system.run(cmd + ' ' + ' '.join(stages) + ' ' + ' '.join(params)) pipeline |= pdal.Writer.las(
**las_writer_def
)
pipeline.execute()
self.update_progress(50) self.update_progress(50)
@ -202,7 +267,10 @@ class ODMGeoreferencingStage(types.ODM_Stage):
export_to_bounds_files(outputs['boundary'], reconstruction.get_proj_srs(), bounds_json, bounds_gpkg) export_to_bounds_files(outputs['boundary'], reconstruction.get_proj_srs(), bounds_json, bounds_gpkg)
else: else:
log.ODM_INFO("Converting point cloud (non-georeferenced)") log.ODM_INFO("Converting point cloud (non-georeferenced)")
system.run(cmd + ' ' + ' '.join(stages) + ' ' + ' '.join(params)) pipeline |= pdal.Writer.las(
tree.odm_georeferencing_model_laz
)
pipeline.execute()
stats_dir = tree.path("opensfm", "stats", "codem") stats_dir = tree.path("opensfm", "stats", "codem")
@ -250,7 +318,7 @@ class ODMGeoreferencingStage(types.ODM_Stage):
except Exception as e: except Exception as e:
log.ODM_WARNING("Cannot transform textured model: %s" % str(e)) log.ODM_WARNING("Cannot transform textured model: %s" % str(e))
os.rename(unaligned_obj, obj) os.rename(unaligned_obj, obj)
#TODO: seems gltf file is not converted in alignment?
for texturing in [tree.odm_texturing, tree.odm_25dtexturing]: for texturing in [tree.odm_texturing, tree.odm_25dtexturing]:
if reconstruction.multi_camera: if reconstruction.multi_camera:
primary = get_primary_band_name(reconstruction.multi_camera, args.primary_band) primary = get_primary_band_name(reconstruction.multi_camera, args.primary_band)

Wyświetl plik

@ -19,11 +19,11 @@ class ODMeshingStage(types.ODM_Stage):
# Create full 3D model unless --skip-3dmodel is set # Create full 3D model unless --skip-3dmodel is set
if not args.skip_3dmodel: if not args.skip_3dmodel:
if not io.file_exists(tree.odm_mesh) or self.rerun(): if not io.file_exists(tree.odm_mesh_topo) or self.rerun():
log.ODM_INFO('Writing ODM Mesh file in: %s' % tree.odm_mesh) log.ODM_INFO('Writing ODM Mesh file in: %s' % tree.odm_mesh_topo)
mesh.screened_poisson_reconstruction(tree.filtered_point_cloud, mesh.screened_poisson_reconstruction(tree.filtered_point_cloud_topo,
tree.odm_mesh, tree.odm_mesh_topo,
depth=self.params.get('oct_tree'), depth=self.params.get('oct_tree'),
samples=self.params.get('samples'), samples=self.params.get('samples'),
maxVertexCount=self.params.get('max_vertex'), maxVertexCount=self.params.get('max_vertex'),
@ -31,16 +31,16 @@ class ODMeshingStage(types.ODM_Stage):
threads=max(1, self.params.get('max_concurrency') - 1)), # poissonrecon can get stuck on some machines if --threads == all cores threads=max(1, self.params.get('max_concurrency') - 1)), # poissonrecon can get stuck on some machines if --threads == all cores
else: else:
log.ODM_WARNING('Found a valid ODM Mesh file in: %s' % log.ODM_WARNING('Found a valid ODM Mesh file in: %s' %
tree.odm_mesh) tree.odm_mesh_topo)
self.update_progress(50) self.update_progress(50)
# Always generate a 2.5D mesh # Always generate a 2.5D mesh
# unless --use-3dmesh is set. # unless --use-3dmesh is set.
if not args.use_3dmesh: if not args.use_3dmesh:
if not io.file_exists(tree.odm_25dmesh) or self.rerun(): if not io.file_exists(tree.odm_25dmesh_topo) or self.rerun():
log.ODM_INFO('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh) log.ODM_INFO('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh_topo)
multiplier = math.pi / 2.0 multiplier = math.pi / 2.0
radius_steps = commands.get_dem_radius_steps(tree.filtered_point_cloud_stats, 3, args.orthophoto_resolution, multiplier=multiplier) radius_steps = commands.get_dem_radius_steps(tree.filtered_point_cloud_stats, 3, args.orthophoto_resolution, multiplier=multiplier)
@ -51,7 +51,7 @@ class ODMeshingStage(types.ODM_Stage):
if args.fast_orthophoto: if args.fast_orthophoto:
dsm_resolution *= 8.0 dsm_resolution *= 8.0
mesh.create_25dmesh(tree.filtered_point_cloud, tree.odm_25dmesh, mesh.create_25dmesh(tree.filtered_point_cloud_topo, tree.odm_25dmesh_topo,
radius_steps, radius_steps,
dsm_resolution=dsm_resolution, dsm_resolution=dsm_resolution,
depth=self.params.get('oct_tree'), depth=self.params.get('oct_tree'),
@ -63,5 +63,5 @@ class ODMeshingStage(types.ODM_Stage):
max_tiles=None if reconstruction.has_geotagged_photos() else math.ceil(len(reconstruction.photos) / 2)) max_tiles=None if reconstruction.has_geotagged_photos() else math.ceil(len(reconstruction.photos) / 2))
else: else:
log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' % log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' %
tree.odm_25dmesh) tree.odm_25dmesh_topo)

Wyświetl plik

@ -69,13 +69,13 @@ class ODMOpenSfMStage(types.ODM_Stage):
self.update_progress(75) self.update_progress(75)
# We now switch to a geographic CRS # We now switch to a geographic CRS
if reconstruction.is_georeferenced() and (not io.file_exists(tree.opensfm_topocentric_reconstruction) or self.rerun()): # if reconstruction.is_georeferenced() and (not io.file_exists(tree.opensfm_topocentric_reconstruction) or self.rerun()):
octx.run('export_geocoords --reconstruction --proj "%s" --offset-x %s --offset-y %s' % # octx.run('export_geocoords --reconstruction --proj "%s" --offset-x %s --offset-y %s' %
(reconstruction.georef.proj4(), reconstruction.georef.utm_east_offset, reconstruction.georef.utm_north_offset)) # (reconstruction.georef.proj4(), reconstruction.georef.utm_east_offset, reconstruction.georef.utm_north_offset))
shutil.move(tree.opensfm_reconstruction, tree.opensfm_topocentric_reconstruction) # shutil.move(tree.opensfm_reconstruction, tree.opensfm_topocentric_reconstruction)
shutil.move(tree.opensfm_geocoords_reconstruction, tree.opensfm_reconstruction) # shutil.move(tree.opensfm_geocoords_reconstruction, tree.opensfm_reconstruction)
else: # else:
log.ODM_WARNING("Will skip exporting %s" % tree.opensfm_geocoords_reconstruction) # log.ODM_WARNING("Will skip exporting %s" % tree.opensfm_geocoords_reconstruction)
self.update_progress(80) self.update_progress(80)