2018-08-08 16:04:12 +00:00
import os
import json
import numpy as np
2019-05-30 19:58:37 +00:00
import math
2018-08-11 16:45:21 +00:00
from repoze . lru import lru_cache
2018-08-08 19:41:08 +00:00
from opendm import log
2018-08-08 16:04:12 +00:00
2018-10-17 22:54:18 +00:00
def rounded_gsd ( reconstruction_json , default_value = None , ndigits = 0 , ignore_gsd = False ) :
"""
: param reconstruction_json path to OpenSfM ' s reconstruction.json
: return GSD value rounded . If GSD cannot be computed , or ignore_gsd is set , it returns a default value .
"""
if ignore_gsd :
return default_value
gsd = opensfm_reconstruction_average_gsd ( reconstruction_json )
if gsd is not None :
return round ( gsd , ndigits )
else :
return default_value
2018-10-19 22:09:26 +00:00
2020-02-04 17:56:20 +00:00
def image_max_size ( photos , target_resolution , reconstruction_json , gsd_error_estimate = 0.5 , ignore_gsd = False , has_gcp = False ) :
2019-05-30 19:58:37 +00:00
"""
: param photos images database
: param target_resolution resolution the user wants have in cm / pixel
: param reconstruction_json path to OpenSfM ' s reconstruction.json
: param gsd_error_estimate percentage of estimated error in the GSD calculation to set an upper bound on resolution .
: param ignore_gsd if set to True , simply return the largest side of the largest image in the images database .
: return A dimension in pixels calculated by taking the image_scale_factor and applying it to the size of the largest image .
Returned value is never higher than the size of the largest side of the largest image .
"""
max_width = 0
max_height = 0
if ignore_gsd :
isf = 1.0
else :
2020-02-04 17:56:20 +00:00
isf = image_scale_factor ( target_resolution , reconstruction_json , gsd_error_estimate , has_gcp = has_gcp )
2019-05-30 19:58:37 +00:00
for p in photos :
max_width = max ( p . width , max_width )
max_height = max ( p . height , max_height )
return int ( math . ceil ( max ( max_width , max_height ) * isf ) )
2020-02-04 17:56:20 +00:00
def image_scale_factor ( target_resolution , reconstruction_json , gsd_error_estimate = 0.5 , has_gcp = False ) :
2018-08-11 16:45:21 +00:00
"""
: param target_resolution resolution the user wants have in cm / pixel
: param reconstruction_json path to OpenSfM ' s reconstruction.json
: param gsd_error_estimate percentage of estimated error in the GSD calculation to set an upper bound on resolution .
: return A down - scale ( < = 1 ) value to apply to images to achieve the target resolution by comparing the current GSD of the reconstruction .
If a GSD cannot be computed , it just returns 1. Returned scale values are never higher than 1.
"""
2020-02-04 17:56:20 +00:00
gsd = opensfm_reconstruction_average_gsd ( reconstruction_json , use_all_shots = has_gcp )
2018-08-11 16:45:21 +00:00
if gsd is not None and target_resolution > 0 :
gsd = gsd * ( 1 + gsd_error_estimate )
return min ( 1 , gsd / target_resolution )
else :
return 1
2020-02-04 17:56:20 +00:00
def cap_resolution ( resolution , reconstruction_json , gsd_error_estimate = 0.1 , ignore_gsd = False , ignore_resolution = False , has_gcp = False ) :
2018-08-08 19:41:08 +00:00
"""
: param resolution resolution in cm / pixel
: param reconstruction_json path to OpenSfM ' s reconstruction.json
2018-08-11 16:45:21 +00:00
: param gsd_error_estimate percentage of estimated error in the GSD calculation to set an upper bound on resolution .
: param ignore_gsd when set to True , forces the function to just return resolution .
2018-08-08 19:41:08 +00:00
: return The max value between resolution and the GSD computed from the reconstruction .
2018-08-11 16:45:21 +00:00
If a GSD cannot be computed , or ignore_gsd is set to True , it just returns resolution . Units are in cm / pixel .
2018-08-08 19:41:08 +00:00
"""
2018-08-11 16:45:21 +00:00
if ignore_gsd :
return resolution
2020-02-04 17:56:20 +00:00
gsd = opensfm_reconstruction_average_gsd ( reconstruction_json , use_all_shots = has_gcp or ignore_resolution )
2018-08-08 19:41:08 +00:00
if gsd is not None :
2018-08-11 16:45:21 +00:00
gsd = gsd * ( 1 - gsd_error_estimate )
2020-02-04 17:56:20 +00:00
if gsd > resolution or ignore_resolution :
2018-08-11 16:45:21 +00:00
log . ODM_WARNING ( ' Maximum resolution set to GSD - {} % ( {} cm / pixel, requested resolution was {} cm / pixel) ' . format ( gsd_error_estimate * 100 , round ( gsd , 2 ) , round ( resolution , 2 ) ) )
2018-08-08 19:41:08 +00:00
return gsd
else :
return resolution
else :
2018-08-11 16:45:21 +00:00
log . ODM_WARNING ( ' Cannot calculate GSD, using requested resolution of {} ' . format ( round ( resolution , 2 ) ) )
2018-08-08 19:41:08 +00:00
return resolution
2018-08-11 16:45:21 +00:00
@lru_cache ( maxsize = None )
2020-02-04 17:56:20 +00:00
def opensfm_reconstruction_average_gsd ( reconstruction_json , use_all_shots = False ) :
2018-08-08 16:04:12 +00:00
"""
Computes the average Ground Sampling Distance of an OpenSfM reconstruction .
: param reconstruction_json path to OpenSfM ' s reconstruction.json
: return Ground Sampling Distance value ( cm / pixel ) or None if
a GSD estimate cannot be compute
"""
if not os . path . isfile ( reconstruction_json ) :
2018-10-19 22:09:26 +00:00
raise IOError ( reconstruction_json + " does not exist. " )
2018-08-08 16:04:12 +00:00
with open ( reconstruction_json ) as f :
data = json . load ( f )
# Calculate median height from sparse reconstruction
reconstruction = data [ 0 ]
point_heights = [ ]
for pointId in reconstruction [ ' points ' ] :
point = reconstruction [ ' points ' ] [ pointId ]
point_heights . append ( point [ ' coordinates ' ] [ 2 ] )
ground_height = np . median ( point_heights )
gsds = [ ]
for shotImage in reconstruction [ ' shots ' ] :
shot = reconstruction [ ' shots ' ] [ shotImage ]
2020-02-04 19:19:50 +00:00
if use_all_shots or shot [ ' gps_dop ' ] < 999999 :
camera = reconstruction [ ' cameras ' ] [ shot [ ' camera ' ] ]
shot_height = shot [ ' translation ' ] [ 2 ]
focal_ratio = camera . get ( ' focal ' , camera . get ( ' focal_x ' ) )
if not focal_ratio :
log . ODM_WARNING ( " Cannot parse focal values from %s . This is likely an unsupported camera model. " % reconstruction_json )
return None
gsds . append ( calculate_gsd_from_focal_ratio ( focal_ratio ,
shot_height - ground_height ,
camera [ ' width ' ] ) )
2018-08-08 16:04:12 +00:00
2018-08-11 16:45:21 +00:00
if len ( gsds ) > 0 :
2020-09-28 14:51:35 +00:00
mean = np . mean ( gsds )
if mean < 0 :
log . ODM_WARNING ( " Negative GSD estimated, this might indicate a flipped Z-axis. " )
return abs ( mean )
2018-08-11 16:45:21 +00:00
return None
2018-08-08 16:04:12 +00:00
def calculate_gsd ( sensor_width , flight_height , focal_length , image_width ) :
"""
: param sensor_width in millimeters
: param flight_height in meters
: param focal_length in millimeters
: param image_width in pixels
: return Ground Sampling Distance
>> > round ( calculate_gsd ( 13.2 , 100 , 8.8 , 5472 ) , 2 )
2.74
>> > calculate_gsd ( 13.2 , 100 , 0 , 2000 )
>> > calculate_gsd ( 13.2 , 100 , 8.8 , 0 )
"""
if sensor_width != 0 :
return calculate_gsd_from_focal_ratio ( focal_length / sensor_width ,
flight_height ,
image_width )
else :
return None
def calculate_gsd_from_focal_ratio ( focal_ratio , flight_height , image_width ) :
"""
: param focal_ratio focal length ( mm ) / sensor_width ( mm )
: param flight_height in meters
: param image_width in pixels
: return Ground Sampling Distance
"""
if focal_ratio == 0 or image_width == 0 :
return None
return ( ( flight_height * 100 ) / image_width ) / focal_ratio