Spaces:
Sleeping
Sleeping
Merge pull request #10 from umyuu/feature/0.0.8
Browse files- app.py +53 -23
- assets/DSC_0108.webp +0 -0
- assets/DSC_0297.webp +0 -0
- assets/black_256x256.webp +0 -0
- assets/grayscale_256x256.webp +0 -0
- docs/ThirdPartyNotices.txt +8 -0
- src/__init__.py +1 -1
- src/args_parser.py +23 -0
- src/reporter.py +1 -1
- src/saliency.py +23 -9
- src/utils.py +18 -11
app.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
"""
|
3 |
SaliencyMapDemo
|
4 |
"""
|
5 |
-
|
6 |
#from datetime import datetime
|
7 |
import sys
|
8 |
from typing import Literal
|
@@ -11,6 +11,7 @@ import gradio as gr
|
|
11 |
import numpy as np
|
12 |
|
13 |
from src import PROGRAM_NAME, get_package_version
|
|
|
14 |
from src.reporter import log
|
15 |
from src.saliency import SaliencyMap, convert_colormap
|
16 |
from src.utils import Stopwatch
|
@@ -20,25 +21,6 @@ log.info("#アプリ起動中")
|
|
20 |
watch = Stopwatch.start_new()
|
21 |
|
22 |
|
23 |
-
def parse_args():
|
24 |
-
"""
|
25 |
-
コマンドライン引数の解析を行います
|
26 |
-
"""
|
27 |
-
parser = ArgumentParser(prog=PROGRAM_NAME, description=PROGRAM_NAME)
|
28 |
-
parser.add_argument('--inbrowser',
|
29 |
-
action=BooleanOptionalAction, default=True, help="Gradio inbrowser")
|
30 |
-
parser.add_argument('--share',
|
31 |
-
action=BooleanOptionalAction, default=False, help="Gradio share")
|
32 |
-
parser.add_argument('--server_port',
|
33 |
-
type=int, default=7860, help="Gradio server port")
|
34 |
-
parser.add_argument('--max_file_size',
|
35 |
-
type=str, default="20MB", help="Gradio max file size")
|
36 |
-
parser.add_argument('--version',
|
37 |
-
action='version', version=f'%(prog)s {__version__}')
|
38 |
-
|
39 |
-
return parser.parse_args()
|
40 |
-
|
41 |
-
|
42 |
def jet_tab_selected(image: np.ndarray):
|
43 |
"""
|
44 |
JETタブを選択時
|
@@ -101,6 +83,21 @@ def submit_clicked(image: np.ndarray, algorithm: Literal["SpectralResidual", "Fi
|
|
101 |
return jet, hot
|
102 |
|
103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
args = parse_args()
|
105 |
"""
|
106 |
アプリの画面を作成し、Gradioサービスを起動します。
|
@@ -123,9 +120,15 @@ with gr.Blocks(
|
|
123 |
""")
|
124 |
with gr.Accordion("取り扱い説明書", open=False):
|
125 |
gr.Markdown("""
|
126 |
-
|
127 |
-
|
|
|
|
|
128 |
3. 結果は、JETタブとHOTタブに表示します。
|
|
|
|
|
|
|
|
|
129 |
""")
|
130 |
algorithm_type = gr.Radio(
|
131 |
["SpectralResidual", "FineGrained"],
|
@@ -150,6 +153,32 @@ with gr.Blocks(
|
|
150 |
# inputs=[image_input],
|
151 |
# outputs=image_overlay_hot, api_name=False)
|
152 |
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
submit_button.click(
|
154 |
submit_clicked,
|
155 |
inputs=[image_input, algorithm_type],
|
@@ -160,9 +189,10 @@ with gr.Blocks(
|
|
160 |
App {get_package_version()}
|
161 |
""")
|
162 |
|
163 |
-
demo.queue(default_concurrency_limit=
|
164 |
|
165 |
log.info(f"#アプリ起動完了({watch.elapsed:.3f}s)アプリを終了するにはCtrl+Cキーを入力してください。")
|
|
|
166 |
|
167 |
|
168 |
if __name__ == "__main__":
|
|
|
2 |
"""
|
3 |
SaliencyMapDemo
|
4 |
"""
|
5 |
+
|
6 |
#from datetime import datetime
|
7 |
import sys
|
8 |
from typing import Literal
|
|
|
11 |
import numpy as np
|
12 |
|
13 |
from src import PROGRAM_NAME, get_package_version
|
14 |
+
from src.args_parser import parse_args
|
15 |
from src.reporter import log
|
16 |
from src.saliency import SaliencyMap, convert_colormap
|
17 |
from src.utils import Stopwatch
|
|
|
21 |
watch = Stopwatch.start_new()
|
22 |
|
23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
def jet_tab_selected(image: np.ndarray):
|
25 |
"""
|
26 |
JETタブを選択時
|
|
|
83 |
return jet, hot
|
84 |
|
85 |
|
86 |
+
def gallery_selected(_, evt: gr.SelectData):
|
87 |
+
"""
|
88 |
+
ギャラリーの画像が選択されたときに呼び出されるコールバック関数。
|
89 |
+
|
90 |
+
Parameters:
|
91 |
+
_ (Unused): 使用されない引数。
|
92 |
+
evt (gr.SelectData): Gradioのギャラリー選択イベントデータ。
|
93 |
+
Returns:
|
94 |
+
str: 選択されたギャラリー画像のパス。
|
95 |
+
"""
|
96 |
+
image_path = evt.value['image']['path']
|
97 |
+
|
98 |
+
return image_path
|
99 |
+
|
100 |
+
|
101 |
args = parse_args()
|
102 |
"""
|
103 |
アプリの画面を作成し、Gradioサービスを起動します。
|
|
|
120 |
""")
|
121 |
with gr.Accordion("取り扱い説明書", open=False):
|
122 |
gr.Markdown("""
|
123 |
+
### 操作説明
|
124 |
+
顕著性マップデモを使用する手順は以下の通りです:
|
125 |
+
1. inputタブで画像を選択します。下部の📋下部のクリップボードアイコン(コピー&ペーストアイコン)よりクリップボートから入力することも出来ます。
|
126 |
+
2. Submitボタンを押すと、入力された画像が処理されます。
|
127 |
3. 結果は、JETタブとHOTタブに表示します。
|
128 |
+
### 活用アイデア🎨
|
129 |
+
このデモは、創作活動の際に注目するポイントを視覚化するために役立ちます。視覚化された結果を基に、どの部分に加筆が必要かを判断することができます。
|
130 |
+
例えば、目に注目するポイントが少ない場合は、目を重点的に加筆することで、作品全体の魅力を高めることができるかもしれません。
|
131 |
+
ご利用いただき、ありがとうございます。
|
132 |
""")
|
133 |
algorithm_type = gr.Radio(
|
134 |
["SpectralResidual", "FineGrained"],
|
|
|
153 |
# inputs=[image_input],
|
154 |
# outputs=image_overlay_hot, api_name=False)
|
155 |
#
|
156 |
+
with gr.Accordion("Sample Image Gallery", open=False):
|
157 |
+
gr.Markdown("""
|
158 |
+
### 画像のライセンス表示
|
159 |
+
画像のライセンスはすべてCC0(パブリックドメイン)です。
|
160 |
+
""")
|
161 |
+
gallery = gr.Gallery(type="filepath",
|
162 |
+
value=["assets/black_256x256.webp",
|
163 |
+
"assets/grayscale_256x256.webp",
|
164 |
+
"assets/DSC_0108.webp",
|
165 |
+
"assets/DSC_0297.webp"],
|
166 |
+
label="Sample Gallery",
|
167 |
+
interactive=False,
|
168 |
+
#height=156,
|
169 |
+
columns=5,
|
170 |
+
allow_preview=False,
|
171 |
+
selected_index=0,
|
172 |
+
preview=False,
|
173 |
+
show_download_button=False,
|
174 |
+
show_share_button=False
|
175 |
+
)
|
176 |
+
# ギャラリー内の画像を選択時
|
177 |
+
gallery.select(gallery_selected,
|
178 |
+
inputs=[gallery],
|
179 |
+
outputs=[image_input],
|
180 |
+
show_api=False
|
181 |
+
)
|
182 |
submit_button.click(
|
183 |
submit_clicked,
|
184 |
inputs=[image_input, algorithm_type],
|
|
|
189 |
App {get_package_version()}
|
190 |
""")
|
191 |
|
192 |
+
demo.queue(default_concurrency_limit=5)
|
193 |
|
194 |
log.info(f"#アプリ起動完了({watch.elapsed:.3f}s)アプリを終了するにはCtrl+Cキーを入力してください。")
|
195 |
+
log.debug("reload")
|
196 |
|
197 |
|
198 |
if __name__ == "__main__":
|
assets/DSC_0108.webp
ADDED
![]() |
assets/DSC_0297.webp
ADDED
![]() |
assets/black_256x256.webp
ADDED
![]() |
assets/grayscale_256x256.webp
ADDED
![]() |
docs/ThirdPartyNotices.txt
CHANGED
@@ -4,6 +4,14 @@ This repository uses the materials listed or described below.
|
|
4 |
|
5 |
---------------------------------------------------------
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
Gradio - Apache-2.0
|
8 |
https://github.com/gradio-app/gradio
|
9 |
---------------------------------------------------------
|
|
|
4 |
|
5 |
---------------------------------------------------------
|
6 |
|
7 |
+
DSC_0297 - CC0
|
8 |
+
https://www.flickr.com/photos/layout_nu/14445520741/
|
9 |
+
---------------------------------------------------------
|
10 |
+
|
11 |
+
DSC_0108 - CC0
|
12 |
+
https://www.flickr.com/photos/layout_nu/14239881090/
|
13 |
+
---------------------------------------------------------
|
14 |
+
|
15 |
Gradio - Apache-2.0
|
16 |
https://github.com/gradio-app/gradio
|
17 |
---------------------------------------------------------
|
src/__init__.py
CHANGED
@@ -9,7 +9,7 @@ from src.utils import get_package_version
|
|
9 |
|
10 |
__all__ = ["LocalTimeFormatter"]
|
11 |
|
12 |
-
PROGRAM_NAME = 'SaliencyMapDemo'
|
13 |
__version__ = get_package_version()
|
14 |
|
15 |
|
|
|
9 |
|
10 |
__all__ = ["LocalTimeFormatter"]
|
11 |
|
12 |
+
PROGRAM_NAME: str = 'SaliencyMapDemo'
|
13 |
__version__ = get_package_version()
|
14 |
|
15 |
|
src/args_parser.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""コマンドライン引数の解析"""
|
3 |
+
from argparse import ArgumentParser, BooleanOptionalAction
|
4 |
+
from . import PROGRAM_NAME, get_package_version
|
5 |
+
|
6 |
+
|
7 |
+
def parse_args():
|
8 |
+
"""
|
9 |
+
コマンドライン引数の解析を行います
|
10 |
+
"""
|
11 |
+
parser = ArgumentParser(prog=PROGRAM_NAME, description=PROGRAM_NAME)
|
12 |
+
parser.add_argument('--inbrowser',
|
13 |
+
action=BooleanOptionalAction, default=True, help="Gradio inbrowser")
|
14 |
+
parser.add_argument('--share',
|
15 |
+
action=BooleanOptionalAction, default=False, help="Gradio share")
|
16 |
+
parser.add_argument('--server_port',
|
17 |
+
type=int, default=7860, help="Gradio server port")
|
18 |
+
parser.add_argument('--max_file_size',
|
19 |
+
type=str, default="20MB", help="Gradio max file size")
|
20 |
+
parser.add_argument('--version',
|
21 |
+
action='version', version=f'%(prog)s {get_package_version()}')
|
22 |
+
|
23 |
+
return parser.parse_args()
|
src/reporter.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
# -*- coding: utf-8 -*-
|
2 |
"""
|
3 |
-
Reporter
|
4 |
ログハンドラーが重複登録されるのを防ぐために1箇所で生成してログハンドラーを返します。
|
5 |
Example:
|
6 |
from src.reporter import log
|
@@ -27,6 +26,7 @@ class Reporter:
|
|
27 |
|
28 |
def __new__(cls):
|
29 |
"""
|
|
|
30 |
"""
|
31 |
# インスタンスがまだ存在しない場合は新たに作成します。
|
32 |
if not cls._instance:
|
|
|
1 |
# -*- coding: utf-8 -*-
|
2 |
"""
|
|
|
3 |
ログハンドラーが重複登録されるのを防ぐために1箇所で生成してログハンドラーを返します。
|
4 |
Example:
|
5 |
from src.reporter import log
|
|
|
26 |
|
27 |
def __new__(cls):
|
28 |
"""
|
29 |
+
インスタンスの生成を制御します。
|
30 |
"""
|
31 |
# インスタンスがまだ存在しない場合は新たに作成します。
|
32 |
if not cls._instance:
|
src/saliency.py
CHANGED
@@ -8,7 +8,8 @@ import cv2
|
|
8 |
|
9 |
class SaliencyMap:
|
10 |
"""
|
11 |
-
|
|
|
12 |
Example:
|
13 |
from src.saliency import SaliencyMap
|
14 |
|
@@ -19,6 +20,15 @@ class SaliencyMap:
|
|
19 |
self,
|
20 |
algorithm: Literal["SpectralResidual", "FineGrained"] = "SpectralResidual",
|
21 |
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
self.algorithm = algorithm
|
23 |
# OpenCVのsaliencyを作成します。
|
24 |
if algorithm == "SpectralResidual":
|
@@ -28,17 +38,16 @@ class SaliencyMap:
|
|
28 |
|
29 |
def compute(self, image: np.ndarray) -> Tuple[bool, Any]:
|
30 |
"""
|
31 |
-
|
32 |
|
33 |
Parameters:
|
34 |
image: 入力画像
|
35 |
|
36 |
Returns:
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
"""
|
41 |
-
# 画像の顕著性を計算します。
|
42 |
return self.saliency.computeSaliency(image)
|
43 |
|
44 |
|
@@ -48,19 +57,24 @@ def convert_colormap(
|
|
48 |
colormap_name: Literal["jet", "hot", "turbo"] = "jet"
|
49 |
):
|
50 |
"""
|
51 |
-
|
52 |
|
53 |
Parameters:
|
54 |
image: 入力画像
|
55 |
saliency_map: 顕著性マップ
|
56 |
colormap_name: カラーマップの種類
|
|
|
|
|
|
|
57 |
|
58 |
Returns:
|
59 |
-
np.ndarray:
|
60 |
"""
|
61 |
maps = {"jet": cv2.COLORMAP_JET, "hot": cv2.COLORMAP_HOT, "turbo": cv2.COLORMAP_TURBO}
|
|
|
|
|
62 |
if colormap_name not in maps:
|
63 |
-
raise ValueError(colormap_name)
|
64 |
|
65 |
# 顕著性マップをカラーマップに変換
|
66 |
saliency_map = (saliency_map * 255).astype("uint8")
|
|
|
8 |
|
9 |
class SaliencyMap:
|
10 |
"""
|
11 |
+
画像から顕著性マップを計算するクラス。
|
12 |
+
|
13 |
Example:
|
14 |
from src.saliency import SaliencyMap
|
15 |
|
|
|
20 |
self,
|
21 |
algorithm: Literal["SpectralResidual", "FineGrained"] = "SpectralResidual",
|
22 |
):
|
23 |
+
"""
|
24 |
+
SaliencyMapオブジェクトを初期化します。
|
25 |
+
|
26 |
+
Parameters:
|
27 |
+
algorithm: 使用する顕著性マップアルゴリズムの種類。
|
28 |
+
有効なアルゴリズムについてはOpenCVのドキュメントを参照してください。
|
29 |
+
https://docs.opencv.org/4.9.0/d8/d65/group__saliency.html
|
30 |
+
|
31 |
+
"""
|
32 |
self.algorithm = algorithm
|
33 |
# OpenCVのsaliencyを作成します。
|
34 |
if algorithm == "SpectralResidual":
|
|
|
38 |
|
39 |
def compute(self, image: np.ndarray) -> Tuple[bool, Any]:
|
40 |
"""
|
41 |
+
入力画像から顕著性マップを計算します。
|
42 |
|
43 |
Parameters:
|
44 |
image: 入力画像
|
45 |
|
46 |
Returns:
|
47 |
+
Tuple[bool, Any]: 顕著性マップの計算結果。
|
48 |
+
bool値がTrueの場合は計算成功、Falseの場合は失敗。
|
49 |
+
顕著性マップのデータ。
|
50 |
"""
|
|
|
51 |
return self.saliency.computeSaliency(image)
|
52 |
|
53 |
|
|
|
57 |
colormap_name: Literal["jet", "hot", "turbo"] = "jet"
|
58 |
):
|
59 |
"""
|
60 |
+
入力画像と顕著性マップを合成し、指定されたカラーマップを適用します。
|
61 |
|
62 |
Parameters:
|
63 |
image: 入力画像
|
64 |
saliency_map: 顕著性マップ
|
65 |
colormap_name: カラーマップの種類
|
66 |
+
"jet": Jetカラーマップ
|
67 |
+
"hot": Hotカラーマップ
|
68 |
+
"turbo": Turboカラーマップ
|
69 |
|
70 |
Returns:
|
71 |
+
np.ndarray: 合成された画像 (RGBA形式)
|
72 |
"""
|
73 |
maps = {"jet": cv2.COLORMAP_JET, "hot": cv2.COLORMAP_HOT, "turbo": cv2.COLORMAP_TURBO}
|
74 |
+
|
75 |
+
# colormap_nameが有効かどうかをチェック
|
76 |
if colormap_name not in maps:
|
77 |
+
raise ValueError(f"Invalid colormap name: {colormap_name}")
|
78 |
|
79 |
# 顕著性マップをカラーマップに変換
|
80 |
saliency_map = (saliency_map * 255).astype("uint8")
|
src/utils.py
CHANGED
@@ -6,9 +6,9 @@ import time
|
|
6 |
|
7 |
def get_package_version() -> str:
|
8 |
"""
|
9 |
-
|
10 |
"""
|
11 |
-
return '0.0.
|
12 |
|
13 |
|
14 |
@dataclass
|
@@ -29,18 +29,20 @@ class Stopwatch:
|
|
29 |
@property
|
30 |
def elapsed(self) -> float:
|
31 |
"""
|
32 |
-
|
|
|
|
|
|
|
33 |
"""
|
34 |
if self._is_running:
|
35 |
-
|
36 |
-
self._elapsed = end_time - self._start_time
|
37 |
|
38 |
return self._elapsed
|
39 |
|
40 |
@property
|
41 |
def is_running(self) -> bool:
|
42 |
"""
|
43 |
-
|
44 |
"""
|
45 |
return self._is_running
|
46 |
|
@@ -53,9 +55,12 @@ class Stopwatch:
|
|
53 |
self._is_running = True
|
54 |
|
55 |
@classmethod
|
56 |
-
def start_new(cls):
|
57 |
"""
|
58 |
-
|
|
|
|
|
|
|
59 |
"""
|
60 |
stopwatch = Stopwatch()
|
61 |
stopwatch.start()
|
@@ -63,10 +68,12 @@ class Stopwatch:
|
|
63 |
|
64 |
def stop(self) -> float:
|
65 |
"""
|
66 |
-
|
|
|
|
|
|
|
67 |
"""
|
68 |
if self._is_running:
|
69 |
-
|
70 |
-
self._elapsed = end_time - self._start_time
|
71 |
self._is_running = False
|
72 |
return self._elapsed
|
|
|
6 |
|
7 |
def get_package_version() -> str:
|
8 |
"""
|
9 |
+
バージョン情報を取得します。
|
10 |
"""
|
11 |
+
return '0.0.8'
|
12 |
|
13 |
|
14 |
@dataclass
|
|
|
29 |
@property
|
30 |
def elapsed(self) -> float:
|
31 |
"""
|
32 |
+
計測中の経過時間を取得します。
|
33 |
+
|
34 |
+
Returns:
|
35 |
+
float: 計測中の経過時間(小数秒)
|
36 |
"""
|
37 |
if self._is_running:
|
38 |
+
self._elapsed = time.perf_counter() - self._start_time
|
|
|
39 |
|
40 |
return self._elapsed
|
41 |
|
42 |
@property
|
43 |
def is_running(self) -> bool:
|
44 |
"""
|
45 |
+
計測が実行中であるかどうかを取得します
|
46 |
"""
|
47 |
return self._is_running
|
48 |
|
|
|
55 |
self._is_running = True
|
56 |
|
57 |
@classmethod
|
58 |
+
def start_new(cls) -> 'Stopwatch':
|
59 |
"""
|
60 |
+
新しいストップウォッチを生成し、計測を開始します。
|
61 |
+
|
62 |
+
Returns:
|
63 |
+
Stopwatch: 新しいストップウォッチオブジェクト
|
64 |
"""
|
65 |
stopwatch = Stopwatch()
|
66 |
stopwatch.start()
|
|
|
68 |
|
69 |
def stop(self) -> float:
|
70 |
"""
|
71 |
+
計測を終了し、経過時間を返します。
|
72 |
+
|
73 |
+
Returns:
|
74 |
+
float: 計測中の経過時間
|
75 |
"""
|
76 |
if self._is_running:
|
77 |
+
self._elapsed = time.perf_counter() - self._start_time
|
|
|
78 |
self._is_running = False
|
79 |
return self._elapsed
|