|
|
|
import glob |
|
import logging |
|
import numpy as np |
|
import os |
|
import tempfile |
|
from collections import OrderedDict |
|
import torch |
|
from PIL import Image |
|
|
|
from detectron2.data import MetadataCatalog |
|
from detectron2.utils import comm |
|
from detectron2.utils.file_io import PathManager |
|
|
|
from .evaluator import DatasetEvaluator |
|
|
|
|
|
class CityscapesEvaluator(DatasetEvaluator): |
|
""" |
|
Base class for evaluation using cityscapes API. |
|
""" |
|
|
|
def __init__(self, dataset_name): |
|
""" |
|
Args: |
|
dataset_name (str): the name of the dataset. |
|
It must have the following metadata associated with it: |
|
"thing_classes", "gt_dir". |
|
""" |
|
self._metadata = MetadataCatalog.get(dataset_name) |
|
self._cpu_device = torch.device("cpu") |
|
self._logger = logging.getLogger(__name__) |
|
|
|
def reset(self): |
|
self._working_dir = tempfile.TemporaryDirectory(prefix="cityscapes_eval_") |
|
self._temp_dir = self._working_dir.name |
|
|
|
|
|
assert ( |
|
comm.get_local_size() == comm.get_world_size() |
|
), "CityscapesEvaluator currently do not work with multiple machines." |
|
self._temp_dir = comm.all_gather(self._temp_dir)[0] |
|
if self._temp_dir != self._working_dir.name: |
|
self._working_dir.cleanup() |
|
self._logger.info( |
|
"Writing cityscapes results to temporary directory {} ...".format(self._temp_dir) |
|
) |
|
|
|
|
|
class CityscapesInstanceEvaluator(CityscapesEvaluator): |
|
""" |
|
Evaluate instance segmentation results on cityscapes dataset using cityscapes API. |
|
|
|
Note: |
|
* It does not work in multi-machine distributed training. |
|
* It contains a synchronization, therefore has to be used on all ranks. |
|
* Only the main process runs evaluation. |
|
""" |
|
|
|
def process(self, inputs, outputs): |
|
from cityscapesscripts.helpers.labels import name2label |
|
|
|
for input, output in zip(inputs, outputs): |
|
file_name = input["file_name"] |
|
basename = os.path.splitext(os.path.basename(file_name))[0] |
|
pred_txt = os.path.join(self._temp_dir, basename + "_pred.txt") |
|
|
|
if "instances" in output: |
|
output = output["instances"].to(self._cpu_device) |
|
num_instances = len(output) |
|
with open(pred_txt, "w") as fout: |
|
for i in range(num_instances): |
|
pred_class = output.pred_classes[i] |
|
classes = self._metadata.thing_classes[pred_class] |
|
class_id = name2label[classes].id |
|
score = output.scores[i] |
|
mask = output.pred_masks[i].numpy().astype("uint8") |
|
png_filename = os.path.join( |
|
self._temp_dir, basename + "_{}_{}.png".format(i, classes) |
|
) |
|
|
|
Image.fromarray(mask * 255).save(png_filename) |
|
fout.write( |
|
"{} {} {}\n".format(os.path.basename(png_filename), class_id, score) |
|
) |
|
else: |
|
|
|
with open(pred_txt, "w") as fout: |
|
pass |
|
|
|
def evaluate(self): |
|
""" |
|
Returns: |
|
dict: has a key "segm", whose value is a dict of "AP" and "AP50". |
|
""" |
|
comm.synchronize() |
|
if comm.get_rank() > 0: |
|
return |
|
import cityscapesscripts.evaluation.evalInstanceLevelSemanticLabeling as cityscapes_eval |
|
|
|
self._logger.info("Evaluating results under {} ...".format(self._temp_dir)) |
|
|
|
|
|
cityscapes_eval.args.predictionPath = os.path.abspath(self._temp_dir) |
|
cityscapes_eval.args.predictionWalk = None |
|
cityscapes_eval.args.JSONOutput = False |
|
cityscapes_eval.args.colorized = False |
|
cityscapes_eval.args.gtInstancesFile = os.path.join(self._temp_dir, "gtInstances.json") |
|
|
|
|
|
|
|
gt_dir = PathManager.get_local_path(self._metadata.gt_dir) |
|
groundTruthImgList = glob.glob(os.path.join(gt_dir, "*", "*_gtFine_instanceIds.png")) |
|
assert len( |
|
groundTruthImgList |
|
), "Cannot find any ground truth images to use for evaluation. Searched for: {}".format( |
|
cityscapes_eval.args.groundTruthSearch |
|
) |
|
predictionImgList = [] |
|
for gt in groundTruthImgList: |
|
predictionImgList.append(cityscapes_eval.getPrediction(gt, cityscapes_eval.args)) |
|
results = cityscapes_eval.evaluateImgLists( |
|
predictionImgList, groundTruthImgList, cityscapes_eval.args |
|
)["averages"] |
|
|
|
ret = OrderedDict() |
|
ret["segm"] = {"AP": results["allAp"] * 100, "AP50": results["allAp50%"] * 100} |
|
self._working_dir.cleanup() |
|
return ret |
|
|
|
|
|
class CityscapesSemSegEvaluator(CityscapesEvaluator): |
|
""" |
|
Evaluate semantic segmentation results on cityscapes dataset using cityscapes API. |
|
|
|
Note: |
|
* It does not work in multi-machine distributed training. |
|
* It contains a synchronization, therefore has to be used on all ranks. |
|
* Only the main process runs evaluation. |
|
""" |
|
|
|
def process(self, inputs, outputs): |
|
from cityscapesscripts.helpers.labels import trainId2label |
|
|
|
for input, output in zip(inputs, outputs): |
|
file_name = input["file_name"] |
|
basename = os.path.splitext(os.path.basename(file_name))[0] |
|
pred_filename = os.path.join(self._temp_dir, basename + "_pred.png") |
|
|
|
output = output["sem_seg"].argmax(dim=0).to(self._cpu_device).numpy() |
|
pred = 255 * np.ones(output.shape, dtype=np.uint8) |
|
for train_id, label in trainId2label.items(): |
|
if label.ignoreInEval: |
|
continue |
|
pred[output == train_id] = label.id |
|
Image.fromarray(pred).save(pred_filename) |
|
|
|
def evaluate(self): |
|
comm.synchronize() |
|
if comm.get_rank() > 0: |
|
return |
|
|
|
|
|
import cityscapesscripts.evaluation.evalPixelLevelSemanticLabeling as cityscapes_eval |
|
|
|
self._logger.info("Evaluating results under {} ...".format(self._temp_dir)) |
|
|
|
|
|
cityscapes_eval.args.predictionPath = os.path.abspath(self._temp_dir) |
|
cityscapes_eval.args.predictionWalk = None |
|
cityscapes_eval.args.JSONOutput = False |
|
cityscapes_eval.args.colorized = False |
|
|
|
|
|
|
|
gt_dir = PathManager.get_local_path(self._metadata.gt_dir) |
|
groundTruthImgList = glob.glob(os.path.join(gt_dir, "*", "*_gtFine_labelIds.png")) |
|
assert len( |
|
groundTruthImgList |
|
), "Cannot find any ground truth images to use for evaluation. Searched for: {}".format( |
|
cityscapes_eval.args.groundTruthSearch |
|
) |
|
predictionImgList = [] |
|
for gt in groundTruthImgList: |
|
predictionImgList.append(cityscapes_eval.getPrediction(cityscapes_eval.args, gt)) |
|
results = cityscapes_eval.evaluateImgLists( |
|
predictionImgList, groundTruthImgList, cityscapes_eval.args |
|
) |
|
ret = OrderedDict() |
|
ret["sem_seg"] = { |
|
"IoU": 100.0 * results["averageScoreClasses"], |
|
"iIoU": 100.0 * results["averageScoreInstClasses"], |
|
"IoU_sup": 100.0 * results["averageScoreCategories"], |
|
"iIoU_sup": 100.0 * results["averageScoreInstCategories"], |
|
} |
|
self._working_dir.cleanup() |
|
return ret |
|
|