maskveil 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. maskveil-0.1.0/LICENSE +21 -0
  2. maskveil-0.1.0/MANIFEST.in +17 -0
  3. maskveil-0.1.0/PKG-INFO +421 -0
  4. maskveil-0.1.0/README.md +366 -0
  5. maskveil-0.1.0/__init__.py +41 -0
  6. maskveil-0.1.0/core/__init__.py +5 -0
  7. maskveil-0.1.0/core/class_mapping.py +45 -0
  8. maskveil-0.1.0/core/constants.py +46 -0
  9. maskveil-0.1.0/core/detector.py +59 -0
  10. maskveil-0.1.0/core/geometry.py +191 -0
  11. maskveil-0.1.0/core/labels.py +87 -0
  12. maskveil-0.1.0/core/masks.py +201 -0
  13. maskveil-0.1.0/core/mosaic.py +123 -0
  14. maskveil-0.1.0/core/onnx_utils.py +34 -0
  15. maskveil-0.1.0/core/pipeline.py +157 -0
  16. maskveil-0.1.0/core/postprocess.py +88 -0
  17. maskveil-0.1.0/core/preprocess.py +26 -0
  18. maskveil-0.1.0/core/preview.py +112 -0
  19. maskveil-0.1.0/core/raster.py +245 -0
  20. maskveil-0.1.0/core/refiner.py +256 -0
  21. maskveil-0.1.0/core/tensors.py +28 -0
  22. maskveil-0.1.0/core/types.py +88 -0
  23. maskveil-0.1.0/maskveil.egg-info/PKG-INFO +421 -0
  24. maskveil-0.1.0/maskveil.egg-info/SOURCES.txt +48 -0
  25. maskveil-0.1.0/maskveil.egg-info/dependency_links.txt +1 -0
  26. maskveil-0.1.0/maskveil.egg-info/entry_points.txt +4 -0
  27. maskveil-0.1.0/maskveil.egg-info/requires.txt +15 -0
  28. maskveil-0.1.0/maskveil.egg-info/top_level.txt +1 -0
  29. maskveil-0.1.0/nodes/__init__.py +33 -0
  30. maskveil-0.1.0/nodes/_convert.py +124 -0
  31. maskveil-0.1.0/nodes/_envelope.py +228 -0
  32. maskveil-0.1.0/nodes/_inputs.py +35 -0
  33. maskveil-0.1.0/nodes/_models.py +213 -0
  34. maskveil-0.1.0/nodes/_preview.py +62 -0
  35. maskveil-0.1.0/nodes/_segs.py +102 -0
  36. maskveil-0.1.0/nodes/detector.py +146 -0
  37. maskveil-0.1.0/nodes/mosaic.py +175 -0
  38. maskveil-0.1.0/nodes/refiner.py +193 -0
  39. maskveil-0.1.0/pyproject.toml +103 -0
  40. maskveil-0.1.0/setup.cfg +4 -0
  41. maskveil-0.1.0/tools/__init__.py +4 -0
  42. maskveil-0.1.0/tools/_common.py +117 -0
  43. maskveil-0.1.0/tools/censor.py +631 -0
  44. maskveil-0.1.0/tools/detect.py +141 -0
  45. maskveil-0.1.0/tools/paths.py +48 -0
  46. maskveil-0.1.0/tools/refine.py +363 -0
  47. maskveil-0.1.0/web/__init__.py +1 -0
  48. maskveil-0.1.0/web/hotscreen_classes.js +214 -0
