From c6d273a7534ffa3feb6331876207c81ef1c00e72 Mon Sep 17 00:00:00 2001 From: lartpang Date: Fri, 5 Mar 2021 14:34:52 +0800 Subject: [PATCH] - Add the github action for publishing the package. - Rename files. --- .github/workflows/python-publish.yml | 26 +++++++++ LICENSE | 2 +- py_sod_metrics/__init__.py | 0 .../sod_metrics.py | 53 ++++++++++------- pyproject.toml | 29 ++++++++++ readme.md | 15 +++-- readme_zh.md | 14 +++-- requirements.txt | 1 - setup.py | 30 ++++++++++ tests/metric_recorder.py | 58 +++++++++++++++++++ tests/test_metrics.py | 2 +- 11 files changed, 195 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/python-publish.yml create mode 100755 py_sod_metrics/__init__.py rename sod_metrics/__init__.py => py_sod_metrics/sod_metrics.py (91%) mode change 100755 => 100644 create mode 100644 pyproject.toml create mode 100644 setup.py create mode 100644 tests/metric_recorder.py diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..2c14551 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,26 @@ +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_NAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PSS }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/LICENSE b/LICENSE index bf676a3..6cb72ea 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 MY_ +Copyright (c) 2020 lartpang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/py_sod_metrics/__init__.py b/py_sod_metrics/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/sod_metrics/__init__.py b/py_sod_metrics/sod_metrics.py old mode 100755 new mode 100644 similarity index 91% rename from sod_metrics/__init__.py rename to py_sod_metrics/sod_metrics.py index 046247e..bd26421 --- a/sod_metrics/__init__.py +++ b/py_sod_metrics/sod_metrics.py @@ -1,7 +1,7 @@ import numpy as np from scipy.ndimage import convolve, distance_transform_edt as bwdist -__version__ = '1.2.1' +__version__ = "1.2.1" _EPS = 1e-16 _TYPE = np.float64 @@ -88,8 +88,7 @@ def get_results(self) -> dict: changeable_fm = np.mean(np.array(self.changeable_fms, dtype=_TYPE), axis=0) precision = np.mean(np.array(self.precisions, dtype=_TYPE), axis=0) # N, 256 recall = np.mean(np.array(self.recalls, dtype=_TYPE), axis=0) # N, 256 - return dict(fm=dict(adp=adaptive_fm, curve=changeable_fm), - pr=dict(p=precision, r=recall)) + return dict(fm=dict(adp=adaptive_fm, curve=changeable_fm), pr=dict(p=precision, r=recall)) class MAE(object): @@ -149,11 +148,11 @@ def s_object(self, pred: np.ndarray, gt: np.ndarray) -> float: def region(self, pred: np.ndarray, gt: np.ndarray) -> float: x, y = self.centroid(gt) part_info = self.divide_with_xy(pred, gt, x, y) - w1, w2, w3, w4 = part_info['weight'] + w1, w2, w3, w4 = part_info["weight"] # assert np.isclose(w1 + w2 + w3 + w4, 1), (w1 + w2 + w3 + w4, pred.mean(), gt.mean()) - pred1, pred2, pred3, pred4 = part_info['pred'] - gt1, gt2, gt3, gt4 = part_info['gt'] + pred1, pred2, pred3, pred4 = part_info["pred"] + gt1, gt2, gt3, gt4 = part_info["gt"] score1 = self.ssim(pred1, gt1) score2 = self.ssim(pred2, gt2) score3 = self.ssim(pred3, gt3) @@ -198,9 +197,11 @@ def divide_with_xy(self, pred: np.ndarray, gt: np.ndarray, x, y) -> dict: # w4 = (h - y) * (w - x) / area w4 = 1 - w1 - w2 - w3 - return dict(gt=(gt_LT, gt_RT, gt_LB, gt_RB), - pred=(pred_LT, pred_RT, pred_LB, pred_RB), - weight=(w1, w2, w3, w4)) + return dict( + gt=(gt_LT, gt_RT, gt_LB, gt_RB), + pred=(pred_LT, pred_RT, pred_LB, pred_RB), + weight=(w1, w2, w3, w4), + ) def ssim(self, pred: np.ndarray, gt: np.ndarray) -> float: h, w = pred.shape @@ -272,14 +273,19 @@ def cal_em_with_threshold(self, pred: np.ndarray, gt: np.ndarray, threshold: flo enhanced_matrix_sum = fg___numel else: parts_numel, combinations = self.generate_parts_numel_combinations( - fg_fg_numel=fg_fg_numel, fg_bg_numel=fg_bg_numel, - pred_fg_numel=fg___numel, pred_bg_numel=bg___numel, + fg_fg_numel=fg_fg_numel, + fg_bg_numel=fg_bg_numel, + pred_fg_numel=fg___numel, + pred_bg_numel=bg___numel, ) 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) + align_matrix_value = ( + 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) enhanced_matrix_sum = sum(results_parts) @@ -309,14 +315,19 @@ def cal_em_with_cumsumhistogram(self, pred: np.ndarray, gt: np.ndarray) -> np.nd enhanced_matrix_sum = fg___numel_w_thrs else: parts_numel_w_thrs, combinations = self.generate_parts_numel_combinations( - fg_fg_numel=fg_fg_numel_w_thrs, fg_bg_numel=fg_bg_numel_w_thrs, - pred_fg_numel=fg___numel_w_thrs, pred_bg_numel=bg___numel_w_thrs, + fg_fg_numel=fg_fg_numel_w_thrs, + fg_bg_numel=fg_bg_numel_w_thrs, + pred_fg_numel=fg___numel_w_thrs, + pred_bg_numel=bg___numel_w_thrs, ) 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) + align_matrix_value = ( + 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 enhanced_matrix_sum = results_parts.sum(axis=0) @@ -324,7 +335,9 @@ def cal_em_with_cumsumhistogram(self, pred: np.ndarray, gt: np.ndarray) -> np.nd em = enhanced_matrix_sum / (self.gt_size - 1 + _EPS) return em - def generate_parts_numel_combinations(self, fg_fg_numel, fg_bg_numel, pred_fg_numel, pred_bg_numel): + def generate_parts_numel_combinations( + 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 @@ -342,7 +355,7 @@ def generate_parts_numel_combinations(self, fg_fg_numel, fg_bg_numel, pred_fg_nu (demeaned_pred_fg_value, demeaned_gt_fg_value), (demeaned_pred_fg_value, demeaned_gt_bg_value), (demeaned_pred_bg_value, demeaned_gt_fg_value), - (demeaned_pred_bg_value, demeaned_gt_bg_value) + (demeaned_pred_bg_value, demeaned_gt_bg_value), ] return parts_numel, combinations @@ -415,7 +428,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/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8668f2c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +# https://github.com/LongTengDao/TOML/ + +[tool.isort] +# https://pycqa.github.io/isort/docs/configuration/options/ +profile = "black" +multi_line_output = 3 +filter_files = true +supported_extensions = "py" + +[tool.black] +line-length = 99 +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.idea + | \.vscode + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | output +)/ +''' diff --git a/readme.md b/readme.md index 0b60e0b..448f3d0 100644 --- a/readme.md +++ b/readme.md @@ -42,20 +42,23 @@ For related discussion, please see: +* <./tests/test_metrics.py> +* <./tests/metric_recorder.py> ## Thanks diff --git a/readme_zh.md b/readme_zh.md index 4a7855e..3dbc1ca 100644 --- a/readme_zh.md +++ b/readme_zh.md @@ -35,21 +35,23 @@ matlab: Smeasure:0.903; wFmeasure:0.558; MAE:0.037; adpEm:0.941; meanEm:0.957; m ### 下载文件为自己的脚本 ```shell script -wget -nc -O metrics.py https://raw.githubusercontent.com/lartpang/PySODMetrics/main/sod_metrics/__init__.py +wget -nc -O metrics.py https://raw.githubusercontent.com/lartpang/PySODMetrics/main/py_sod_metrics/sod_metrics.py +# 或许你还需要: +pip install -r requirements.txt ``` -`-nc`: 如果文件存在,就不会下载 - +注意:`-nc`: 如果文件存在,就不会下载 -### 依赖 +### 安装成一个包 -```shell -pip install -r requirements.txt +```shell script +pip install pysodmetrics ``` ### 示例 * <./tests/test_metrics.py> +* <./tests/metric_recorder.py> ## 感谢 diff --git a/requirements.txt b/requirements.txt index aac10b7..140cc4e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ numpy~=1.18.0 scipy~=1.5.0 -opencv-python~=4.2.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8e78b26 --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +from setuptools import setup, find_packages + +setup( + name="pysodmetrics", + packages=find_packages(), + version="1.2.1", + license="MIT", + description="A simple and efficient implementation of SOD metrics.", + author="lartpang", + author_email="lartpang@gmail.com", + url="https://github.com/lartpang/PySODMetrics", + keywords=[ + "salient object detection", + "saliency detection", + "metric", + "deep learning", + ], + install_requires=["scipy>=1.5,<2", "numpy>=1.18,<2"], + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + ], +) diff --git a/tests/metric_recorder.py b/tests/metric_recorder.py new file mode 100644 index 0000000..619aaf5 --- /dev/null +++ b/tests/metric_recorder.py @@ -0,0 +1,58 @@ +# -*- 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 index 6d8c7ea..4854d3f 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -11,7 +11,7 @@ sys.path.append('../') -import sod_metrics as M +import py_sod_metrics.sod_metrics as M FM = M.Fmeasure() WFM = M.WeightedFmeasure()