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: <https://github.com/DengPingFan/CODToolbox/i
 ### Download the file as your script
 
 ```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
+# maybe, you need:
+pip install -r requirements.txt
 ```
 
-`-nc`: If the file 'metrics.py' already exists, it cannot be retrieved.
+NOTE: `-nc`: If the file 'metrics.py' already exists, it cannot be retrieved.
 
-### Requirements
+### Install it as a python package.
 
-```shell
-pip install -r requirements.txt
+```shell script
+pip install pysodmetrics
 ```
 
 ### Examples
  
- * <./tests/test_metrics.py>
+* <./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()