#!/usr/bin/env python # coding: utf-8 # # Pandora: a new stereo matching framework # # # *Cournet, M., Sarrazin, E., Dumas, L., Michel, J., Guinet, J., Youssefi, D., Defonte, V., Fardet, Q., 2020. Ground-truth generation and disparity estimation for optical satellite imagery. ISPRS - International Archives of the Photogrammetry, Remote Sensing and Spatial Information Sciences.* # # Introduction and basic usage # #### Imports and external functions # In[ ]: import io from IPython.display import Image, display import io import matplotlib.pyplot as plt import numpy as np import os from pathlib import Path import rasterio # In[ ]: from snippets.utils import * # In[ ]: def plot_image(img, title=None, output_dir=None, cmap="viridis"): fig = plt.figure() plt.title(title) plt.imshow(img, cmap=cmap, vmin=np.min(img), vmax=np.max(img)) plt.colorbar() if output_dir is not None: fig.savefig(os.path.join(output_dir,title + '.pdf')) # In[ ]: def plot_state_machine(machine): stream = io.BytesIO() try: pandora_machine.get_graph().draw(stream, prog='dot', format='png') display(Image(stream.getvalue())) except: print("It is not possible to show the graphic of the state machine. To solve it, please install graphviz on your system (apt-get install graphviz if operating in Linux) and install python package with pip install graphviz") # # What is Pandora ? # # * Pandora is a Toolbox to estimate disparity # * It is inspired by the work of [Scharstein 2002] # * Pandora embeds several state-of-art algorithms # * It is easy to configure and modular # * Will be used in the 3D reconstruction pipeline CARS for CO3D mission # # [Scharstein 2002] *A Taxonomy and Evaluation of Dense Two-Frame Stereo Correspondence Algorithms*, D. Scharstein and R. Szeliski, # vol. 47, International Journal of Computer Vision}, 2002 # # ## Inputs # # * Stereo rectified image pair (with associated masks) # * Disparity range to explore # * Configuration file # # ## Outputs # # * Disparity and validity maps in left image geometry # * Disparity and validity maps in righ image geometry (*optional*) # ## Pandora's pipeline # # Pandora provides the following steps: # * matching cost computation (**mandatory**) # * cost aggregation # * cost optimization # * disparity computation (**mandatory**) # * subpixel disparity refinement # * disparity filtering # * validation # * multiscale processing # # ### Available implementations for each step # # | Step | Algorithms implemented | # |:--------------------------|:-----------------------| # | Matching cost computation | Census / SAD / SSD / ZNNC / MC-CNN | # | Cost aggregation | Cross Based Cost Aggregation | # | Cost optimization | SGM | # | Disparity computation | Winner-Take-All | # | Subpixel disparity refinement | Vfit / Quadratic | # | Disparity filtering | Median / Bilateral | # | Validation | Cross checking | # | Multiscale | Fixed zoom pyramid | # # Pandora execution options with state machine # #### Imports of pandora # In[ ]: # Load pandora imports import pandora from pandora.img_tools import read_img from pandora.check_json import check_pipeline_section, concat_conf, memory_consumption_estimation from pandora.state_machine import PandoraMachine from pandora import import_plugin, check_conf # #### (Optional) If Pandora plugins are to be used, import them # Available Pandora Plugins include : # - MC-CNN Matching cost computation # - SGM Optimization # In[ ]: # Load plugins import_plugin() # #### Load and visualize input data # Provide image path # In[ ]: # Paths to left and right images img_left_path = "data/Cones_LEFT.tif" img_right_path = "data/Cones_RIGHT.tif" # Paths to masks (None if not provided) left_mask_path = None right_mask_path = None # Provide image configuration # In[ ]: image_cfg = {'image': {'no_data_left': np.nan, 'no_data_right': np.nan}} # Provide output directory to write results # In[ ]: output_dir = os.path.join(os.getcwd(),"output") # If necessary, create output dir Path(output_dir).mkdir(exist_ok=True,parents=True) # Convert input data to dataset # In[ ]: img_left = read_img(img_left_path, no_data=image_cfg['image']['no_data_left'], mask=left_mask_path) img_right = read_img(img_right_path, no_data=image_cfg['image']['no_data_right'], mask=right_mask_path) # Visualize input data # In[ ]: plot_image(img_left.im, "Left input image", output_dir, cmap="gray") # # Option 1 : trigger all the steps of the machine at ones # #### Instantiate the machine # In[ ]: pandora_machine = PandoraMachine() # #### Define pipeline configuration # In[ ]: user_pipeline_cfg = { 'pipeline':{ "right_disp_map": { "method": "accurate" }, "matching_cost" : { "matching_cost_method": "zncc", "window_size": 5, "subpix": 4 }, "disparity": { "disparity_method": "wta", "invalid_disparity": "NaN" }, "refinement": { "refinement_method": "quadratic" }, "filter": { "filter_method": "median" }, "validation": { "validation_method": "cross_checking" }, "filter.this_time_after_validation" : { "filter_method": "median", "filter_size": 3 } } } # Disparity interval used # In[ ]: disp_min = -60 disp_max = 0 # #### Check the configuration and sequence of steps # In[ ]: checked_cfg = check_pipeline_section(user_pipeline_cfg, pandora_machine) # In[ ]: pipeline_cfg = checked_cfg['pipeline'] print(pipeline_cfg) # #### Estimate the memory consumption of the pipeline # In[ ]: min_mem_consump, max_mem_consump = memory_consumption_estimation(user_pipeline_cfg, [img_left_path, disp_min, disp_max], pandora_machine) print("Estimated maximum memory consumption between {:.2f} GiB and {:.2f} GiB".format(min_mem_consump, max_mem_consump)) # #### Prepare the machine # In[ ]: pandora_machine.run_prepare(pipeline_cfg, img_left, img_right, disp_min, disp_max) # #### Trigger all the steps of the machine at ones # In[ ]: left_disparity, right_disparity = pandora.run(pandora_machine, img_left, img_right, disp_min, disp_max, pipeline_cfg) # Visualize output disparity map # In[ ]: plot_image(left_disparity.disparity_map, "Left disparity map", output_dir, cmap=pandora_cmap()) # # Option 2 : trigger the machine step by step # The implementation of Pandora with a state machine makes it possible to set up a more flexible pipeline, which makes it possible to choose via a configuration file the steps as well as the order of the steps that one wishes to follow in Pandora. # # Moreover, the state machine allows to run each step of the pipeline independently, giving the possibility to save and visualize the results after each step. # The state machine has three states : # * Begin # * Cost volume # * Disparity map # # Being the connections between them the different steps of the pipeline. # # #### Instantiate the machine # In[ ]: pandora_machine = PandoraMachine() # #### Define pipeline configuration # In[ ]: user_pipeline_cfg = { 'pipeline':{ "right_disp_map": { "method": "accurate" }, "matching_cost" : { "matching_cost_method": "zncc", "window_size": 5, "subpix": 4 }, "aggregation": { "aggregation_method": "cbca" }, "disparity": { "disparity_method": "wta", "invalid_disparity": "NaN" }, "refinement": { "refinement_method": "quadratic" }, "filter": { "filter_method": "median" }, "validation": { "validation_method": "cross_checking" } } } # Disparity interval used # In[ ]: disp_min = -60 disp_max = 0 # #### Check the pipeline configuration and sequence of steps # In[ ]: checked_cfg = check_pipeline_section(user_pipeline_cfg, pandora_machine) # In[ ]: print(checked_cfg) # In[ ]: pipeline_cfg = checked_cfg['pipeline'] # #### Estimate the memory consumption of the pipeline # In[ ]: min_mem_consump, max_mem_consump = memory_consumption_estimation(user_pipeline_cfg, [img_left_path, disp_min, disp_max], pandora_machine) print("Estimated maximum memory consumption between {:.2f} GiB and {:.2f} GiB".format(min_mem_consump, max_mem_consump)) # #### Prepare the machine # In[ ]: pandora_machine.run_prepare(pipeline_cfg, img_left, img_right, disp_min, disp_max) # #### Trigger the machine step by step # In[ ]: plot_state_machine(pandora_machine) # Run matching cost # In[ ]: pandora_machine.run('matching_cost', pipeline_cfg) # In[ ]: plot_state_machine(pandora_machine) # In[ ]: pandora_machine.run('aggregation', pipeline_cfg) # In[ ]: plot_state_machine(pandora_machine) # Run disparity # In[ ]: pandora_machine.run('disparity', pipeline_cfg) # In[ ]: plot_state_machine(pandora_machine) # Run refinement # In[ ]: pandora_machine.run('refinement', pipeline_cfg) # In[ ]: plot_state_machine(pandora_machine) # Run filter # In[ ]: pandora_machine.run('filter', pipeline_cfg) # In[ ]: plot_state_machine(pandora_machine) # Run validation # In[ ]: pandora_machine.run('validation', pipeline_cfg) # In[ ]: plot_state_machine(pandora_machine) # Visualize output disparity map # In[ ]: plot_image(pandora_machine.left_disparity.disparity_map, "Left disparity map", output_dir, cmap=pandora_cmap())