2015-12-02 14:24:38 +00:00
import cv2
2018-09-13 20:56:49 +00:00
import exifread
2015-12-11 21:26:04 +00:00
import re
2019-04-29 20:18:08 +00:00
import os
2015-12-11 21:26:04 +00:00
from fractions import Fraction
2019-03-07 17:36:05 +00:00
from opendm import get_image_size
2019-06-20 19:39:49 +00:00
from opendm import location
from opendm . gcp import GCPFile
from pyproj import CRS
2019-12-07 22:05:59 +00:00
import xmltodict as x2d
2015-11-17 17:17:56 +00:00
import log
2015-11-26 12:15:02 +00:00
import io
2015-11-18 16:39:38 +00:00
import system
2015-12-11 21:26:04 +00:00
import context
2018-09-13 20:56:49 +00:00
import logging
2019-05-15 21:04:09 +00:00
from opendm . progress import progressbc
2016-02-26 18:50:12 +00:00
2015-12-10 17:17:39 +00:00
class ODM_Photo :
2015-11-17 17:17:56 +00:00
""" ODMPhoto - a class for ODMPhotos
"""
2016-02-26 18:50:12 +00:00
2019-03-07 17:36:05 +00:00
def __init__ ( self , path_file ) :
2015-11-17 17:17:56 +00:00
# general purpose
2015-11-27 16:48:15 +00:00
self . filename = io . extract_file_from_path_file ( path_file )
2015-11-19 12:01:15 +00:00
self . width = None
self . height = None
# other attributes
2016-07-27 14:27:34 +00:00
self . camera_make = ' '
self . camera_model = ' '
2017-06-23 15:49:24 +00:00
self . latitude = None
self . longitude = None
self . altitude = None
2019-12-07 22:05:59 +00:00
self . band_name = ' RGB '
2015-11-27 10:00:08 +00:00
# parse values from metadata
2019-03-07 17:36:05 +00:00
self . parse_exif_values ( path_file )
2015-11-17 17:17:56 +00:00
2015-11-27 16:48:15 +00:00
# print log message
2018-11-24 17:56:17 +00:00
log . ODM_DEBUG ( ' Loaded {} ' . format ( self ) )
def __str__ ( self ) :
2019-12-07 22:05:59 +00:00
return ' {} | camera: {} {} | dimensions: {} x {} | lat: {} | lon: {} | alt: {} | band: {} ' . format (
self . filename , self . camera_make , self . camera_model , self . width , self . height ,
self . latitude , self . longitude , self . altitude , self . band_name )
2019-03-07 17:36:05 +00:00
def parse_exif_values ( self , _path_file ) :
2018-09-13 20:56:49 +00:00
# Disable exifread log
logging . getLogger ( ' exifread ' ) . setLevel ( logging . CRITICAL )
with open ( _path_file , ' rb ' ) as f :
tags = exifread . process_file ( f , details = False )
2015-12-04 14:12:40 +00:00
try :
2018-09-13 20:56:49 +00:00
if ' Image Make ' in tags :
self . camera_make = tags [ ' Image Make ' ] . values . encode ( ' utf8 ' )
if ' Image Model ' in tags :
self . camera_model = tags [ ' Image Model ' ] . values . encode ( ' utf8 ' )
if ' GPS GPSAltitude ' in tags :
self . altitude = self . float_values ( tags [ ' GPS GPSAltitude ' ] ) [ 0 ]
if ' GPS GPSAltitudeRef ' in tags and self . int_values ( tags [ ' GPS GPSAltitudeRef ' ] ) [ 0 ] > 0 :
self . altitude * = - 1
if ' GPS GPSLatitude ' in tags and ' GPS GPSLatitudeRef ' in tags :
self . latitude = self . dms_to_decimal ( tags [ ' GPS GPSLatitude ' ] , tags [ ' GPS GPSLatitudeRef ' ] )
if ' GPS GPSLongitude ' in tags and ' GPS GPSLongitudeRef ' in tags :
self . longitude = self . dms_to_decimal ( tags [ ' GPS GPSLongitude ' ] , tags [ ' GPS GPSLongitudeRef ' ] )
except IndexError as e :
log . ODM_WARNING ( " Cannot read EXIF tags for %s : %s " % ( _path_file , e . message ) )
2016-02-26 18:50:12 +00:00
2019-12-07 22:05:59 +00:00
# Extract XMP tags
f . seek ( 0 )
xmp = self . get_xmp ( f )
# Find band name (if available)
for tags in xmp :
if ' Camera:BandName ' in tags :
self . band_name = str ( tags [ ' Camera:BandName ' ] ) . replace ( " " , " " )
break
2016-03-04 19:35:35 +00:00
2019-06-11 20:29:46 +00:00
self . width , self . height = get_image_size . get_image_size ( _path_file )
2019-12-07 22:05:59 +00:00
# From https://github.com/mapillary/OpenSfM/blob/master/opensfm/exif.py
def get_xmp ( self , file ) :
img_str = str ( file . read ( ) )
xmp_start = img_str . find ( ' <x:xmpmeta ' )
xmp_end = img_str . find ( ' </x:xmpmeta ' )
if xmp_start < xmp_end :
xmp_str = img_str [ xmp_start : xmp_end + 12 ]
xdict = x2d . parse ( xmp_str )
xdict = xdict . get ( ' x:xmpmeta ' , { } )
xdict = xdict . get ( ' rdf:RDF ' , { } )
xdict = xdict . get ( ' rdf:Description ' , { } )
if isinstance ( xdict , list ) :
return xdict
else :
return [ xdict ]
else :
return [ ]
2015-11-27 10:00:08 +00:00
2018-09-13 20:56:49 +00:00
def dms_to_decimal ( self , dms , sign ) :
2017-06-23 15:49:24 +00:00
""" Converts dms coords to decimal degrees """
2018-09-13 20:56:49 +00:00
degrees , minutes , seconds = self . float_values ( dms )
return ( - 1 if sign . values [ 0 ] in ' SWsw ' else 1 ) * (
degrees +
minutes / 60 +
seconds / 3600
2017-06-23 15:49:24 +00:00
)
2018-09-13 20:56:49 +00:00
def float_values ( self , tag ) :
return map ( lambda v : float ( v . num ) / float ( v . den ) , tag . values )
def int_values ( self , tag ) :
return map ( int , tag . values )
2015-11-27 10:00:08 +00:00
2019-03-07 17:36:05 +00:00
2015-12-10 17:17:39 +00:00
class ODM_Reconstruction ( object ) :
2019-06-20 19:39:49 +00:00
def __init__ ( self , photos ) :
self . photos = photos
2018-04-19 02:03:54 +00:00
self . georef = None
2019-06-21 18:47:00 +00:00
self . gcp = None
2019-12-05 20:28:06 +00:00
self . multi_camera = self . detect_multi_camera ( )
def detect_multi_camera ( self ) :
"""
Looks at the reconstruction photos and determines if this
is a single or multi - camera setup .
"""
2019-12-07 22:05:59 +00:00
mc = { }
for p in self . photos :
if not p . band_name in mc :
mc [ p . band_name ] = [ ]
mc [ p . band_name ] . append ( p )
bands_count = len ( mc )
if bands_count > = 2 and bands_count < = 8 :
# Validate that all bands have the same number of images,
# otherwise this is not a multi-camera setup
img_per_band = len ( mc [ p . band_name ] )
for band in mc :
if len ( mc [ 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 , mc [ band ] [ 0 ] . filename , len ( mc [ band ] ) , img_per_band ) )
raise RuntimeError ( " Invalid multi-camera images " )
2019-12-05 20:28:06 +00:00
2019-12-07 22:05:59 +00:00
return mc
2019-12-05 20:28:06 +00:00
return None
2019-06-20 19:39:49 +00:00
def is_georeferenced ( self ) :
return self . georef is not None
2019-06-21 18:47:00 +00:00
def georeference_with_gcp ( self , gcp_file , output_coords_file , output_gcp_file , rerun = False ) :
if not io . file_exists ( output_coords_file ) or not io . file_exists ( output_gcp_file ) or rerun :
2019-06-20 19:39:49 +00:00
gcp = GCPFile ( gcp_file )
if gcp . exists ( ) :
2019-06-21 18:47:00 +00:00
# Create coords file, we'll be using this later
# during georeferencing
2019-06-20 19:39:49 +00:00
with open ( output_coords_file , ' w ' ) as f :
coords_header = gcp . wgs84_utm_zone ( )
f . write ( coords_header + " \n " )
2019-06-28 15:10:08 +00:00
log . ODM_INFO ( " Generated coords file from GCP: %s " % coords_header )
2019-06-21 18:47:00 +00:00
# Convert GCP file to a UTM projection since the rest of the pipeline
# does not handle other SRS well.
rejected_entries = [ ]
2019-06-24 12:28:44 +00:00
utm_gcp = GCPFile ( gcp . create_utm_copy ( output_gcp_file , filenames = [ p . filename for p in self . photos ] , rejected_entries = rejected_entries , include_extras = False ) )
2019-06-21 18:47:00 +00:00
if not utm_gcp . exists ( ) :
raise RuntimeError ( " Could not project GCP file to UTM. Please double check your GCP file for mistakes. " )
for re in rejected_entries :
log . ODM_WARNING ( " GCP line ignored (image not found): %s " % str ( re ) )
if utm_gcp . entries_count ( ) > 0 :
log . ODM_INFO ( " %s GCP points will be used for georeferencing " % utm_gcp . entries_count ( ) )
else :
raise RuntimeError ( " A GCP file was provided, but no valid GCP entries could be used. Note that the GCP file is case sensitive ( \" .JPG \" is not the same as \" .jpg \" ). " )
self . gcp = utm_gcp
2019-06-20 19:39:49 +00:00
else :
log . ODM_WARNING ( " GCP file does not exist: %s " % gcp_file )
return
2018-01-26 19:38:26 +00:00
else :
2019-06-20 19:39:49 +00:00
log . ODM_INFO ( " Coordinates file already exist: %s " % output_coords_file )
2019-06-21 18:47:00 +00:00
log . ODM_INFO ( " GCP file already exist: %s " % output_gcp_file )
self . gcp = GCPFile ( output_gcp_file )
2019-06-20 19:39:49 +00:00
self . georef = ODM_GeoRef . FromCoordsFile ( output_coords_file )
return self . georef
2019-06-21 18:47:00 +00:00
def georeference_with_gps ( self , images_path , output_coords_file , rerun = False ) :
2019-06-20 19:39:49 +00:00
try :
2019-06-21 18:47:00 +00:00
if not io . file_exists ( output_coords_file ) or rerun :
2019-06-21 19:51:27 +00:00
location . extract_utm_coords ( self . photos , images_path , output_coords_file )
2019-06-20 19:39:49 +00:00
else :
log . ODM_INFO ( " Coordinates file already exist: %s " % output_coords_file )
self . georef = ODM_GeoRef . FromCoordsFile ( output_coords_file )
except :
log . ODM_WARNING ( ' Could not generate coordinates file. An orthophoto will not be generated. ' )
2019-06-21 19:33:10 +00:00
self . gcp = GCPFile ( None )
2019-06-20 19:39:49 +00:00
return self . georef
def save_proj_srs ( self , file ) :
# Save proj to file for future use (unless this
# dataset is not georeferenced)
if self . is_georeferenced ( ) :
with open ( file , ' w ' ) as f :
f . write ( self . georef . proj4 ( ) )
class ODM_GeoRef ( object ) :
@staticmethod
def FromProj ( projstring ) :
return ODM_GeoRef ( CRS . from_proj4 ( projstring ) )
2018-01-26 19:38:26 +00:00
2019-06-20 19:39:49 +00:00
@staticmethod
def FromCoordsFile ( coords_file ) :
2018-01-26 19:38:26 +00:00
# check for coordinate file existence
2019-06-20 19:39:49 +00:00
if not io . file_exists ( coords_file ) :
log . ODM_WARNING ( ' Could not find file %s ' % coords_file )
2018-01-26 19:38:26 +00:00
return
2019-06-20 19:39:49 +00:00
srs = None
with open ( coords_file ) as f :
2018-01-26 19:38:26 +00:00
# extract reference system and utm zone from first line.
# We will assume the following format:
# 'WGS84 UTM 17N' or 'WGS84 UTM 17N \n'
line = f . readline ( ) . rstrip ( )
2019-06-20 19:39:49 +00:00
srs = location . parse_srs_header ( line )
2015-12-10 17:17:39 +00:00
2019-06-20 19:39:49 +00:00
return ODM_GeoRef ( srs )
2015-12-10 17:17:39 +00:00
2019-06-20 19:39:49 +00:00
def __init__ ( self , srs ) :
self . srs = srs
2015-12-10 17:17:39 +00:00
self . utm_east_offset = 0
self . utm_north_offset = 0
2018-01-26 19:38:26 +00:00
self . transform = [ ]
2016-04-05 20:10:02 +00:00
2019-06-20 19:39:49 +00:00
def proj4 ( self ) :
return self . srs . to_proj4 ( )
def valid_utm_offsets ( self ) :
return self . utm_east_offset and self . utm_north_offset
2016-04-05 20:10:02 +00:00
2019-06-20 19:39:49 +00:00
def extract_offsets ( self , geo_sys_file ) :
if not io . file_exists ( geo_sys_file ) :
log . ODM_ERROR ( ' Could not find file %s ' % geo_sys_file )
2015-12-10 17:17:39 +00:00
return
2019-06-20 19:39:49 +00:00
with open ( geo_sys_file ) as f :
2018-01-26 19:38:26 +00:00
offsets = f . readlines ( ) [ 1 ] . split ( ' ' )
2018-04-25 14:00:56 +00:00
self . utm_east_offset = float ( offsets [ 0 ] )
self . utm_north_offset = float ( offsets [ 1 ] )
2016-04-05 20:10:02 +00:00
2019-06-20 19:39:49 +00:00
def parse_transformation_matrix ( self , matrix_file ) :
if not io . file_exists ( matrix_file ) :
log . ODM_ERROR ( ' Could not find file %s ' % matrix_file )
2018-01-26 19:38:26 +00:00
return
# Create a nested list for the transformation matrix
2019-06-20 19:39:49 +00:00
with open ( matrix_file ) as f :
2018-01-26 19:38:26 +00:00
for line in f :
2018-06-27 18:32:49 +00:00
# Handle matrix formats that either
2018-04-25 14:00:56 +00:00
# have leading or trailing brakets or just plain numbers.
line = re . sub ( r " [ \ [ \ ],] " , " " , line ) . strip ( )
2018-01-26 19:38:26 +00:00
self . transform + = [ [ float ( i ) for i in line . split ( ) ] ]
self . utm_east_offset = self . transform [ 0 ] [ 3 ]
self . utm_north_offset = self . transform [ 1 ] [ 3 ]
2016-02-25 20:02:48 +00:00
2015-12-10 17:17:39 +00:00
class ODM_Tree ( object ) :
2019-06-30 14:21:37 +00:00
def __init__ ( self , root_path , gcp_file = None ) :
2016-02-26 18:50:12 +00:00
# root path to the project
2015-12-02 14:24:38 +00:00
self . root_path = io . absolute_path_file ( root_path )
2019-06-30 14:21:37 +00:00
self . input_images = io . join_paths ( self . root_path , ' images ' )
2015-12-02 14:24:38 +00:00
2016-02-26 18:50:12 +00:00
# modules paths
2015-12-02 14:24:38 +00:00
# here are defined where all modules should be located in
# order to keep track all files al directories during the
# whole reconstruction process.
self . dataset_raw = io . join_paths ( self . root_path , ' images ' )
self . opensfm = io . join_paths ( self . root_path , ' opensfm ' )
2018-12-02 17:45:26 +00:00
self . mve = io . join_paths ( self . root_path , ' mve ' )
2015-12-02 14:24:38 +00:00
self . odm_meshing = io . join_paths ( self . root_path , ' odm_meshing ' )
self . odm_texturing = io . join_paths ( self . root_path , ' odm_texturing ' )
2017-04-05 17:56:48 +00:00
self . odm_25dtexturing = io . join_paths ( self . root_path , ' odm_texturing_25d ' )
2015-12-02 14:24:38 +00:00
self . odm_georeferencing = io . join_paths ( self . root_path , ' odm_georeferencing ' )
2017-04-05 17:56:48 +00:00
self . odm_25dgeoreferencing = io . join_paths ( self . root_path , ' odm_25dgeoreferencing ' )
2019-04-03 18:47:06 +00:00
self . odm_filterpoints = io . join_paths ( self . root_path , ' odm_filterpoints ' )
2015-12-02 14:24:38 +00:00
self . odm_orthophoto = io . join_paths ( self . root_path , ' odm_orthophoto ' )
2016-02-26 18:50:12 +00:00
# important files paths
2016-02-29 14:45:00 +00:00
# benchmarking
self . benchmarking = io . join_paths ( self . root_path , ' benchmark.txt ' )
2018-01-26 19:38:26 +00:00
self . dataset_list = io . join_paths ( self . root_path , ' img_list.txt ' )
2016-02-29 14:45:00 +00:00
2015-12-02 14:24:38 +00:00
# opensfm
2017-03-22 22:22:24 +00:00
self . opensfm_tracks = io . join_paths ( self . opensfm , ' tracks.csv ' )
2015-12-02 14:24:38 +00:00
self . opensfm_bundle = io . join_paths ( self . opensfm , ' bundle_r000.out ' )
self . opensfm_bundle_list = io . join_paths ( self . opensfm , ' list_r000.out ' )
self . opensfm_image_list = io . join_paths ( self . opensfm , ' image_list.txt ' )
self . opensfm_reconstruction = io . join_paths ( self . opensfm , ' reconstruction.json ' )
2017-04-06 05:31:03 +00:00
self . opensfm_reconstruction_nvm = io . join_paths ( self . opensfm , ' reconstruction.nvm ' )
2016-09-30 13:08:56 +00:00
self . opensfm_model = io . join_paths ( self . opensfm , ' depthmaps/merged.ply ' )
2018-01-26 19:38:26 +00:00
self . opensfm_transformation = io . join_paths ( self . opensfm , ' geocoords_transformation.txt ' )
2015-12-10 11:01:41 +00:00
2018-12-02 17:45:26 +00:00
# mve
self . mve_model = io . join_paths ( self . mve , ' mve_dense_point_cloud.ply ' )
self . mve_views = io . join_paths ( self . mve , ' views ' )
2018-06-27 18:32:49 +00:00
2019-04-03 18:47:06 +00:00
# filter points
self . filtered_point_cloud = io . join_paths ( self . odm_filterpoints , " point_cloud.ply " )
2015-12-02 14:24:38 +00:00
# odm_meshing
2015-12-10 11:01:41 +00:00
self . odm_mesh = io . join_paths ( self . odm_meshing , ' odm_mesh.ply ' )
self . odm_meshing_log = io . join_paths ( self . odm_meshing , ' odm_meshing_log.txt ' )
2017-04-05 17:56:48 +00:00
self . odm_25dmesh = io . join_paths ( self . odm_meshing , ' odm_25dmesh.ply ' )
self . odm_25dmeshing_log = io . join_paths ( self . odm_meshing , ' odm_25dmeshing_log.txt ' )
2016-02-26 18:50:12 +00:00
2016-03-24 17:35:29 +00:00
# texturing
2016-03-03 11:58:25 +00:00
self . odm_texturing_undistorted_image_path = io . join_paths (
self . odm_texturing , ' undistorted ' )
2017-04-05 17:56:48 +00:00
self . odm_textured_model_obj = ' odm_textured_model.obj '
self . odm_textured_model_mtl = ' odm_textured_model.mtl '
# Log is only used by old odm_texturing
self . odm_texuring_log = ' odm_texturing_log.txt '
2015-12-10 11:01:41 +00:00
# odm_georeferencing
2015-12-10 17:17:39 +00:00
self . odm_georeferencing_latlon = io . join_paths (
self . odm_georeferencing , ' latlon.txt ' )
2015-12-10 11:01:41 +00:00
self . odm_georeferencing_coords = io . join_paths (
2018-03-03 16:48:43 +00:00
self . odm_georeferencing , ' coords.txt ' )
2018-02-01 16:10:32 +00:00
self . odm_georeferencing_gcp = gcp_file or io . find ( ' gcp_list.txt ' , self . root_path )
2019-06-21 18:47:00 +00:00
self . odm_georeferencing_gcp_utm = io . join_paths ( self . odm_georeferencing , ' gcp_list_utm.txt ' )
2015-12-10 11:01:41 +00:00
self . odm_georeferencing_utm_log = io . join_paths (
self . odm_georeferencing , ' odm_georeferencing_utm_log.txt ' )
2017-04-05 17:56:48 +00:00
self . odm_georeferencing_log = ' odm_georeferencing_log.txt '
2017-06-12 11:15:32 +00:00
self . odm_georeferencing_transform_file = ' odm_georeferencing_transform.txt '
2018-03-03 16:48:43 +00:00
self . odm_georeferencing_proj = ' proj.txt '
2017-04-05 17:56:48 +00:00
self . odm_georeferencing_model_txt_geo = ' odm_georeferencing_model_geo.txt '
self . odm_georeferencing_model_obj_geo = ' odm_textured_model_geo.obj '
2016-02-25 19:51:03 +00:00
self . odm_georeferencing_xyz_file = io . join_paths (
2016-02-25 20:02:48 +00:00
self . odm_georeferencing , ' odm_georeferenced_model.csv ' )
2017-04-06 17:59:26 +00:00
self . odm_georeferencing_las_json = io . join_paths (
self . odm_georeferencing , ' las.json ' )
2018-06-18 13:57:20 +00:00
self . odm_georeferencing_model_laz = io . join_paths (
2018-06-17 13:51:37 +00:00
self . odm_georeferencing , ' odm_georeferenced_model.laz ' )
2019-02-22 20:28:37 +00:00
self . odm_georeferencing_model_las = io . join_paths (
self . odm_georeferencing , ' odm_georeferenced_model.las ' )
2017-04-06 13:06:09 +00:00
self . odm_georeferencing_dem = io . join_paths (
self . odm_georeferencing , ' odm_georeferencing_model_dem.tif ' )
2015-12-10 11:01:41 +00:00
2015-12-02 14:24:38 +00:00
# odm_orthophoto
2019-10-24 15:48:21 +00:00
self . odm_orthophoto_render = io . join_paths ( self . odm_orthophoto , ' odm_orthophoto_render.tif ' )
2016-02-23 17:47:43 +00:00
self . odm_orthophoto_tif = io . join_paths ( self . odm_orthophoto , ' odm_orthophoto.tif ' )
2016-08-10 22:46:19 +00:00
self . odm_orthophoto_corners = io . join_paths ( self . odm_orthophoto , ' odm_orthophoto_corners.txt ' )
2015-12-10 11:01:41 +00:00
self . odm_orthophoto_log = io . join_paths ( self . odm_orthophoto , ' odm_orthophoto_log.txt ' )
2016-02-23 18:23:34 +00:00
self . odm_orthophoto_tif_log = io . join_paths ( self . odm_orthophoto , ' gdal_translate_log.txt ' )
2017-06-23 15:20:46 +00:00
2019-04-23 01:42:32 +00:00
# Split-merge
self . submodels_path = io . join_paths ( self . root_path , ' submodels ' )
2019-05-31 16:12:59 +00:00
# Tiles
self . entwine_pointcloud = self . path ( " entwine_pointcloud " )
2017-06-23 20:15:13 +00:00
def path ( self , * args ) :
2019-04-29 20:18:08 +00:00
return os . path . join ( self . root_path , * args )
2019-04-22 19:14:39 +00:00
class ODM_Stage :
2019-05-15 21:04:09 +00:00
def __init__ ( self , name , args , progress = 0.0 , * * params ) :
2019-04-22 19:14:39 +00:00
self . name = name
self . args = args
2019-05-15 21:04:09 +00:00
self . progress = progress
2019-04-22 19:14:39 +00:00
self . params = params
if self . params is None :
self . params = { }
self . next_stage = None
2019-05-15 21:04:09 +00:00
self . prev_stage = None
2019-04-22 19:14:39 +00:00
def connect ( self , stage ) :
self . next_stage = stage
2019-05-15 21:04:09 +00:00
stage . prev_stage = self
2019-04-22 19:14:39 +00:00
return stage
def rerun ( self ) :
"""
Does this stage need to be rerun ?
"""
2019-04-23 22:01:14 +00:00
return ( self . args . rerun is not None and self . args . rerun == self . name ) or \
2019-04-22 19:14:39 +00:00
( self . args . rerun_all ) or \
2019-04-23 22:01:14 +00:00
( self . args . rerun_from is not None and self . name in self . args . rerun_from )
2019-04-22 19:14:39 +00:00
def run ( self , outputs = { } ) :
start_time = system . now_raw ( )
log . ODM_INFO ( ' Running %s stage ' % self . name )
self . process ( self . args , outputs )
# The tree variable should always be populated at this point
if outputs . get ( ' tree ' ) is None :
raise Exception ( " Assert violation: tree variable is missing from outputs dictionary. " )
if self . args . time :
system . benchmark ( start_time , outputs [ ' tree ' ] . benchmarking , self . name )
log . ODM_INFO ( ' Finished %s stage ' % self . name )
2019-05-15 21:04:09 +00:00
self . update_progress_end ( )
2019-04-22 19:14:39 +00:00
# Last stage?
2019-04-29 02:48:49 +00:00
if self . args . end_with == self . name or self . args . rerun == self . name :
2019-04-22 19:23:02 +00:00
log . ODM_INFO ( " No more stages to run " )
return
2019-04-22 19:14:39 +00:00
# Run next stage?
elif self . next_stage is not None :
self . next_stage . run ( outputs )
2019-05-15 21:04:09 +00:00
def delta_progress ( self ) :
if self . prev_stage :
return max ( 0.0 , self . progress - self . prev_stage . progress )
else :
return max ( 0.0 , self . progress )
def previous_stages_progress ( self ) :
2019-05-21 16:38:30 +00:00
if self . prev_stage :
return max ( 0.0 , self . prev_stage . progress )
else :
return 0.0
2019-05-15 21:04:09 +00:00
def update_progress_end ( self ) :
self . update_progress ( 100.0 )
def update_progress ( self , progress ) :
2019-05-15 22:01:46 +00:00
progress = max ( 0.0 , min ( 100.0 , progress ) )
2019-05-15 21:04:09 +00:00
progressbc . send_update ( self . previous_stages_progress ( ) +
2019-05-20 20:29:51 +00:00
( self . delta_progress ( ) / 100.0 ) * float ( progress ) )
2019-05-15 21:04:09 +00:00
2019-04-22 19:14:39 +00:00
def process ( self , args , outputs ) :
raise NotImplementedError