image_to_image.utils.physgen_benchmark
From: https://github.com/physicsgen/physicsgen/blob/main/eval_scripts/sound_metrics.py
Slightly adjusted by Tobia Ippolito
Original programmed by Martin Spitznagel
1""" 2From: https://github.com/physicsgen/physicsgen/blob/main/eval_scripts/sound_metrics.py 3 4Slightly adjusted by Tobia Ippolito 5 6Original programmed by Martin Spitznagel 7""" 8import argparse 9import os 10 11from PIL import Image 12import numpy as np 13import matplotlib.pyplot as plt 14import pandas as pd 15 16import numba 17from numba import jit 18 19from tqdm import tqdm 20 21# for black-white image -> osm conversion 22import itertools 23import cv2 24import shapely.geometry 25import xml.etree.ElementTree as ET 26 27 28def MAE(y_true, y_pred): 29 return np.mean(np.abs(y_true - y_pred)) 30 31def calc_mae(true_path, pred_path): 32 pred_noisemap = (1 - np.array( 33 Image.open(pred_path).convert("L"), 34 dtype=np.float32 35 ) / 255) * 100 36 37 true_noisemap = (1 - np.array( 38 Image.open(true_path).convert("L"), 39 dtype=np.float32 40 ) / 255) * 100 41 return MAE(true_noisemap, pred_noisemap) 42 43def calc_mape(true_path, pred_path): 44 # Load and process the predicted and true noise maps 45 pred_noisemap = (1 - np.array( 46 Image.open(pred_path).convert("L"), 47 dtype=np.float32 48 ) / 255) * 100 49 true_noisemap = (1 - np.array( 50 Image.open(true_path).convert("L"), 51 dtype=np.float32 52 ) / 255) * 100 53 54 # Initialize an error map with zeros 55 error_map = np.zeros_like(true_noisemap, dtype=np.float32) 56 57 # Find indices where true noisemap is not 0 58 nonzero_indices = true_noisemap != 0 59 60 # Calculate percentage error where true noisemap is not 0 61 error_map[nonzero_indices] = np.abs((true_noisemap[nonzero_indices] - pred_noisemap[nonzero_indices]) / true_noisemap[nonzero_indices]) * 100 62 63 # For positions where true noisemap is 0 but pred noisemap is not, set error to 100% 64 zero_true_indices = (true_noisemap == 0) & (pred_noisemap != 0) 65 error_map[zero_true_indices] = 100 66 67 # Calculate the MAPE over the whole image, ignoring positions where both are 0 68 return np.mean(error_map) 69 70 71@jit(nopython=True) 72def ray_tracing(image_size, image_map): 73 visibility_map = np.zeros((image_size, image_size)) 74 source = (image_size // 2, image_size // 2) 75 for x in range(image_size): 76 for y in range(image_size): 77 dx = x - source[0] 78 dy = y - source[1] 79 dist = np.sqrt(dx*dx + dy*dy) 80 steps = int(dist) 81 if steps == 0: 82 continue # Skip the source point itself 83 step_dx = dx / steps 84 step_dy = dy / steps 85 86 visible = True # Assume this point is visible unless proven otherwise 87 ray_x, ray_y = source 88 for _ in range(steps): 89 ray_x += step_dx 90 ray_y += step_dy 91 int_x, int_y = int(ray_x), int(ray_y) 92 if 0 <= int_x < image_size and 0 <= int_y < image_size: 93 if image_map[int_y, int_x] == 0: 94 visible = False 95 break 96 visibility_map[y, x] = visible 97 return visibility_map 98 99def compute_visibility(osm_path, image_size=256): 100 image_map = np.array(Image.open(osm_path).convert('L').resize((image_size, image_size))) 101 image_map = np.where(image_map > 0, 1, 0) 102 visibility_map = ray_tracing(image_size, image_map) 103 pixels_in_sight = np.logical_and(visibility_map == 1, image_map == 1) 104 pixels_not_in_sight = np.logical_and(visibility_map == 0, image_map == 1) 105 pixels_not_in_sight = np.where(image_map == 0, 0, pixels_not_in_sight) 106 pixels_in_sight = np.where(image_map == 0, 0, pixels_in_sight) 107 return pixels_in_sight, pixels_not_in_sight 108 109def masked_mae(true_labels, predictions): 110 # Convert to numpy arrays 111 true_labels = np.array(true_labels) 112 predictions = np.array(predictions) 113 114 # Create a mask where true_labels are not equal to -1 115 mask = true_labels != -1 116 117 # Filter arrays with the mask 118 true_labels = true_labels[mask] 119 predictions = predictions[mask] 120 121 # Compute the MAE and return 122 return MAE(true_labels, predictions) 123 124def masked_mape(true_labels, predictions): 125 # Convert to numpy arrays 126 true_labels = np.array(true_labels) 127 predictions = np.array(predictions) 128 129 # Create a mask to exclude -1 130 mask = true_labels != -1 131 132 # Apply the mask to filter arrays 133 true_labels_filtered = true_labels[mask] 134 predictions_filtered = predictions[mask] 135 136 # Initialize an error map with zeros 137 error_map = np.zeros_like(true_labels_filtered, dtype=np.float32) 138 139 # Find indices where true noisemap is not 0 140 nonzero_indices = true_labels_filtered != 0 141 142 # Calculate percentage error where true noisemap is not 0 143 error_map[nonzero_indices] = np.abs((true_labels_filtered[nonzero_indices] - predictions_filtered[nonzero_indices]) / true_labels_filtered[nonzero_indices]) * 100 144 145 # For positions where true noisemap is 0 but pred noisemap is not, set error to 100% 146 zero_true_indices = (true_labels_filtered == 0) & (predictions_filtered != 0) 147 error_map[zero_true_indices] = 100 148 149 # Calculate the MAPE over the whole image, ignoring positions where both are 0 150 return np.mean(error_map) 151 152def calculate_sight_error(true_path, pred_path, osm_path): 153 pred_soundmap = (1 - np.array( 154 Image.open(pred_path).convert("L"), 155 dtype=np.float32 156 ) / 255) * 100 157 true_soundmap = (1 - np.array( 158 Image.open(true_path).convert("L"), 159 dtype=np.float32 160 ) / 255) * 100 161 _, true_pixels_not_in_sight = compute_visibility(osm_path) 162 163 in_sight_soundmap = true_soundmap.copy() 164 not_in_sight_soundmap = true_soundmap.copy() 165 166 in_sight_pred_soundmap = pred_soundmap.copy() 167 not_in_sight_pred_soundmap = pred_soundmap.copy() 168 169 #only get the pixels in sight 170 for x in range(256): 171 for y in range(256): 172 if true_pixels_not_in_sight[y, x] == 0: 173 not_in_sight_soundmap[y, x] = -1 174 not_in_sight_pred_soundmap[y, x] = -1 175 else: 176 in_sight_soundmap[y, x] = -1 177 in_sight_pred_soundmap[y, x] = -1 178 179 return masked_mae(in_sight_soundmap, in_sight_pred_soundmap), masked_mae(not_in_sight_soundmap, not_in_sight_pred_soundmap), masked_mape(in_sight_soundmap, in_sight_pred_soundmap), masked_mape(not_in_sight_soundmap, not_in_sight_pred_soundmap) 180 181def evaluate_sample(true_path, pred_path, osm_path=None) -> (float, float, float, float): 182 mae = calc_mae(true_path, pred_path) 183 mape = calc_mape(true_path, pred_path) 184 185 mae_in_sight, mae_not_in_sight, mape_in_sight, mape_not_in_sight = None, None, None, None 186 if osm_path is not None: 187 mae_in_sight, mae_not_in_sight, mape_in_sight, mape_not_in_sight = calculate_sight_error(true_path, pred_path, osm_path) 188 return mae, mape, mae_in_sight, mae_not_in_sight, mape_in_sight, mape_not_in_sight 189 190# main function for evaluation 191if __name__ == "__main__": 192 193 parser = argparse.ArgumentParser() 194 parser.add_argument("--data_dir", type=str, default="data/true") 195 parser.add_argument("--pred_dir", type=str, default="data/pred") 196 parser.add_argument("--osm_dir", type=str, default="none") 197 parser.add_argument("--output", type=str, default="evaluation.csv") 198 args = parser.parse_args() 199 200 data_dir = args.data_dir 201 pred_dir = args.pred_dir 202 osm_dir = args.osm_dir 203 osm_dir = None if osm_dir.lower() == "none" else osm_dir 204 output = args.output 205 206 output_path, _ = os.path.split(output) 207 os.makedirs(output_path, exist_ok=True) 208 209 results = [] 210 211 # get files 212 file_names = os.listdir(data_dir) 213 214 # Use tqdm to create a progress bar for the loop 215 for sample_name in tqdm(file_names, total=len(file_names), desc="Evaluating samples"): 216 pred_ = os.path.join(pred_dir, sample_name) 217 real_ = os.path.join(data_dir, sample_name) 218 osm_ = os.path.join(osm_dir, sample_name) 219 220 # Check if prediction is available 221 if not os.path.exists(f"{pred_dir}/{sample_name}"): 222 print(f"Prediction for sample {sample_name} not found.") 223 print(f"{pred_dir}/{sample_name}") 224 continue 225 mae, mape, mae_in_sight, mae_not_in_sight, mape_in_sight, mape_not_in_sight = evaluate_sample(real_, pred_, osm_path=osm_) # adjust prediction naming if needed 226 results.append([sample_name, mae, mape, mae_in_sight, mae_not_in_sight, mape_in_sight, mape_not_in_sight]) 227 228 results_df = pd.DataFrame(results, columns=["sample_id", "MAE", "MAPE", "LoS_MAE", "NLoS_MAE", "LoS_wMAPE", "NLoS_wMAPE"]) 229 results_df.to_csv(output, index=False) 230 print(results_df[["MAE", "MAPE", "LoS_MAE", "NLoS_MAE", "LoS_wMAPE", "NLoS_wMAPE"]].describe())
def
MAE(y_true, y_pred):
def
calc_mae(true_path, pred_path):
32def calc_mae(true_path, pred_path): 33 pred_noisemap = (1 - np.array( 34 Image.open(pred_path).convert("L"), 35 dtype=np.float32 36 ) / 255) * 100 37 38 true_noisemap = (1 - np.array( 39 Image.open(true_path).convert("L"), 40 dtype=np.float32 41 ) / 255) * 100 42 return MAE(true_noisemap, pred_noisemap)
def
calc_mape(true_path, pred_path):
44def calc_mape(true_path, pred_path): 45 # Load and process the predicted and true noise maps 46 pred_noisemap = (1 - np.array( 47 Image.open(pred_path).convert("L"), 48 dtype=np.float32 49 ) / 255) * 100 50 true_noisemap = (1 - np.array( 51 Image.open(true_path).convert("L"), 52 dtype=np.float32 53 ) / 255) * 100 54 55 # Initialize an error map with zeros 56 error_map = np.zeros_like(true_noisemap, dtype=np.float32) 57 58 # Find indices where true noisemap is not 0 59 nonzero_indices = true_noisemap != 0 60 61 # Calculate percentage error where true noisemap is not 0 62 error_map[nonzero_indices] = np.abs((true_noisemap[nonzero_indices] - pred_noisemap[nonzero_indices]) / true_noisemap[nonzero_indices]) * 100 63 64 # For positions where true noisemap is 0 but pred noisemap is not, set error to 100% 65 zero_true_indices = (true_noisemap == 0) & (pred_noisemap != 0) 66 error_map[zero_true_indices] = 100 67 68 # Calculate the MAPE over the whole image, ignoring positions where both are 0 69 return np.mean(error_map)
@jit(nopython=True)
def
ray_tracing(image_size, image_map):
72@jit(nopython=True) 73def ray_tracing(image_size, image_map): 74 visibility_map = np.zeros((image_size, image_size)) 75 source = (image_size // 2, image_size // 2) 76 for x in range(image_size): 77 for y in range(image_size): 78 dx = x - source[0] 79 dy = y - source[1] 80 dist = np.sqrt(dx*dx + dy*dy) 81 steps = int(dist) 82 if steps == 0: 83 continue # Skip the source point itself 84 step_dx = dx / steps 85 step_dy = dy / steps 86 87 visible = True # Assume this point is visible unless proven otherwise 88 ray_x, ray_y = source 89 for _ in range(steps): 90 ray_x += step_dx 91 ray_y += step_dy 92 int_x, int_y = int(ray_x), int(ray_y) 93 if 0 <= int_x < image_size and 0 <= int_y < image_size: 94 if image_map[int_y, int_x] == 0: 95 visible = False 96 break 97 visibility_map[y, x] = visible 98 return visibility_map
def
compute_visibility(osm_path, image_size=256):
100def compute_visibility(osm_path, image_size=256): 101 image_map = np.array(Image.open(osm_path).convert('L').resize((image_size, image_size))) 102 image_map = np.where(image_map > 0, 1, 0) 103 visibility_map = ray_tracing(image_size, image_map) 104 pixels_in_sight = np.logical_and(visibility_map == 1, image_map == 1) 105 pixels_not_in_sight = np.logical_and(visibility_map == 0, image_map == 1) 106 pixels_not_in_sight = np.where(image_map == 0, 0, pixels_not_in_sight) 107 pixels_in_sight = np.where(image_map == 0, 0, pixels_in_sight) 108 return pixels_in_sight, pixels_not_in_sight
def
masked_mae(true_labels, predictions):
110def masked_mae(true_labels, predictions): 111 # Convert to numpy arrays 112 true_labels = np.array(true_labels) 113 predictions = np.array(predictions) 114 115 # Create a mask where true_labels are not equal to -1 116 mask = true_labels != -1 117 118 # Filter arrays with the mask 119 true_labels = true_labels[mask] 120 predictions = predictions[mask] 121 122 # Compute the MAE and return 123 return MAE(true_labels, predictions)
def
masked_mape(true_labels, predictions):
125def masked_mape(true_labels, predictions): 126 # Convert to numpy arrays 127 true_labels = np.array(true_labels) 128 predictions = np.array(predictions) 129 130 # Create a mask to exclude -1 131 mask = true_labels != -1 132 133 # Apply the mask to filter arrays 134 true_labels_filtered = true_labels[mask] 135 predictions_filtered = predictions[mask] 136 137 # Initialize an error map with zeros 138 error_map = np.zeros_like(true_labels_filtered, dtype=np.float32) 139 140 # Find indices where true noisemap is not 0 141 nonzero_indices = true_labels_filtered != 0 142 143 # Calculate percentage error where true noisemap is not 0 144 error_map[nonzero_indices] = np.abs((true_labels_filtered[nonzero_indices] - predictions_filtered[nonzero_indices]) / true_labels_filtered[nonzero_indices]) * 100 145 146 # For positions where true noisemap is 0 but pred noisemap is not, set error to 100% 147 zero_true_indices = (true_labels_filtered == 0) & (predictions_filtered != 0) 148 error_map[zero_true_indices] = 100 149 150 # Calculate the MAPE over the whole image, ignoring positions where both are 0 151 return np.mean(error_map)
def
calculate_sight_error(true_path, pred_path, osm_path):
153def calculate_sight_error(true_path, pred_path, osm_path): 154 pred_soundmap = (1 - np.array( 155 Image.open(pred_path).convert("L"), 156 dtype=np.float32 157 ) / 255) * 100 158 true_soundmap = (1 - np.array( 159 Image.open(true_path).convert("L"), 160 dtype=np.float32 161 ) / 255) * 100 162 _, true_pixels_not_in_sight = compute_visibility(osm_path) 163 164 in_sight_soundmap = true_soundmap.copy() 165 not_in_sight_soundmap = true_soundmap.copy() 166 167 in_sight_pred_soundmap = pred_soundmap.copy() 168 not_in_sight_pred_soundmap = pred_soundmap.copy() 169 170 #only get the pixels in sight 171 for x in range(256): 172 for y in range(256): 173 if true_pixels_not_in_sight[y, x] == 0: 174 not_in_sight_soundmap[y, x] = -1 175 not_in_sight_pred_soundmap[y, x] = -1 176 else: 177 in_sight_soundmap[y, x] = -1 178 in_sight_pred_soundmap[y, x] = -1 179 180 return masked_mae(in_sight_soundmap, in_sight_pred_soundmap), masked_mae(not_in_sight_soundmap, not_in_sight_pred_soundmap), masked_mape(in_sight_soundmap, in_sight_pred_soundmap), masked_mape(not_in_sight_soundmap, not_in_sight_pred_soundmap)
def
evaluate_sample( true_path, pred_path, osm_path=None) -> (<class 'float'>, <class 'float'>, <class 'float'>, <class 'float'>):
182def evaluate_sample(true_path, pred_path, osm_path=None) -> (float, float, float, float): 183 mae = calc_mae(true_path, pred_path) 184 mape = calc_mape(true_path, pred_path) 185 186 mae_in_sight, mae_not_in_sight, mape_in_sight, mape_not_in_sight = None, None, None, None 187 if osm_path is not None: 188 mae_in_sight, mae_not_in_sight, mape_in_sight, mape_not_in_sight = calculate_sight_error(true_path, pred_path, osm_path) 189 return mae, mape, mae_in_sight, mae_not_in_sight, mape_in_sight, mape_not_in_sight