diff --git a/examples/metric_recorder.py b/examples/metric_recorder.py new file mode 100644 index 0000000..15ad3b5 --- /dev/null +++ b/examples/metric_recorder.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# @Time : 2021/1/4 +# @Author : Lart Pang +# @GitHub : https://github.com/lartpang + +import numpy as np + +from py_sod_metrics import Emeasure, Fmeasure, MAE, Smeasure, WeightedFmeasure + + +def ndarray_to_basetype(data): + """ + 将单独的ndarray,或者tuple,list或者dict中的ndarray转化为基本数据类型, + 即列表(.tolist())和python标量 + """ + + def _to_list_or_scalar(item): + listed_item = item.tolist() + if isinstance(listed_item, list) and len(listed_item) == 1: + listed_item = listed_item[0] + return listed_item + + if isinstance(data, (tuple, list)): + results = [_to_list_or_scalar(item) for item in data] + elif isinstance(data, dict): + results = {k: _to_list_or_scalar(item) for k, item in data.items()} + else: + assert isinstance(data, np.ndarray) + results = _to_list_or_scalar(data) + return results + + +class CalTotalMetric(object): + def __init__(self): + """ + 用于统计各种指标的类 + https://github.com/lartpang/Py-SOD-VOS-EvalToolkit/blob/81ce89da6813fdd3e22e3f20e3a09fe1e4a1a87c/utils/recorders/metric_recorder.py + """ + self.mae = MAE() + self.fm = Fmeasure() + self.sm = Smeasure() + self.em = Emeasure() + self.wfm = WeightedFmeasure() + + def step(self, pre: np.ndarray, gt: np.ndarray): + assert pre.shape == gt.shape + assert pre.dtype == np.uint8 + assert gt.dtype == np.uint8 + + self.mae.step(pre, gt) + self.sm.step(pre, gt) + self.fm.step(pre, gt) + self.em.step(pre, gt) + self.wfm.step(pre, gt) + + def get_results(self, num_bits: int = 3, return_ndarray: bool = False) -> dict: + """ + 返回指标计算结果: + + - 曲线数据(sequential): fm/em/p/r + - 数值指标(numerical): SM/MAE/maxE/avgE/adpE/maxF/avgF/adpF/wFm + """ + fm_info = self.fm.get_results() + fm = fm_info["fm"] + pr = fm_info["pr"] + wfm = self.wfm.get_results()["wfm"] + sm = self.sm.get_results()["sm"] + em = self.em.get_results()["em"] + mae = self.mae.get_results()["mae"] + + sequential_results = { + "fm": np.flip(fm["curve"]), + "em": np.flip(em["curve"]), + "p": np.flip(pr["p"]), + "r": np.flip(pr["r"]), + } + numerical_results = { + "SM": sm, + "MAE": mae, + "maxE": em["curve"].max(), + "avgE": em["curve"].mean(), + "adpE": em["adp"], + "maxF": fm["curve"].max(), + "avgF": fm["curve"].mean(), + "adpF": fm["adp"], + "wFm": wfm, + } + if num_bits is not None and isinstance(num_bits, int): + numerical_results = {k: v.round(num_bits) for k, v in numerical_results.items()} + if not return_ndarray: + sequential_results = ndarray_to_basetype(sequential_results) + numerical_results = ndarray_to_basetype(numerical_results) + return {"sequential": sequential_results, "numerical": numerical_results} + + +if __name__ == "__main__": + data_loader = ... + model = ... + + cal_total_seg_metrics = CalTotalMetric() + for batch in data_loader: + seg_preds = model(batch) + for seg_pred in seg_preds: + mask_array = ... + cal_total_seg_metrics.step(seg_pred, mask_array) + fixed_seg_results = cal_total_seg_metrics.get_results() diff --git a/tests/test_data/masks/0001.png b/examples/test_data/masks/0001.png similarity index 100% rename from tests/test_data/masks/0001.png rename to examples/test_data/masks/0001.png diff --git a/tests/test_data/masks/19.png b/examples/test_data/masks/19.png similarity index 100% rename from tests/test_data/masks/19.png rename to examples/test_data/masks/19.png diff --git a/tests/test_data/masks/aerial-1867541__340.png b/examples/test_data/masks/aerial-1867541__340.png similarity index 100% rename from tests/test_data/masks/aerial-1867541__340.png rename to examples/test_data/masks/aerial-1867541__340.png diff --git a/tests/test_data/preds/0001.png b/examples/test_data/preds/0001.png similarity index 100% rename from tests/test_data/preds/0001.png rename to examples/test_data/preds/0001.png diff --git a/tests/test_data/preds/19.png b/examples/test_data/preds/19.png similarity index 100% rename from tests/test_data/preds/19.png rename to examples/test_data/preds/19.png diff --git a/tests/test_data/preds/aerial-1867541__340.png b/examples/test_data/preds/aerial-1867541__340.png similarity index 100% rename from tests/test_data/preds/aerial-1867541__340.png rename to examples/test_data/preds/aerial-1867541__340.png diff --git a/tests/test_data/readme.md b/examples/test_data/readme.md similarity index 100% rename from tests/test_data/readme.md rename to examples/test_data/readme.md diff --git a/examples/test_metrics.py b/examples/test_metrics.py new file mode 100644 index 0000000..0758502 --- /dev/null +++ b/examples/test_metrics.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# @Time : 2020/11/21 +# @Author : Lart Pang +# @GitHub : https://github.com/lartpang + +import os + +import cv2 +from tqdm import tqdm + +# pip install pysodmetrics +from py_sod_metrics import Emeasure, Fmeasure, MAE, Smeasure, WeightedFmeasure + +FM = Fmeasure() +WFM = WeightedFmeasure() +SM = Smeasure() +EM = Emeasure() +MAE = MAE() + +data_root = "./test_data" +mask_root = os.path.join(data_root, "masks") +pred_root = os.path.join(data_root, "preds") +mask_name_list = sorted(os.listdir(mask_root)) +for mask_name in tqdm(mask_name_list, total=len(mask_name_list)): + mask_path = os.path.join(mask_root, mask_name) + pred_path = os.path.join(pred_root, mask_name) + mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) + pred = cv2.imread(pred_path, cv2.IMREAD_GRAYSCALE) + FM.step(pred=pred, gt=mask) + WFM.step(pred=pred, gt=mask) + SM.step(pred=pred, gt=mask) + EM.step(pred=pred, gt=mask) + MAE.step(pred=pred, gt=mask) + +fm = FM.get_results()["fm"] +wfm = WFM.get_results()["wfm"] +sm = SM.get_results()["sm"] +em = EM.get_results()["em"] +mae = MAE.get_results()["mae"] + +results = { + "Smeasure": sm.round(3), + "wFmeasure": wfm.round(3), + "MAE": mae.round(3), + "adpEm": em["adp"].round(3), + "meanEm": em["curve"].mean().round(3), + "maxEm": em["curve"].max().round(3), + "adpFm": fm["adp"].round(3), + "meanFm": fm["curve"].mean().round(3), + "maxFm": fm["curve"].max().round(3), +} + +print(results) diff --git a/py_sod_metrics/__init__.py b/py_sod_metrics/__init__.py index e69de29..b6cec16 100755 --- a/py_sod_metrics/__init__.py +++ b/py_sod_metrics/__init__.py @@ -0,0 +1,3 @@ +from py_sod_metrics.sod_metrics import * + +__version__ = "1.2.2" diff --git a/py_sod_metrics/sod_metrics.py b/py_sod_metrics/sod_metrics.py index bd26421..28399e9 100644 --- a/py_sod_metrics/sod_metrics.py +++ b/py_sod_metrics/sod_metrics.py @@ -1,8 +1,6 @@ import numpy as np from scipy.ndimage import convolve, distance_transform_edt as bwdist -__version__ = "1.2.1" - _EPS = 1e-16 _TYPE = np.float64 @@ -282,9 +280,9 @@ def cal_em_with_threshold(self, pred: np.ndarray, gt: np.ndarray, threshold: flo results_parts = [] for i, (part_numel, combination) in enumerate(zip(parts_numel, combinations)): align_matrix_value = ( - 2 - * (combination[0] * combination[1]) - / (combination[0] ** 2 + combination[1] ** 2 + _EPS) + 2 + * (combination[0] * combination[1]) + / (combination[0] ** 2 + combination[1] ** 2 + _EPS) ) enhanced_matrix_value = (align_matrix_value + 1) ** 2 / 4 results_parts.append(enhanced_matrix_value * part_numel) @@ -324,9 +322,9 @@ def cal_em_with_cumsumhistogram(self, pred: np.ndarray, gt: np.ndarray) -> np.nd results_parts = np.empty(shape=(4, 256), dtype=np.float64) for i, (part_numel, combination) in enumerate(zip(parts_numel_w_thrs, combinations)): align_matrix_value = ( - 2 - * (combination[0] * combination[1]) - / (combination[0] ** 2 + combination[1] ** 2 + _EPS) + 2 + * (combination[0] * combination[1]) + / (combination[0] ** 2 + combination[1] ** 2 + _EPS) ) enhanced_matrix_value = (align_matrix_value + 1) ** 2 / 4 results_parts[i] = enhanced_matrix_value * part_numel @@ -336,7 +334,7 @@ def cal_em_with_cumsumhistogram(self, pred: np.ndarray, gt: np.ndarray) -> np.nd return em def generate_parts_numel_combinations( - self, fg_fg_numel, fg_bg_numel, pred_fg_numel, pred_bg_numel + self, fg_fg_numel, fg_bg_numel, pred_fg_numel, pred_bg_numel ): bg_fg_numel = self.gt_fg_numel - fg_fg_numel bg_bg_numel = pred_bg_numel - bg_fg_numel @@ -428,7 +426,7 @@ def matlab_style_gauss2D(self, shape: tuple = (7, 7), sigma: int = 5) -> np.ndar fspecial('gaussian',[shape],[sigma]) """ m, n = [(ss - 1) / 2 for ss in shape] - y, x = np.ogrid[-m : m + 1, -n : n + 1] + y, x = np.ogrid[-m: m + 1, -n: n + 1] h = np.exp(-(x * x + y * y) / (2 * sigma * sigma)) h[h < np.finfo(h.dtype).eps * h.max()] = 0 sumh = h.sum() diff --git a/readme_zh.md b/readme_zh.md index 3dbc1ca..79930a2 100644 --- a/readme_zh.md +++ b/readme_zh.md @@ -50,8 +50,8 @@ pip install pysodmetrics ### 示例 -* <./tests/test_metrics.py> -* <./tests/metric_recorder.py> +* +* ## 感谢 diff --git a/setup.py b/setup.py index 8e78b26..098adae 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,17 @@ from setuptools import setup, find_packages +import py_sod_metrics as my_script + +with open("readme.md", "r") as fh: + long_description = fh.read() setup( name="pysodmetrics", packages=find_packages(), - version="1.2.1", + version=my_script.__version__, license="MIT", description="A simple and efficient implementation of SOD metrics.", + long_description=long_description, + long_description_content_type="text/markdown", author="lartpang", author_email="lartpang@gmail.com", url="https://github.com/lartpang/PySODMetrics", diff --git a/tests/metric_recorder.py b/tests/metric_recorder.py deleted file mode 100644 index 619aaf5..0000000 --- a/tests/metric_recorder.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2021/3/5 -# @Author : Lart Pang -# @GitHub : https://github.com/lartpang - - -import numpy as np - -from py_sod_metrics.sod_metrics import Emeasure, Fmeasure, MAE, Smeasure, WeightedFmeasure - - -class CalTotalMetric(object): - def __init__(self): - self.cal_mae = MAE() - self.cal_fm = Fmeasure() - self.cal_sm = Smeasure() - self.cal_em = Emeasure() - self.cal_wfm = WeightedFmeasure() - - def step(self, pred: np.ndarray, gt: np.ndarray, gt_path: str): - assert pred.ndim == gt.ndim and pred.shape == gt.shape, (pred.shape, gt.shape, gt_path) - assert pred.dtype == np.uint8, pred.dtype - assert gt.dtype == np.uint8, gt.dtype - - self.cal_mae.step(pred, gt) - self.cal_fm.step(pred, gt) - self.cal_sm.step(pred, gt) - self.cal_em.step(pred, gt) - self.cal_wfm.step(pred, gt) - - def get_results(self, bit_width: int = 3) -> dict: - fm = self.cal_fm.get_results()["fm"] - wfm = self.cal_wfm.get_results()["wfm"] - sm = self.cal_sm.get_results()["sm"] - em = self.cal_em.get_results()["em"] - mae = self.cal_mae.get_results()["mae"] - results = { - "Smeasure": sm, - "wFmeasure": wfm, - "MAE": mae, - "adpEm": em["adp"], - "meanEm": em["curve"].mean(), - "maxEm": em["curve"].max(), - "adpFm": fm["adp"], - "meanFm": fm["curve"].mean(), - "maxFm": fm["curve"].max(), - } - results = {name: metric.round(bit_width) for name, metric in results.items()} - return results - - -if __name__ == '__main__': - cal_total_seg_metrics = CalTotalMetric() - for batch in data_loader: - seg_preds = model(batch) - for seg_pred in seg_preds: - cal_total_seg_metrics.step(seg_pred, mask_array, mask_path) - fixed_seg_results = cal_total_seg_metrics.get_results() diff --git a/tests/test_metrics.py b/tests/test_metrics.py deleted file mode 100644 index 4854d3f..0000000 --- a/tests/test_metrics.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2020/11/21 -# @Author : Lart Pang -# @GitHub : https://github.com/lartpang - -import os -import sys - -import cv2 -from tqdm import tqdm - -sys.path.append('../') - -import py_sod_metrics.sod_metrics as M - -FM = M.Fmeasure() -WFM = M.WeightedFmeasure() -SM = M.Smeasure() -EM = M.Emeasure() -MAE = M.MAE() - -data_root = './test_data' -mask_root = os.path.join(data_root, 'masks') -pred_root = os.path.join(data_root, 'preds') -mask_name_list = sorted(os.listdir(mask_root)) -for mask_name in tqdm(mask_name_list, total=len(mask_name_list)): - mask_path = os.path.join(mask_root, mask_name) - pred_path = os.path.join(pred_root, mask_name) - mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) - pred = cv2.imread(pred_path, cv2.IMREAD_GRAYSCALE) - FM.step(pred=pred, gt=mask) - WFM.step(pred=pred, gt=mask) - SM.step(pred=pred, gt=mask) - EM.step(pred=pred, gt=mask) - MAE.step(pred=pred, gt=mask) - -fm = FM.get_results()['fm'] -wfm = WFM.get_results()['wfm'] -sm = SM.get_results()['sm'] -em = EM.get_results()['em'] -mae = MAE.get_results()['mae'] - -print( - 'Smeasure:', sm.round(3), '; ', - 'wFmeasure:', wfm.round(3), '; ', - 'MAE:', mae.round(3), '; ', - 'adpEm:', em['adp'].round(3), '; ', - 'meanEm:', '-' if em['curve'] is None else em['curve'].mean().round(3), '; ', - 'maxEm:', '-' if em['curve'] is None else em['curve'].max().round(3), '; ', - 'adpFm:', fm['adp'].round(3), '; ', - 'meanFm:', fm['curve'].mean().round(3), '; ', - 'maxFm:', fm['curve'].max().round(3), - sep='' -) diff --git a/tests/test_speed_for_count_nonzero.py b/tests/test_speed_for_count_nonzero.py deleted file mode 100644 index 92dc9d1..0000000 --- a/tests/test_speed_for_count_nonzero.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2020/11/27 -# @Author : Lart Pang -# @GitHub : https://github.com/lartpang -import time - -import numpy as np - - -# 快速统计numpy数组的非零值建议使用np.count_nonzero,一个简单的小实验 -def cal_nonzero(size): - a = np.random.randn(size, size) - a = a > 0 - start = time.time() - print(np.count_nonzero(a), time.time() - start) - start = time.time() - print(np.sum(a), time.time() - start) - start = time.time() - print(len(np.nonzero(a)[0]), time.time() - start) - - -if __name__ == '__main__': - cal_nonzero(1000) - # 499950 6.723403930664062e-05 - # 499950 0.0006949901580810547 - # 499950 0.007088184356689453 diff --git a/tests/test_speed_for_ops.py b/tests/test_speed_for_ops.py deleted file mode 100644 index 6594e19..0000000 --- a/tests/test_speed_for_ops.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2020/11/27 -# @Author : Lart Pang -# @GitHub : https://github.com/lartpang -import time - -import numpy as np - - -# TPs = fg_w_thrs.copy() -# Ps = (fg_w_thrs + bg_w_thrs).copy() -# T = np.count_nonzero(gt) -# Ps[Ps == 0] = 1 -# if T == 0: -# T = 1 -# 0.06196483850479126 -def ori_process_for_copyop(fg_w_thrs, bg_w_thrs, gt): - TPs = fg_w_thrs.copy() - Ps = (fg_w_thrs + bg_w_thrs).copy() - T = np.count_nonzero(gt) - Ps[Ps == 0] = 1 - if T == 0: - T = 1 - - -# 0.060863380432128904 -def new_process_for_copyop(fg_w_thrs, bg_w_thrs, gt): - TPs = fg_w_thrs - Ps = fg_w_thrs + bg_w_thrs - T = max(np.count_nonzero(gt), 1) - Ps[Ps == 0] = 1 - - -if __name__ == '__main__': - size = 1000 - fg_w_thrs = np.random.randn(size, size) > 0 - bg_w_thrs = np.random.randn(size, size) < 0 - gt = np.random.randn(size, size) > 0 - start = time.time() - for _ in range(1000): - ori_process_for_copyop(fg_w_thrs=fg_w_thrs, bg_w_thrs=bg_w_thrs, gt=gt) - print((time.time() - start) / 100) - start = time.time() - for _ in range(1000): - new_process_for_copyop(fg_w_thrs=fg_w_thrs, bg_w_thrs=bg_w_thrs, gt=gt) - print((time.time() - start) / 100) - # 0.042382607460021975 - # 0.040832839012145995