|
|
|
import os |
|
|
|
import fiona |
|
import numpy as np |
|
import shapely.geometry |
|
import argparse |
|
from multiprocess import Pool |
|
from functools import partial |
|
from tqdm import tqdm |
|
|
|
try: |
|
__import__("frame_field_learning.local_utils") |
|
except ImportError: |
|
print("ERROR: The frame_field_learning package is not installed! " |
|
"Execute script setup.sh to install local dependencies such as frame_field_learning in develop mode.") |
|
exit() |
|
|
|
from lydorn_utils import polygon_utils, python_utils, print_utils, geo_utils |
|
|
|
|
|
def get_args(): |
|
argparser = argparse.ArgumentParser(description=__doc__) |
|
argparser.add_argument( |
|
'--im_filepath', |
|
required=True, |
|
type=str, |
|
nargs='*', |
|
help='Path(s) to tiff images (to get projection info).') |
|
argparser.add_argument( |
|
'--gt_filepath', |
|
required=True, |
|
type=str, |
|
nargs='*', |
|
help='Path(s) to ground truth.') |
|
argparser.add_argument( |
|
'--pred_filepath', |
|
required=True, |
|
type=str, |
|
nargs='*', |
|
help='Path(s) to predictions.') |
|
argparser.add_argument( |
|
'--overwrite', |
|
action='store_true', |
|
help='The default behavior is to not re-compute a metric if it already has been computed. ' |
|
'Add the --overwrite flag if you want to overwrite existing metric results.') |
|
|
|
args = argparser.parse_args() |
|
return args |
|
|
|
|
|
def load_geom(geom_filepath, im_filepath): |
|
ext = os.path.splitext(geom_filepath)[-1] |
|
if ext == ".geojson": |
|
file = fiona.open(geom_filepath) |
|
assert len(file) == 1, "There should be only one feature per file" |
|
feat = file[0] |
|
geoms = shapely.geometry.shape(feat["geometry"]) |
|
elif ext == ".shp": |
|
polygons, _ = geo_utils.get_polygons_from_shapefile(im_filepath, geom_filepath, progressbar=False) |
|
geoms = shapely.geometry.collection.GeometryCollection([shapely.geometry.Polygon(polygon[:, ::-1]) for polygon in polygons]) |
|
else: |
|
raise ValueError(f"Geometry can not be loaded from a {ext} file.") |
|
return geoms |
|
|
|
|
|
def extract_name(filepath): |
|
basename = os.path.basename(filepath) |
|
name = basename.split(".")[0] |
|
return name |
|
|
|
|
|
def match_im_gt_pred(im_filepaths, gt_filepaths, pred_filepaths): |
|
im_names = list(map(extract_name, im_filepaths)) |
|
gt_names = list(map(extract_name, gt_filepaths)) |
|
|
|
im_gt_pred_filepaths = [] |
|
for pred_filepath in pred_filepaths: |
|
name = extract_name(pred_filepath) |
|
im_index = im_names.index(name) |
|
gt_index = gt_names.index(name) |
|
im_gt_pred_filepaths.append((im_filepaths[im_index], gt_filepaths[gt_index], pred_filepath)) |
|
|
|
return im_gt_pred_filepaths |
|
|
|
|
|
def eval_one(im_gt_pred_filepath, overwrite=False): |
|
im_filepath, gt_filepath, pred_filepath = im_gt_pred_filepath |
|
metrics_filepath = os.path.splitext(pred_filepath)[0] + ".metrics.json" |
|
iou_filepath = os.path.splitext(pred_filepath)[0] + ".iou.json" |
|
|
|
metrics = False |
|
iou = False |
|
if not overwrite: |
|
|
|
metrics = python_utils.load_json(metrics_filepath) |
|
iou = python_utils.load_json(iou_filepath) |
|
|
|
if not metrics or not iou: |
|
|
|
gt_polygons = load_geom(gt_filepath, im_filepath) |
|
fixed_gt_polygons = polygon_utils.fix_polygons(gt_polygons, |
|
buffer=0.0001) |
|
pred_polygons = load_geom(pred_filepath, im_filepath) |
|
fixed_pred_polygons = polygon_utils.fix_polygons(pred_polygons) |
|
|
|
if not metrics: |
|
|
|
max_angle_diffs = polygon_utils.compute_polygon_contour_measures(fixed_pred_polygons, fixed_gt_polygons, |
|
sampling_spacing=1.0, min_precision=0.5, |
|
max_stretch=2, progressbar=True) |
|
max_angle_diffs = [value for value in max_angle_diffs if value is not None] |
|
max_angle_diffs = np.array(max_angle_diffs) |
|
max_angle_diffs = max_angle_diffs * 180 / np.pi |
|
metrics = { |
|
"max_angle_diffs": list(max_angle_diffs) |
|
} |
|
python_utils.save_json(metrics_filepath, metrics) |
|
|
|
if not iou: |
|
fixed_gt_polygon_collection = shapely.geometry.collection.GeometryCollection(fixed_gt_polygons) |
|
fixed_pred_polygon_collection = shapely.geometry.collection.GeometryCollection(fixed_pred_polygons) |
|
intersection = fixed_gt_polygon_collection.intersection(fixed_pred_polygon_collection).area |
|
union = fixed_gt_polygon_collection.union(fixed_pred_polygon_collection).area |
|
iou = { |
|
"intersection": intersection, |
|
"union": union |
|
} |
|
python_utils.save_json(iou_filepath, iou) |
|
|
|
return { |
|
"metrics": metrics, |
|
"iou": iou, |
|
} |
|
|
|
|
|
def main(): |
|
args = get_args() |
|
print_utils.print_info(f"INFO: evaluating {len(args.pred_filepath)} predictions.") |
|
|
|
|
|
im_gt_pred_filepaths = match_im_gt_pred(args.im_filepath, args.gt_filepath, args.pred_filepath) |
|
|
|
pool = Pool() |
|
metrics_iou_list = list(tqdm(pool.imap(partial(eval_one, overwrite=args.overwrite), im_gt_pred_filepaths), desc="Compute eval metrics", total=len(im_gt_pred_filepaths))) |
|
|
|
|
|
aggr_metrics = { |
|
"max_angle_diffs": [] |
|
} |
|
aggr_iou = { |
|
"intersection": 0, |
|
"union": 0 |
|
} |
|
for metrics_iou in metrics_iou_list: |
|
if metrics_iou["metrics"]: |
|
aggr_metrics["max_angle_diffs"] += metrics_iou["metrics"]["max_angle_diffs"] |
|
if metrics_iou["iou"]: |
|
aggr_iou["intersection"] += metrics_iou["iou"]["intersection"] |
|
aggr_iou["union"] += metrics_iou["iou"]["union"] |
|
aggr_iou["iou"] = aggr_iou["intersection"] / aggr_iou["union"] |
|
|
|
aggr_metrics_filepath = os.path.join(os.path.dirname(args.pred_filepath[0]), "aggr_metrics.json") |
|
aggr_iou_filepath = os.path.join(os.path.dirname(args.pred_filepath[0]), "aggr_iou.json") |
|
python_utils.save_json(aggr_metrics_filepath, aggr_metrics) |
|
python_utils.save_json(aggr_iou_filepath, aggr_iou) |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |
|
|