diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index b98eee8..76884a0 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -3,6 +3,7 @@ on: push: tags: - "v*" + workflow_dispatch: jobs: test: @@ -17,10 +18,11 @@ jobs: needs: [test] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v7 - name: Build wheel - run: pipx run build + run: uv build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 0f4fd9b..1e9dcd0 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: days-before-stale: 17 days-before-close: 3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7383ad6..15f8d67 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,9 @@ on: pull_request: paths: - "adetailer/**.py" + - ".github/workflows/test.yml" workflow_call: + workflow_dispatch: schedule: - cron: "0 0 * * 0" @@ -13,18 +15,21 @@ jobs: name: Test on python ${{ matrix.python-version }} runs-on: macos-latest strategy: + fail-fast: false matrix: python-version: - "3.10" - "3.11" - "3.12" + - "3.13" + - "3.14" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python-version }} - name: Run tests - run: uv run --all-extras pytest -v + run: uv run --all-extras --with 'git+https://github.com/ultralytics/CLIP.git' pytest -v diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0413861..672695b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ exclude: ^modules/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files args: [--maxkb=100] @@ -19,13 +19,13 @@ repos: - id: mixed-line-ending - repo: https://github.com/rbubley/mirrors-prettier - rev: v3.5.3 + rev: v3.8.1 hooks: - id: prettier - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.9 + rev: v0.15.0 hooks: - - id: ruff + - id: ruff-check args: [--fix, --exit-non-zero-on-fix] - id: ruff-format diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5284a0f..1a81507 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,6 @@ { "recommendations": [ "ms-python.vscode-pylance", - "ms-python.black-formatter", "kevinrose.vsc-python-indent", "charliermarsh.ruff", "shardulm94.trailing-spaces" diff --git a/CHANGELOG.md b/CHANGELOG.md index a805d31..cd6fc03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026-02-05 + +- v26.2.0 +- segmentation 모델의 마스크 dtype이 uint8로 변경된 것에 대응 + ## 2025-03-10 - v25.3.0 @@ -121,7 +126,6 @@ - YOLO World 모델 추가: 가장 큰 yolov8x-world.pt 모델만 기본적으로 선택할 수 있게 함. - lllyasviel/stable-diffusion-webui-forge에서 컨트롤넷을 사용가능하게 함 (PR #517) - 기본 스크립트 목록에 soft_inpainting 추가 (https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14208) - - 기존에 설치한 사람에게 소급적용되지는 않음 - 감지모델에 대한 간단한 pytest 추가함 @@ -348,7 +352,6 @@ - `ad_inpaint_full_res` → `ad_inpaint_only_masked` - `ad_inpaint_full_res_padding` → `ad_inpaint_only_masked_padding` - mediapipe face mesh 모델 추가 - - mediapipe 최소 버전 `0.10.0` - rich traceback 제거함 diff --git a/aaaaaa/traceback.py b/aaaaaa/traceback.py index 85631e3..4119a9b 100644 --- a/aaaaaa/traceback.py +++ b/aaaaaa/traceback.py @@ -1,4 +1,4 @@ -from __future__ import annotations # noqa: A005 +from __future__ import annotations import io import platform diff --git a/adetailer/__version__.py b/adetailer/__version__.py index d024107..744258c 100644 --- a/adetailer/__version__.py +++ b/adetailer/__version__.py @@ -1 +1 @@ -__version__ = "25.3.0" +__version__ = "26.2.0" diff --git a/adetailer/ultralytics.py b/adetailer/ultralytics.py index 7c7a1a7..1bd13ff 100644 --- a/adetailer/ultralytics.py +++ b/adetailer/ultralytics.py @@ -61,11 +61,12 @@ def mask_to_pil(masks: torch.Tensor, shape: tuple[int, int]) -> list[Image.Image """ Parameters ---------- - masks: torch.Tensor, dtype=torch.float32, shape=(N, H, W). - The device can be CUDA, but `to_pil_image` takes care of that. + masks: torch.Tensor, dtype=torch.float32 or torch.uint8, shape=(N, H, W). + uint8 tensor is expected to have values 0 or 1 (not 0-255). shape: tuple[int, int] (W, H) of the original image """ + masks = masks.float() n = masks.shape[0] return [to_pil_image(masks[i], mode="L").resize(shape) for i in range(n)] diff --git a/install.py b/install.py index d9c7a8a..2b5c531 100644 --- a/install.py +++ b/install.py @@ -47,7 +47,7 @@ def install(): deps = [ # requirements ("ultralytics", "8.3.75", None), - ("mediapipe", "0.10.13", "0.10.15"), + ("mediapipe", "0.10.13", None), ("rich", "13.0.0", None), ] diff --git a/pyproject.toml b/pyproject.toml index f1be122..87fb281 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,26 +1,26 @@ [project] name = "adetailer" description = "An object detection and auto-mask extension for stable diffusion webui." -authors = [{ name = "dowon", email = "ks2515@naver.com" }] -requires-python = ">=3.9" readme = "README.md" +requires-python = ">=3.9" license = { text = "AGPL-3.0" } -dependencies = [ - "ultralytics>=8.2", - "mediapipe>=0.10.13", - "pydantic<3", - "rich>=13", - "huggingface_hub", -] +authors = [{ name = "dowon", email = "ks2515@naver.com" }] keywords = [ - "stable-diffusion", - "stable-diffusion-webui", - "adetailer", - "ultralytics", + "adetailer", + "stable-diffusion", + "stable-diffusion-webui", + "ultralytics", ] classifiers = [ - "License :: OSI Approved :: GNU Affero General Public License v3", - "Topic :: Scientific/Engineering :: Image Recognition", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Topic :: Scientific/Engineering :: Image Recognition", +] +dependencies = [ + "huggingface-hub", + "mediapipe>=0.10.13", + "pydantic<3", + "rich>=13", + "ultralytics>=8.2", ] dynamic = ["version"] @@ -28,7 +28,7 @@ dynamic = ["version"] repository = "https://github.com/Bing-su/adetailer" [project.optional-dependencies] -dev = ["ruff", "pre-commit", "devtools"] +dev = ["devtools"] test = ["pytest", "hypothesis"] [build-system] @@ -48,32 +48,33 @@ extend-exclude = ["modules"] [tool.ruff.lint] select = [ - "A", - "B", - "C4", - "C90", - "E", - "EM", - "F", - "FA", - "I001", - "ISC", - "N", - "PD", - "PERF", - "PL", - "PIE", - "PT", - "PTH", - "RET", - "RUF", - "SIM", - "T20", - "TRY", - "UP", - "W", + "A", + "B", + "C4", + "C90", + "E", + "EM", + "F", + "FA", + "I001", + "ISC", + "N", + "PD", + "PERF", + "PL", + "PLC", + "PIE", + "PT", + "PTH", + "RET", + "RUF", + "SIM", + "T20", + "TRY", + "UP", + "W", ] -ignore = ["B905", "E501", "PLR2004", "PLW0603"] +ignore = ["B905", "E501", "PLC0415", "PLR2004", "PLW0603"] unfixable = ["F401"] [tool.ruff.lint.isort] diff --git a/tests/test_ultralytics.py b/tests/test_ultralytics.py index e75171d..6d62fa0 100644 --- a/tests/test_ultralytics.py +++ b/tests/test_ultralytics.py @@ -1,8 +1,10 @@ +import numpy as np import pytest +import torch from huggingface_hub import hf_hub_download from PIL import Image -from adetailer.ultralytics import ultralytics_predict +from adetailer.ultralytics import mask_to_pil, ultralytics_predict @pytest.mark.parametrize( @@ -60,3 +62,35 @@ def test_yolo_world(sample_image2: Image.Image, klass: str): assert len(result.masks) > 0 assert len(result.confidences) > 0 assert len(result.bboxes) == len(result.masks) == len(result.confidences) + + +class TestMaskToPil: + def test_mask_to_pil_float32(self): + mask = torch.tensor([[[0.0, 1.0], [0.0, 1.0]]], dtype=torch.float32) + imgs = mask_to_pil(mask, shape=(2, 2)) + + assert len(imgs) == 1 + img = imgs[0] + assert isinstance(img, Image.Image) + + arr = np.array(img) + assert arr.shape == (2, 2) + assert arr.dtype == np.uint8 + + expected = np.array([[0, 255], [0, 255]], dtype=np.uint8) + np.testing.assert_array_equal(arr, expected) + + def test_mask_to_pil_uint8(self): + mask = torch.tensor([[[0, 1], [0, 1]]], dtype=torch.uint8) + imgs = mask_to_pil(mask, shape=(2, 2)) + + assert len(imgs) == 1 + img = imgs[0] + assert isinstance(img, Image.Image) + + arr = np.array(img) + assert arr.shape == (2, 2) + assert arr.dtype == np.uint8 + + expected = np.array([[0, 255], [0, 255]], dtype=np.uint8) + np.testing.assert_array_equal(arr, expected)