From c9d5d42c14890c485fb2adf6fa94dc685e1f8d64 Mon Sep 17 00:00:00 2001 From: Andre Anjos <andre.dos.anjos@gmail.com> Date: Fri, 26 Jun 2020 15:28:51 +0200 Subject: [PATCH] [engine.evaluator] Dump scores for patches as well --- bob/ip/binseg/engine/evaluator.py | 76 ++++++++++++++++++++++++++++++- bob/ip/binseg/test/test_cli.py | 33 ++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/bob/ip/binseg/engine/evaluator.py b/bob/ip/binseg/engine/evaluator.py index 8667300e..1b5be9f7 100644 --- a/bob/ip/binseg/engine/evaluator.py +++ b/bob/ip/binseg/engine/evaluator.py @@ -21,6 +21,9 @@ import logging logger = logging.getLogger(__name__) +_PATCH_CONFIG = (128, 128, 32) +"""Stock configuration for patch analysis""" + def _posneg(pred, gt, threshold): """Calculates true and false positives and negatives""" @@ -51,7 +54,7 @@ def _posneg(pred, gt, threshold): def _sample_measures(pred, gt, steps): """ - Calculates measures on one single sample and saves it to disk + Calculates measures on one single sample Parameters @@ -65,7 +68,7 @@ def _sample_measures(pred, gt, steps): steps : int number of steps to use for threshold analysis. The step size is - calculated from this by dividing ``1.0/steps``. + calculated from this by dividing ``1.0/steps`` Returns @@ -136,6 +139,62 @@ def _sample_measures(pred, gt, steps): ) +def _patch_measures(pred, gt, steps, size): + """ + Calculates measures on patches of a single sample + + + Parameters + ---------- + + pred : torch.Tensor + pixel-wise predictions + + gt : torch.Tensor + ground-truth (annotations) + + steps : int + number of steps to use for threshold analysis. The step size is + calculated from this by dividing ``1.0/steps`` + + size : :py:class:`tuple` + A tripplet with three integers indicating the height, width, and stride + of patches to break measure analysis into. In this case, the input + image and ground-truth will be cut into blocks of the provided height + and width, overlapping by the total overlap size, starting on the top + left corner and then moving right and to the bottom. Windows on the + left and bottom edge of the image may be incomplete. + + + Returns + ------- + + measures : pandas.DataFrame + + A pandas dataframe with the following columns: + + * patch: int + * threshold: float + * precision: float + * recall: float + * specificity: float + * accuracy: float + * jaccard: float + * f1_score: float + + """ + + height, width, stride = window_size + pred_patches = pred.unfold(0, height, stride).unfold(1, width, stride) + gt_patches = unfold(0, height, stride).unfold(1, width, stride) + + # add patch number for each set of measures + dfs = [_sample_measures(p, g, step) for p,g in zip(pred_patches, gt_patches)] + for i, k in enumerate(dfs): k['patch'] = i + + return pandas.concat(dfs, ignore_index=True) + + def _sample_analysis( img, pred, @@ -294,6 +353,12 @@ def run( os.makedirs(os.path.dirname(fullpath), exist_ok=True) data[stem].to_csv(fullpath) + # saves patch analysis + fullpath = os.path.join(output_folder, name, "patches", f"{stem}.csv") + tqdm.write(f"Saving {fullpath}...") + os.makedirs(os.path.dirname(fullpath), exist_ok=True) + _patch_measures(pred, gt, steps, _PATCH_CONFIG).to_csv(fullpath) + if overlayed_folder is not None: overlay_image = _sample_analysis( image, pred, gt, threshold=threshold, overlay=True @@ -422,6 +487,13 @@ def compare_annotators(baseline, other, name, output_folder, os.makedirs(os.path.dirname(fullpath), exist_ok=True) data[stem].to_csv(fullpath) + # saves patch analysis + fullpath = os.path.join(output_folder, "second-annotator", name, + "patches", f"{stem}.csv") + tqdm.write(f"Saving {fullpath}...") + os.makedirs(os.path.dirname(fullpath), exist_ok=True) + _patch_measures(pred, gt, 2, _PATCH_CONFIG).to_csv(fullpath) + if overlayed_folder is not None: overlay_image = _sample_analysis( image, pred, gt, threshold=0.5, overlay=True diff --git a/bob/ip/binseg/test/test_cli.py b/bob/ip/binseg/test/test_cli.py index 97f3dc25..2949d86f 100644 --- a/bob/ip/binseg/test/test_cli.py +++ b/bob/ip/binseg/test/test_cli.py @@ -151,6 +151,11 @@ def _check_experiment_stare(overlay): nose.tools.eq_( len(fnmatch.filter(os.listdir(traindir), "*.csv")), 10 ) + traindir = os.path.join(eval_folder, "train", "patches", "stare-images") + assert os.path.exists(traindir) + nose.tools.eq_( + len(fnmatch.filter(os.listdir(traindir), "*.csv")), 10 + ) assert os.path.exists(os.path.join(eval_folder, "test.csv")) # checks individual performance figures are there @@ -159,6 +164,11 @@ def _check_experiment_stare(overlay): nose.tools.eq_( len(fnmatch.filter(os.listdir(testdir), "*.csv")), 10 ) + testdir = os.path.join(eval_folder, "test", "patches", "stare-images") + assert os.path.exists(testdir) + nose.tools.eq_( + len(fnmatch.filter(os.listdir(testdir), "*.csv")), 10 + ) assert os.path.exists( os.path.join(eval_folder, "second-annotator", "train.csv") @@ -170,6 +180,12 @@ def _check_experiment_stare(overlay): nose.tools.eq_( len(fnmatch.filter(os.listdir(traindir_sa), "*.csv")), 10 ) + traindir_sa = os.path.join(eval_folder, "second-annotator", "patches", + "train", "stare-images") + assert os.path.exists(traindir_sa) + nose.tools.eq_( + len(fnmatch.filter(os.listdir(traindir_sa), "*.csv")), 10 + ) assert os.path.exists( os.path.join(eval_folder, "second-annotator", "test.csv") @@ -180,6 +196,12 @@ def _check_experiment_stare(overlay): nose.tools.eq_( len(fnmatch.filter(os.listdir(testdir_sa), "*.csv")), 10 ) + testdir_sa = os.path.join(eval_folder, "second-annotator", "patches", + "test", "stare-images") + assert os.path.exists(testdir_sa) + nose.tools.eq_( + len(fnmatch.filter(os.listdir(testdir_sa), "*.csv")), 10 + ) overlay_folder = os.path.join(output_folder, "overlayed", "analysis") traindir = os.path.join(overlay_folder, "train", "stare-images") @@ -439,6 +461,11 @@ def _check_evaluate(runner): nose.tools.eq_( len(fnmatch.filter(os.listdir(testdir), "*.csv")), 10 ) + testdir = os.path.join(output_folder, "test", "patches", "stare-images") + assert os.path.exists(testdir) + nose.tools.eq_( + len(fnmatch.filter(os.listdir(testdir), "*.csv")), 10 + ) assert os.path.exists( os.path.join(output_folder, "second-annotator", "test.csv") @@ -450,6 +477,12 @@ def _check_evaluate(runner): nose.tools.eq_( len(fnmatch.filter(os.listdir(testdir_sa), "*.csv")), 10 ) + testdir_sa = os.path.join(output_folder, "second-annotator", "test", + "patches", "stare-images") + assert os.path.exists(testdir_sa) + nose.tools.eq_( + len(fnmatch.filter(os.listdir(testdir_sa), "*.csv")), 10 + ) # check overlayed images are there (since we requested them) basedir = os.path.join(overlay_folder, "test", "stare-images") -- GitLab