maskveil-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 purple-ocean-ego
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ # sdist に含めないもの
2
+ #
3
+ # wheel は `[tool.setuptools] packages` と `package-data` で制御しているため、
4
+ # ここでの指定は **sdist(.tar.gz)** に効く。
5
+
6
+ # テスト・運用資材・アイコン画像(配布物には不要)
7
+ prune tests
8
+ prune assets
9
+ prune .github
10
+
11
+ # モデルは非同梱(MIT の前提)
12
+ global-exclude *.onnx
13
+ global-exclude *.ONNX
14
+
15
+ # 生成物
16
+ global-exclude *.py[cod]
17
+ global-exclude __pycache__
@@ -0,0 +1,421 @@
1
+ Metadata-Version: 2.4
2
+ Name: maskveil
3
+ Version: 0.1.0
4
+ Summary: Shape-following region censor: ONNX detection, contour masks, and mosaic/blur/black modes. ComfyUI nodes and CLI. Works with any model that follows the hotscreen 16-class contract.
5
+ Author: purple-ocean-ego
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 purple-ocean-ego
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/purple-ocean-ego/maskveil
29
+ Project-URL: Repository, https://github.com/purple-ocean-ego/maskveil
30
+ Project-URL: Bug Tracker, https://github.com/purple-ocean-ego/maskveil/issues
31
+ Project-URL: Documentation, https://github.com/purple-ocean-ego/maskveil#readme
32
+ Keywords: censor,mosaic,pixelate,nsfw,mask,segmentation,onnx,comfyui
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: End Users/Desktop
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Multimedia :: Graphics
41
+ Requires-Python: >=3.10
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: numpy>=1.24
45
+ Requires-Dist: onnxruntime>=1.16
46
+ Requires-Dist: pillow>=10
47
+ Provides-Extra: cli
48
+ Provides-Extra: nodes
49
+ Provides-Extra: dev
50
+ Requires-Dist: pytest>=9; extra == "dev"
51
+ Requires-Dist: pytest-cov>=5; extra == "dev"
52
+ Requires-Dist: ruff>=0.6; extra == "dev"
53
+ Requires-Dist: tomli>=2; python_version < "3.11" and extra == "dev"
54
+ Dynamic: license-file
55
+
56
+ # maskveil
57
+
58
+ **領域を検出し、輪郭に沿ったマスクを作り、モザイク/ぼかし/黒塗りで隠すエンジン。**
59
+ ComfyUI ノードと CLI は、そのエンジンに載せたアダプタです。
60
+
61
+ | 層 | 役割 | 実行時依存 | ComfyUI 依存 |
62
+ | --- | --- | --- | --- |
63
+ | `core/` | 推論・前後処理・座標復元・輪郭抽出(純粋ロジック) | numpy / onnxruntime | **なし** |
64
+ | `tools/` | CLI(`detect` / `refine` / `censor`)。モデルのパス検証を担当 | 上記 + Pillow | なし |
65
+ | `nodes/` | ComfyUI ノード(`folder_paths` によるモデル解決) | 上記 + ComfyUI 提供の torch | あり |
66
+
67
+ - 対象は NSFW 部位に限りません。**対応モデルのクラス契約(16クラス)に含まれるクラス**であれば、
68
+ 顔・文字などの墨塗りにも使えます(任意の ONNX をそのまま受け付けるわけではありません)。
69
+ - 隠蔽方法は **`mosaic` / `blur` / `black`** の3モード(モザイクは選択肢の1つ)。
70
+ - `ultralytics`(AGPL)は**同梱せず、import もしません**。書き出し済み ONNX の推論だけを行います。
71
+
72
+ > **名前について**: `maskveil` はパッケージ(エンジン)の名前です。
73
+ > ノード名が `Hotscreen*` なのは、**同梱するモデルではなく「対応モデルのクラス契約」に紐づくノードだから**です([ノード名の方針](#ノード名の方針))。
74
+
75
+ ## 段構成
76
+
77
+ ```
78
+ detect : IMAGE + 検出モデル → 検出JSON(クラス名・スコア・原画像座標bbox) schema_version 1
79
+ refine : 検出JSON + 輪郭モデル → マスク付きJSON(形状ポリゴン・mask_source) schema_version 2
80
+ censor : 画像/ディレクトリ + 検出モデル(+任意で輪郭)
81
+ → 隠蔽済みPNG + レポートJSON(1コマンド完結)
82
+ ```
83
+
84
+ - `detect` は高速(約 0.5 秒/枚・CLI 全体)。`refine` は低速(約 4.8 秒/枚 @ CPU, 1280px)だが任意。
85
+ - `detect` に輪郭を内包する便宜フラグは**設けない**(1コマンド1モデル)。
86
+
87
+ ## 対応モデル(**非同梱**)
88
+
89
+ **モデルは本パッケージに含まれません。** 利用者が取得し、**絶対パス**で指定します。
90
+ 環境変数・既定値・ディレクトリ探索は行いません(ComfyUI ノードのみ `folder_paths` で解決します)。
91
+
92
+ | 用途 | 必要なもの | 例 | SHA-256(開発で使用したビルド) |
93
+ | --- | --- | --- | --- |
94
+ | 検出 | YOLO11 検出 ONNX(16クラス) | `hs-real-anime-y11n-640-fp32.onnx` | `fecf08b62cf091df6b46222e163ac1721570d5f0a9cffa93591e5bb46f61bccf` |
95
+ | 輪郭(任意) | YOLO11-Seg ONNX(7クラス) | `nsfw-anime-xl-x1280.onnx` | `7d193296684e13d9998f50bdf4bb4696ff826785b6d22f5260313e353e5cae1f` |
96
+
97
+ ### モデルの差し替え可能性
98
+
99
+ エンジンはモデル名に依存しませんが、**クラス契約**には依存します。
100
+
101
+ | 前提 | 根拠 | 満たさない場合 |
102
+ | --- | --- | --- |
103
+ | YOLO 系の出力 `(1, 4+nc, N)` | `core/postprocess.py` | デコード処理の追加が必要 |
104
+ | クラス数 16・クラス名と順序が一致 | `core/labels.py` の `LABELS` | `LABELS` の差し替え(データ修正) |
105
+ | 静的・正方形入力 | `core/onnx_utils.py` | 入力サイズの明示指定、非正方形は非対応 |
106
+ | NMS 非内蔵 | 自前の class-aware NMS | 二重適用に注意 |
107
+ | 輪郭は YOLO11-Seg(プロトタイプ 32) | `core/refiner.py` | SAM 等はデコード実装の追加が必要 |
108
+
109
+ ## インストール
110
+
111
+ ### ComfyUI にノードとして入れる
112
+
113
+ ```bash
114
+ # ComfyUI Manager を使う場合: 「maskveil」を検索してインストール
115
+ # または Comfy Registry CLI
116
+ comfy node install maskveil
117
+
118
+ # 手動の場合(ComfyUI/custom_nodes/ 配下に clone する)
119
+ cd ComfyUI/custom_nodes
120
+ git clone https://github.com/purple-ocean-ego/maskveil.git
121
+ # 手動 clone では依存が自動で入らないため、必ず入れる
122
+ python -m pip install -r maskveil/requirements.txt
123
+ ```
124
+
125
+ - **`onnxruntime` は ComfyUI 本体の依存に含まれません。** 手動 clone の経路では
126
+ `requirements.txt` を入れるまでノードが読み込まれません(`IMPORT FAILED` になります)。
127
+ - ComfyUI Manager / Registry 経由のインストールでは `requirements.txt` が自動で使われます。
128
+ - ノードの追加は **ComfyUI の再起動**で反映されます。
129
+
130
+ ### CLI として入れる
131
+
132
+ ```bash
133
+ pipx install maskveil # 推奨(隔離環境)
134
+ # または
135
+ pip install maskveil
136
+ ```
137
+
138
+ ## ComfyUI で使う
139
+
140
+ `hotscreen` カテゴリに3ノードを公開します。
141
+
142
+ | ノード(クラス名) | 入力 | 出力 | 役割 |
143
+ | --- | --- | --- | --- |
144
+ | `Hotscreen Detector`(`HotscreenDetector`) | `IMAGE` + 検出モデル + `classes` / `conf` / `iou` / `top_k` / `mask_expand` / `preview_mode` | `HOTSCREEN` / `MASK` / `SEGS` / `detection`(JSON) / `preview` | 検出とマスク生成 |
145
+ | `Hotscreen Refiner`(`HotscreenRefiner`) | `HOTSCREEN` + 輪郭モデル + `refiner_classes` / `conf` / `iou` / `seg_crop` / `mask_expand` / `preview_mode` | `HOTSCREEN` / `MASK` / `refine`(JSON) / `preview` | マスク形状を輪郭へ置換 |
146
+ | `Mosaic by Mask`(`MosaicByMask`) | `mode` / `block_size` +(任意)`hotscreen` / `image` / `mask` | `IMAGE` / `HOTSCREEN` | マスク領域の隠蔽 |
147
+
148
+ ### 配線
149
+
150
+ | 構成 | 配線 | 用途 |
151
+ | --- | --- | --- |
152
+ | 最短 | `Detector → Mosaic`(`HOTSCREEN` 1本) | 輪郭なし・最速 |
153
+ | 標準 | `Detector → Refiner → Mosaic` | 形状追従 |
154
+ | 汎用 | `IMAGE` + `MASK` を `Mosaic` へ直接 | 検出モデルに依存しない隠蔽 |
155
+ | 多段 | 1段目の `hotscreen` を2段目へ、**2段目の `mask` は別 `Detector` から明示接続** | 部位ごとに異なる加工 |
156
+
157
+ - `Mosaic by Mask` の既定は **`mode=mosaic` / `block_size=48`**。48px は 2560×3840 で
158
+ モザイクと分かる粗さです(8px では細かすぎてほぼ隠れません)。
159
+ 細かくしたい場合は `block_size` を下げる(`blur` では半径として働く)
160
+ - `Mosaic` のベース画像は **明示 `image` > `hotscreen.mosaic_image` > `hotscreen.image`** の順。
161
+ 多段では明示 `image` を繋がない(繋ぐと元画像へ戻り連鎖が切れる)
162
+ - `mask` は **明示 `mask` > `hotscreen.mask`**。明示指定で「検出ごとに異なる加工」を組める
163
+ - 全ゼロマスクのときは入力をそのまま返し、`mosaic_image` と `meta.processing` を更新しない
164
+
165
+ ### `mask_expand`(追加の膨張)
166
+
167
+ - 形状を外側へ N px 広げる(**既定 0px**)。`bbox` は変わらない
168
+ - 既定 0 の理由: `mosaic` はブロック量子化のため実効的な外側余白(最大 `block_size-1` px)を持つ。
169
+ `blur` / `black` は境界が厳密なので、必要なら明示的に広げる
170
+ - `masks` を持たない検出(`mask_source="none"`)は **bbox 矩形を外挿**してマスク化する
171
+ - 膨張後の形状が正であり、`HOTSCREEN.detections[].masks`・`MASK`・`SEGS.cropped_mask` は同じラスタから作られる
172
+
173
+ ### `SEGS`(互換ビュー)の注意
174
+
175
+ - `SEGS` は **先頭バッチ(`batch_index == 0`)の画像だけ**を表す。全バッチの検出は
176
+ `HOTSCREEN.detections` と `MASK`(`[B,H,W]`)が保持する
177
+ - `SEG.bbox` / `crop_region` は **`mask_expand` を含むマスク全体を囲む**。画像端では画像内へ
178
+ クリップされるため、検出の `bbox` と一致しないことがある
179
+ - `SEGS` は Impact Pack 互換の**構造のみ**を提供する(実接続は未検証)
180
+
181
+ ### モデルの解決
182
+
183
+ - `folder_paths` の `hotscreen` カテゴリ(`extra_model_paths.yaml`)から **`.onnx` のみ**を候補にする
184
+ ```yaml
185
+ # extra_model_paths.yaml(例)
186
+ hotscreen: hotscreen
187
+ ```
188
+ - 入力サイズは det 640 / seg 1280 を要求し、取り違えると `ValueError` で停止する
189
+ - `InferenceSession` はプロセス内でキャッシュする(パス + mtime + size + 入力サイズ)
190
+
191
+ ### クラス選択 UI
192
+
193
+ `web/hotscreen_classes.js` が `classes` ウィジェットの選択 UI(`all` の展開・定義順の正規化)を提供します。
194
+ モデル固有 UI のため、ノード名と同じく `Hotscreen*` の命名を維持しています。
195
+
196
+ ## CLI で使う
197
+
198
+ ```bash
199
+ # 1) 検出(高速)
200
+ maskveil-detect input.png \
201
+ --classes MALE_GENITALIA_EXPOSED \
202
+ --model /abs/det/hs-real-anime-y11n-640-fp32.onnx \
203
+ > det.json
204
+
205
+ # 2) 輪郭化(低速・任意)
206
+ maskveil-refine < det.json \
207
+ --refiner-model /abs/seg/nsfw-anime-xl-x1280.onnx \
208
+ > refined.json
209
+
210
+ # 3) 隠蔽まで1コマンド(ディレクトリを再帰処理)
211
+ maskveil "$HOME/pictures/input" \
212
+ --det-model /abs/det/hs-real-anime-y11n-640-fp32.onnx \
213
+ --output-dir /abs/out \
214
+ --report /abs/out/report.json
215
+
216
+ # 輪郭(Refiner)版
217
+ maskveil /abs/input.png --det-model /abs/det.onnx --refine \
218
+ --refiner-model /abs/seg/nsfw-anime-xl-x1280.onnx
219
+ ```
220
+
221
+ `python -m maskveil.tools.detect` / `.refine` / `.censor` でも同じことができます。
222
+
223
+ ### `detect` の主なオプション
224
+
225
+ > 正は `maskveil-detect --help`。以下の表は既定値の要約です。
226
+
227
+ | オプション | 既定 | 説明 |
228
+ | --- | --- | --- |
229
+ | `--classes` | **必須** | 名前 / ID / `all` のカンマ区切り。未指定は `exit 2` |
230
+ | `--conf` | 0.2 | スコア閾値(モデル作者の推奨値) |
231
+ | `--iou` | 0.45 | class-aware NMS の IoU 閾値 |
232
+ | `--input-size` | モデルから取得 | モデル入力の一辺。指定するとモデルと一致するか検証する |
233
+ | `--top-k` | 0 | スコア上位N件に絞る(0 は無効) |
234
+ | `--list-classes` | - | 16クラスを `id<TAB>name` で表示して終了 |
235
+
236
+ ### `refine` の主なオプション
237
+
238
+ > 正は `maskveil-refine --help`。以下の表は既定値の要約です。
239
+
240
+ | オプション | 既定 | 説明 |
241
+ | --- | --- | --- |
242
+ | `--refiner-model` | (必須) | `--mask-source model_seg` のとき必須。絶対パス |
243
+ | `--refiner-input-size` | モデルから取得 | 輪郭モデルの入力一辺。指定時はモデルと一致するか検証 |
244
+ | `--classes` | 入力JSONの全検出 | 輪郭化する検出クラスを限定 |
245
+ | `--mask-source` | `model_seg` | `bbox_ellipse` は**デバッグ用**(内接楕円) |
246
+ | `--seg-crop` | false | 検出 bbox 周辺をクロップして推論(計測用。1検出ごとに推論するため遅い) |
247
+
248
+ - 終了コード: `0` 成功(0件含む) / `1` 実行時エラー(入力JSON不正・画像読込失敗など) / `2` 引数エラー。
249
+ - 輪郭モデルは**1画像1回**だけ実行し、対応する全クラスのマスクを一度に取得する(クラスごとに再実行しない)。
250
+
251
+ ### `censor`(隠蔽まで1コマンド)
252
+
253
+ `detect →(--refine 時のみ)refine → 隠蔽 → PNG 保存` を1コマンドで行います。
254
+ **ComfyUI・WebUI・コンテナを起動しません**(`tools/` は torch 非依存)。`detect` / `refine` が
255
+ 「1コマンド1モデル」なのに対し、`censor` は複合ドライバとして例外的に2モデルを扱います。
256
+
257
+ | オプション | 既定 | 説明 |
258
+ | --- | --- | --- |
259
+ | `INPUT` | (必須) | 画像、またはディレクトリ |
260
+ | `--det-model` | (必須) | 検出モデルの**絶対パス** |
261
+ | `--refine` | false | 輪郭モデルでマスク形状を精密化する |
262
+ | `--refiner-model` | - | `--refine` のとき必須(絶対パス)。`--refine` 無しで渡すと `exit 2` |
263
+ | `--classes` | 性器 EXPOSED 2クラス | 名前 / ID / `all` のカンマ区切り |
264
+ | `--conf` / `--iou` | 0.2 / 0.45 | 検出・輪郭の閾値 |
265
+ | `--block-size` | **48** | モザイクのブロック辺(2–128) |
266
+ | `--mask-expand` | **0** | マスクの追加膨張(0–256)。既定 0 はブロック量子化の余白で足りるため |
267
+ | `--png-compress-level` | 1 | PNG 圧縮レベル(0–9) |
268
+ | `--output-dir` | 画像→親 / ディレクトリ→`<入力名>_censored` | 出力先(入力ディレクトリと同じ場所は `exit 2`) |
269
+ | `--no-recursive` | false | 直下のみ処理する |
270
+ | `--report` | 標準出力 | レポート JSON の保存先(指定時は stdout にサマリ1行) |
271
+ | `--pretty` | false | JSON を整形する |
272
+
273
+ ファイル名はフォルダごとに `0001` 起点の `NNNN_<元ファイル名>.png` で、入力の相対パス構造を保ちます。
274
+ 検出0件の画像は無加工でコピーされ、レポートでは `clean` として記録されます。
275
+
276
+ ## 出力JSON
277
+
278
+ **`detect`(schema_version 1)**
279
+
280
+ ```json
281
+ {
282
+ "schema_version": 1,
283
+ "image": {"path": "/abs/input.png", "width": 2560, "height": 3840},
284
+ "model": {"path": "/abs/det.onnx", "sha256": "fecf08b6...", "input_size": 640},
285
+ "params": {"conf": 0.2, "iou": 0.45, "classes": [11]},
286
+ "detections": [
287
+ {"class_id": 11, "class_name": "MALE_GENITALIA_EXPOSED", "score": 0.713,
288
+ "bbox": [1587.0, 1247.0, 2258.0, 1637.0]}
289
+ ],
290
+ "count": 1
291
+ }
292
+ ```
293
+
294
+ **`refine`(schema_version 2)** — `detect` の内容を保ち、`masks` / `mask_source` を追加します。
295
+
296
+ ```json
297
+ {
298
+ "schema_version": 2,
299
+ "image": {"path": "/abs/input.png", "width": 2560, "height": 3840},
300
+ "model": {"det": {"path": "...", "sha256": "...", "input_size": 640},
301
+ "seg": {"path": "/abs/seg.onnx", "sha256": "...", "input_size": 1280}},
302
+ "params": {
303
+ "det": {"conf": 0.2, "iou": 0.45, "classes": [11]},
304
+ "refine": {"mask_source": "model_seg", "classes": "all", "conf": 0.2, "iou": 0.45,
305
+ "seg_crop": false}
306
+ },
307
+ "detections": [
308
+ {"class_id": 11, "class_name": "MALE_GENITALIA_EXPOSED", "score": 0.713,
309
+ "bbox": [1587.0, 1247.0, 2258.0, 1637.0],
310
+ "mask_source": "model_seg",
311
+ "masks": [[[1786.0, 1332.0], "..."]]}
312
+ ],
313
+ "count": 1,
314
+ "refine": {"targets": 1, "refined": 1, "preserved": 0, "mask_source": "model_seg"}
315
+ }
316
+ ```
317
+
318
+ - `masks` は**原画像座標のポリゴンの配列**(連結成分ごと。各要素は閉路)。分断されたマスクも
319
+ すべて保持する(1本だけ採用すると未被覆が生まれるため)
320
+ - `mask_source` は `model_seg` / `bbox_ellipse` / `none`
321
+ - 対応表に無いクラス、または重なる輪郭が無かった検出は `mask_source: "none"`(`masks` なし)
322
+ - `refine.preserved` は「入力JSON(v2)で既にマスクを持っていた検出のうち、今回対象外だった件数」。
323
+ **v2 を再投入しても対象外のマスクは保持される(冪等)**
324
+ - `image.path` は絶対パスに解決される。`image.width/height` が実ファイルと不一致なら `exit 1`
325
+
326
+ ## クラス対応(検出16 → 輪郭7)
327
+
328
+ 輪郭モデルが持つのは `anus` / `nipple` / `penis` / `vagina` / `female face` / `male face` /
329
+ `pubic hair` の7クラスのみ。対応表は `core/class_mapping.py` が持ります。
330
+
331
+ | 検出クラス | 輪郭クラス | 備考 |
332
+ | --- | --- | --- |
333
+ | `FEMALE_GENITALIA_EXPOSED` | `vagina` | |
334
+ | `MALE_GENITALIA_EXPOSED` | `penis` | |
335
+ | `ANUS_EXPOSED` | `anus` | |
336
+ | `FEMALE_BREAST_EXPOSED` | `nipple` | **近似**(乳首のみ。乳房全体は覆わない) |
337
+ | `FEMALE_FACE` / `MALE_FACE` | `female face` / `male face` | |
338
+ | 上記以外(COVERED 系・BUTTOCKS・ARMPITS・BELLY・FEET・EYE) | — | `mask_source: "none"` |
339
+
340
+ 対応する輪郭が複数ある場合は **bbox の IoU が最大**のものを1対1で割り当てます。
341
+
342
+ ## 設計上の注意
343
+
344
+ - **マスクは検出 bbox で切り取らない。** 検出モデルと輪郭モデルで bbox が一致しないため、
345
+ 輪郭側の形状をそのまま使う(実測では輪郭が検出 bbox より 5〜154px 大きい場合があり、
346
+ 切り取ると隠蔽漏れになる)。
347
+ - 輪郭の bbox クロップはプロトタイプ格子(320)で量子化するため、seg 自身の bbox に対しても
348
+ 最大 1 格子(入力座標で約 4px)外へ張り出すことがある(安全側の誤差)。
349
+ - `--input-size` / `--refiner-input-size` は省略時に ONNX の静的入力から取得する。指定値が
350
+ モデルと食い違う場合は、推論前に明確なエラーで停止する。
351
+ - 座標復元は軸別倍率(`scale_x` / `scale_y`)で行い、letterbox の往復誤差を出さない。
352
+ - NMS は **class-aware**(同一 bbox でもクラスが違えば残す)。隠蔽漏れを避けるため、
353
+ モデル作者実装のクラス非依存 NMS とは意図的に異なる。
354
+ - `core` は `comfy` / `torch` / `PIL` / `cv2` を import しない(`tests/test_imports.py` が AST で検証)。
355
+ リサイズは numpy のバイリニア実装。
356
+
357
+ ## 性能(実測 / CPU, onnxruntime 1.31.0)
358
+
359
+ | 処理 | 時間 | 備考 |
360
+ | --- | --- | --- |
361
+ | `detect`(2560×3840, CLI全体) | 0.52〜0.55 s | 推論のみ約 0.09 s |
362
+ | `refine`(全体1回, 12検出) | 4.79〜4.98 s | 輪郭推論 約 2.9 s(検出数に依存しない) |
363
+ | `refine --seg-crop`(1検出) | 4.37〜4.46 s | 検出ごとに推論するため検出数に比例 |
364
+ | `censor`(2560×3840, 検出のみ) | 0.74〜1.00 s/枚 | 検出+隠蔽+PNG保存。100枚で約1.5分 |
365
+ | `censor`(同, `--refine`) | 約 4〜5 s/枚 | 輪郭推論の支配的コストが加算される |
366
+
367
+ **参考値であり保証しません。** 計測は CPU 推論(GPU 不使用)の開発環境、onnxruntime 1.31.0 時点の実測です。
368
+ 実行環境・モデル・画像サイズで変動します。
369
+
370
+ ## ノード名の方針
371
+
372
+ **パッケージ名は汎用(`maskveil`)、ノード名はモデル契約ごと(`Hotscreen*`)** という2層の命名です。
373
+
374
+ - `HotscreenDetector` / `HotscreenRefiner` は **hotscreen モデルのクラス契約(16クラス/7クラス)に紐づく**ため、
375
+ ノード名を変更しません。既存のワークフローが壊れないことを優先しています。
376
+ - **別のモデルに対応するときは、既存ノードを改名せず新しいノードクラスを追加します。**
377
+ これにより、モデルを増やしても既存ワークフローは影響を受けません。
378
+ - カテゴリ文字列 `hotscreen` と `web/hotscreen_classes.js` も同じ理由で維持しています。
379
+ - **ただし現時点では、別モデルの追加に `core/` の変更も必要です。** クラス名の一覧
380
+ (`core/labels.py`)と検出→輪郭の対応表(`core/class_mapping.py`)がグローバル定数のため、
381
+ 新しいモデルを足すときは「ノードの追加」に加えてクラス表の追加が要ります
382
+ (既存ノードの挙動は変えません)。
383
+
384
+ ## ライセンス
385
+
386
+ | 対象 | ライセンス | 配布 |
387
+ | --- | --- | --- |
388
+ | **本パッケージのコード**(`core` / `nodes` / `tools` / `web`) | **MIT** | する |
389
+ | **対応モデル**(検出・輪郭の ONNX) | **AGPL-3.0**(モデル提供元の条件) | **しない**(利用者が取得し、絶対パスで指定) |
390
+ | `ultralytics`(学習・ONNX 書き出しに使われるライブラリ) | AGPL-3.0 | **しない**(import もしない) |
391
+
392
+ 本パッケージが MIT である根拠は、**モデルを同梱せず、AGPL ライブラリを import・同梱しない**ことです。
393
+ モデルを同梱した時点でこの前提は崩れるため、リポジトリにも配布物にも `*.onnx` を含めません
394
+ (`tests/test_license.py` が機械検証)。
395
+
396
+ ## 開発
397
+
398
+ ```bash
399
+ git clone https://github.com/purple-ocean-ego/maskveil.git
400
+ cd maskveil
401
+ python -m venv .venv && . .venv/bin/activate
402
+ pip install -e '.[dev]'
403
+
404
+ pytest
405
+ ruff check .
406
+ ruff format --check .
407
+ ```
408
+
409
+ - **clone 先のディレクトリ名は `maskveil` にしてください。** `tests/conftest.py` は親ディレクトリを
410
+ `sys.path` へ追加して `import maskveil` を通すため、別名だと収集に失敗します
411
+ (その場合は `pip install -e .` で解決できます)。
412
+ - `tests/js/check_classes.ts` はクラス選択 UI の検証で、**deno** が必要です(pytest 対象外)。
413
+ JS を実際に import して `parseSelection` / `serializeSelection` を実行します。
414
+ - `tests/test_comfy_loader.py` は ComfyUI と同じ手順(マングル名・`sys.path` に親を入れない)で
415
+ パッケージを読み込みます。`nodes/` が絶対 import に戻るとここで落ちます。
416
+
417
+ ## 制約
418
+
419
+ - 輪郭 ONNX は `ultralytics 8.4.189` で書き出した**静的入力(1280)**版です。動的入力版は未作成。
420
+ - 輪郭が対応しない部位(臀部・脇・腹・足・眼・COVERED 系)は bbox 形状しか持ちません。
421
+ - ComfyUI ノードは GPU 推論を前提にしていません(onnxruntime の CPU 実行で動作します)。