FOUND / evaluation /saliency.py
osimeoni's picture
FOUND - second
25cae60
# Copyright 2022 - Valeo Comfort and Driving Assistance - valeo.ai
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import torch
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
from tqdm import tqdm
from scipy import ndimage
from evaluation.metrics.average_meter import AverageMeter
from evaluation.metrics.f_measure import FMeasure
from evaluation.metrics.iou import compute_iou
from evaluation.metrics.mae import compute_mae
from evaluation.metrics.pixel_acc import compute_pixel_accuracy
from evaluation.metrics.s_measure import SMeasure
from misc import batch_apply_bilateral_solver
@torch.no_grad()
def write_metric_tf(
writer,
metrics,
n_iter = -1,
name = ""
):
writer.add_scalar(
f"Validation/{name}iou_pred",
metrics["ious"].avg,
n_iter,
)
writer.add_scalar(
f"Validation/{name}acc_pred",
metrics["pixel_accs"].avg,
n_iter,
)
writer.add_scalar(
f"Validation/{name}f_max",
metrics["f_maxs"].avg,
n_iter,
)
@torch.no_grad()
def eval_batch(
batch_gt_masks,
batch_pred_masks,
metrics_res={},
reset=False
):
"""
Evaluation code adapted from SelfMask: https://github.com/NoelShin/selfmask
"""
f_values = {}
# Keep track of f_values for each threshold
for i in range(255): # should equal n_bins in metrics/f_measure.py
f_values[i] = AverageMeter()
if metrics_res == {}:
metrics_res["f_scores"] = AverageMeter()
metrics_res["f_maxs"] = AverageMeter()
metrics_res["f_maxs_fixed"] = AverageMeter()
metrics_res["f_means"] = AverageMeter()
metrics_res["maes"] = AverageMeter()
metrics_res["ious"] = AverageMeter()
metrics_res["pixel_accs"] = AverageMeter()
metrics_res["s_measures"] = AverageMeter()
if reset:
metrics_res["f_scores"].reset()
metrics_res["f_maxs"].reset()
metrics_res["f_maxs_fixed"].reset()
metrics_res["f_means"].reset()
metrics_res["maes"].reset()
metrics_res["ious"].reset()
metrics_res["pixel_accs"].reset()
metrics_res["s_measures"].reset()
# iterate over batch dimension
for _, (pred_mask, gt_mask) in enumerate(
zip(batch_pred_masks, batch_gt_masks)
):
assert pred_mask.shape == gt_mask.shape, f"{pred_mask.shape} != {gt_mask.shape}"
assert len(pred_mask.shape) == len(gt_mask.shape) == 2
# Compute
# Binarize at 0.5 for IoU and pixel accuracy
binary_pred = (pred_mask > 0.5).float().squeeze()
iou = compute_iou(binary_pred, gt_mask)
f_measures = FMeasure()(pred_mask, gt_mask) # soft mask for F measure
mae = compute_mae(binary_pred, gt_mask)
pixel_acc = compute_pixel_accuracy(binary_pred, gt_mask)
# Update
metrics_res["ious"].update(val=iou.numpy(), n=1)
metrics_res["f_scores"].update(val=f_measures["f_measure"].numpy(), n=1)
metrics_res["f_maxs"].update(val=f_measures["f_max"].numpy(), n=1)
metrics_res["f_means"].update(val=f_measures["f_mean"].numpy(), n=1)
metrics_res["s_measures"].update(
val=SMeasure()(pred_mask=pred_mask, gt_mask=gt_mask.to(torch.float32)), n=1
)
metrics_res["maes"].update(val=mae.numpy(), n=1)
metrics_res["pixel_accs"].update(val=pixel_acc.numpy(), n=1)
# Keep track of f_values for each threshold
all_f = f_measures["all_f"].numpy()
for k, v in f_values.items():
v.update(val=all_f[k], n=1)
# Then compute the max for the f_max_fixed
metrics_res["f_maxs_fixed"].update(
val=np.max([v.avg for v in f_values.values()]), n=1
)
results = {}
# F-measure, F-max, F-mean, MAE, S-measure, IoU, pixel acc.
results["f_measure"] = metrics_res["f_scores"].avg
results["f_max"] = metrics_res["f_maxs"].avg
results["f_maxs_fixed"] = metrics_res["f_maxs_fixed"].avg
results["f_mean"] = metrics_res["f_means"].avg
results["s_measure"] = metrics_res["s_measures"].avg
results["mae"] = metrics_res["maes"].avg
results["iou"] = float(iou.numpy())
results["pixel_acc"] = metrics_res["pixel_accs"].avg
return results, metrics_res
def evaluate_saliency(
dataset,
model,
writer=None,
batch_size=1,
n_iter=-1,
apply_bilateral=False,
im_fullsize=True,
method="pred", # can also be "bkg",
apply_weights: bool = True,
evaluation_mode: str = 'single', # choices are ["single", "multi"]
):
if im_fullsize:
# Change transformation
dataset.fullimg_mode()
batch_size = 1
valloader = torch.utils.data.DataLoader(
dataset,
batch_size=batch_size,
shuffle=False,
num_workers=2
)
sigmoid = nn.Sigmoid()
metrics_res = {}
metrics_res_bs = {}
valbar = tqdm(enumerate(valloader, 0), leave=None)
for i, data in valbar:
inputs, _, gt_labels, _ = data
inputs = inputs.to("cuda")
gt_labels = gt_labels.to("cuda").float()
# Forward step
with torch.no_grad():
preds, _, shape_f, att = model.forward_step(inputs, for_eval=True)
if method == "pred":
h, w = gt_labels.shape[-2:]
preds_up = F.interpolate(
preds, scale_factor=model.vit_patch_size, mode="bilinear", align_corners=False
)[..., :h, :w]
soft_preds = sigmoid(preds_up.detach()).squeeze(0)
preds_up = (
(sigmoid(preds_up.detach()) > 0.5).squeeze(0).float()
)
elif method == "bkg":
bkg_mask_pred = model.compute_background_batch(
att, shape_f,
apply_weights=apply_weights,
)
# Transform bkg detection to foreground detection
obj_mask = (
~bkg_mask_pred.bool()
).float() # Obj labels is inverse of bkg
# Fit predictions to image size
preds_up = F.interpolate(
obj_mask.unsqueeze(1),
gt_labels.shape[-2:],
mode="bilinear",
align_corners=False,
)
preds_up = (preds_up > 0.5).float()
soft_preds = preds_up # not soft actually
reset = True if i == 0 else False
if evaluation_mode == 'single':
labeled, nr_objects = ndimage.label(preds_up.squeeze().cpu().numpy())
if nr_objects == 0:
preds_up_one_cc = preds_up.squeeze()
print("nr_objects == 0")
else:
nb_pixel = [np.sum(labeled == i) for i in range(nr_objects + 1)]
pixel_order = np.argsort(nb_pixel)
cc = [torch.Tensor(labeled == i) for i in pixel_order]
cc = torch.stack(cc).cuda()
# Find CC set as background, here not necessarily the biggest
cc_background = (
(
(
(~(preds_up[None, :, :, :].bool())).float()
+ cc[:, None, :, :].cuda()
)
> 1
).sum(-1).sum(-1).argmax()
)
pixel_order = np.delete(
pixel_order, int(cc_background.cpu().numpy())
)
preds_up_one_cc = torch.Tensor(labeled == pixel_order[-1]).cuda()
_, metrics_res = eval_batch(
gt_labels,
preds_up_one_cc.unsqueeze(0),
metrics_res=metrics_res,
reset=reset,
)
if writer is not None:
write_metric_tf(writer, metrics_res, n_iter=n_iter, name=f"_{evaluation_mode}_")
elif evaluation_mode == 'multi':
# Eval without bilateral solver
_, metrics_res = eval_batch(
gt_labels,
soft_preds.unsqueeze(0) if len(soft_preds.shape) == 2 else soft_preds,
metrics_res=metrics_res,
reset=reset,
) # soft preds needed for F beta measure
# Apply bilateral solver
preds_bs = None
if apply_bilateral:
get_all_cc = True if evaluation_mode == 'multi' else False
preds_bs, _ = batch_apply_bilateral_solver(data,
preds_up.detach(),
get_all_cc = get_all_cc
)
_, metrics_res_bs = eval_batch(
gt_labels,
preds_bs[None,:,:].float(),
metrics_res=metrics_res_bs,
reset=reset
)
if writer is not None:
write_metric_tf(writer, metrics_res_bs, n_iter=n_iter, name=f"_{evaluation_mode}-BS_")
bar_str = f"{dataset.name} | {evaluation_mode} mode | " \
f"F-max {metrics_res['f_maxs'].avg:.3f} " \
f"IoU {metrics_res['ious'].avg:.3f}, " \
f"PA {metrics_res['pixel_accs'].avg:.3f}"
if apply_bilateral:
bar_str += f" | with bilateral solver: " \
f"F-max {metrics_res_bs['f_maxs'].avg:.3f}, " \
f"IoU {metrics_res_bs['ious'].avg:.3f}, " \
f"PA. {metrics_res_bs['pixel_accs'].avg:.3f}"
valbar.set_description(bar_str)
# Go back to original transformation
if im_fullsize:
dataset.training_mode()