added tests
@@ -0,0 +1,52 @@
|
||||
name: Code Quality (Lint)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master, develop]
|
||||
push:
|
||||
branches: [main, master, develop]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install linting tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff black isort
|
||||
|
||||
- name: Run Ruff (linter)
|
||||
run: |
|
||||
ruff check . --output-format=github
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run Black (formatter check)
|
||||
run: |
|
||||
black --check --diff .
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run isort (import sorting check)
|
||||
run: |
|
||||
isort --check-only --diff .
|
||||
continue-on-error: true
|
||||
|
||||
- name: Lint summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Code Quality Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Ruff linting completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Black formatting check completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ isort import sorting check completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Note**: Linting errors are not blocking CI (continue-on-error: true)" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Publish to Comfy registry
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'PozzettiAndrea' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -0,0 +1,80 @@
|
||||
name: Real Model Integration Tests
|
||||
|
||||
on:
|
||||
# Run on schedule (weekly, every Monday at 3 AM UTC)
|
||||
schedule:
|
||||
- cron: '0 3 * * 1'
|
||||
|
||||
# Allow manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
# Optionally run on specific branches (commented out to save resources)
|
||||
# push:
|
||||
# branches: [main]
|
||||
|
||||
jobs:
|
||||
test-real-models:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60 # Real model tests can take longer
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgl1-mesa-glx libglib2.0-0
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pytest pytest-cov pytest-mock pytest-asyncio
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Create model cache directory
|
||||
run: |
|
||||
mkdir -p ~/.cache/huggingface
|
||||
|
||||
- name: Cache models
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/huggingface
|
||||
key: ${{ runner.os }}-huggingface-models-${{ hashFiles('tests/integration_real/*.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-huggingface-models-
|
||||
|
||||
- name: Run real model tests
|
||||
run: |
|
||||
pytest tests/integration_real/ -v -m real_model --tb=short
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
HF_HUB_DISABLE_TELEMETRY: 1
|
||||
|
||||
- name: Test summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Real Model Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ GroundingDINO SwinT tests completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Florence-2 Base tests completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ OWLv2 Base tests completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ YOLO-World v2 S tests completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Note**: SAM2 tests were skipped as requested" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload test artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-failures
|
||||
path: |
|
||||
pytest-results/
|
||||
*.log
|
||||
@@ -0,0 +1,66 @@
|
||||
name: Unit and Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, develop]
|
||||
pull_request:
|
||||
branches: [main, master, develop]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11"]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pytest pytest-cov pytest-mock pytest-asyncio
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
pytest tests/unit/ -v --cov=nodes --cov-report=xml --cov-report=term-missing
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
|
||||
- name: Run mocked integration tests
|
||||
run: |
|
||||
pytest tests/integration/ -v --cov=nodes --cov-append --cov-report=xml --cov-report=term-missing
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
|
||||
- name: Run workflow validation tests
|
||||
run: |
|
||||
pytest tests/workflows/ -v
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
|
||||
- name: Upload coverage reports
|
||||
uses: codecov/codecov-action@v4
|
||||
if: matrix.python-version == '3.10'
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Test summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Unit tests completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Mocked integration tests completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Workflow validation tests completed" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -4,20 +4,32 @@ Supports GroundingDINO, MM-GroundingDINO, OWLv2, Florence-2, and YOLO-World
|
||||
Now includes SAM2 segmentation nodes
|
||||
"""
|
||||
|
||||
from .grounding_init import init
|
||||
# Only run initialization and imports when loaded by ComfyUI, not during pytest
|
||||
# This prevents relative import errors when pytest collects test modules
|
||||
import sys
|
||||
if 'pytest' not in sys.modules:
|
||||
from .grounding_init import init
|
||||
# Initialize web extensions for compatibility with older ComfyUI versions
|
||||
init()
|
||||
|
||||
# Initialize web extensions for compatibility with older ComfyUI versions
|
||||
init()
|
||||
|
||||
from .nodes import (
|
||||
GroundingModelLoader,
|
||||
GroundingDetector,
|
||||
BboxVisualizer,
|
||||
DownloadAndLoadSAM2Model,
|
||||
Sam2Segmentation,
|
||||
GroundingMaskModelLoader,
|
||||
GroundingMaskDetector,
|
||||
)
|
||||
from .nodes import (
|
||||
GroundingModelLoader,
|
||||
GroundingDetector,
|
||||
BboxVisualizer,
|
||||
DownloadAndLoadSAM2Model,
|
||||
Sam2Segmentation,
|
||||
GroundingMaskModelLoader,
|
||||
GroundingMaskDetector,
|
||||
)
|
||||
else:
|
||||
# During testing, set dummy values
|
||||
GroundingModelLoader = None
|
||||
GroundingDetector = None
|
||||
BboxVisualizer = None
|
||||
DownloadAndLoadSAM2Model = None
|
||||
Sam2Segmentation = None
|
||||
GroundingMaskModelLoader = None
|
||||
GroundingMaskDetector = None
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"GroundingModelLoader": GroundingModelLoader,
|
||||
@@ -35,7 +47,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"BboxVisualizer": "Bounding Box Visualizer",
|
||||
"DownloadAndLoadSAM2Model": "(Down)Load SAM2Model",
|
||||
"Sam2Segmentation": "Sam2 Segmentation",
|
||||
"GroundingMaskModelLoader": "Grounding Mask Loader",
|
||||
"GroundingMaskModelLoader": "Grounding Mask Model Loader",
|
||||
"GroundingMaskDetector": "Grounding Mask Detector",
|
||||
}
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 775 KiB |
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
@@ -1,16 +1,16 @@
|
||||
import yaml
|
||||
from .sam2.modeling.sam2_base import SAM2Base
|
||||
from .sam2.modeling.backbones.image_encoder import ImageEncoder
|
||||
from .sam2.modeling.backbones.hieradet import Hiera
|
||||
from .sam2.modeling.backbones.image_encoder import FpnNeck
|
||||
from .sam2.modeling.position_encoding import PositionEmbeddingSine
|
||||
from .sam2.modeling.memory_attention import MemoryAttention, MemoryAttentionLayer
|
||||
from .sam2.modeling.sam.transformer import RoPEAttention
|
||||
from .sam2.modeling.memory_encoder import MemoryEncoder, MaskDownSampler, Fuser, CXBlock
|
||||
from .modeling.sam2.modeling.sam2_base import SAM2Base
|
||||
from .modeling.sam2.modeling.backbones.image_encoder import ImageEncoder
|
||||
from .modeling.sam2.modeling.backbones.hieradet import Hiera
|
||||
from .modeling.sam2.modeling.backbones.image_encoder import FpnNeck
|
||||
from .modeling.sam2.modeling.position_encoding import PositionEmbeddingSine
|
||||
from .modeling.sam2.modeling.memory_attention import MemoryAttention, MemoryAttentionLayer
|
||||
from .modeling.sam2.modeling.sam.transformer import RoPEAttention
|
||||
from .modeling.sam2.modeling.memory_encoder import MemoryEncoder, MaskDownSampler, Fuser, CXBlock
|
||||
|
||||
from .sam2.sam2_image_predictor import SAM2ImagePredictor
|
||||
from .sam2.sam2_video_predictor import SAM2VideoPredictor
|
||||
from .sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator
|
||||
from .modeling.sam2.sam2_image_predictor import SAM2ImagePredictor
|
||||
from .modeling.sam2.sam2_video_predictor import SAM2VideoPredictor
|
||||
from .modeling.sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator
|
||||
from comfy.utils import load_torch_file
|
||||
|
||||
def load_model(model_path, model_cfg_path, segmentor, dtype, device):
|
||||
|
||||
@@ -74,7 +74,7 @@ def load_sam2(model, segmentor, device, precision, script_directory):
|
||||
version = "2.1" if "2.1" in model else "2.0"
|
||||
|
||||
model_cfg_path = next(
|
||||
(os.path.join(script_directory, "nodes", "sam2", "configs", cfg)
|
||||
(os.path.join(script_directory, "nodes", "sam2", "configs", "sam2_configs", cfg)
|
||||
for key, cfg in model_mapping[version].items() if key in model),
|
||||
None
|
||||
)
|
||||
|
||||
@@ -162,9 +162,20 @@ def segment_sam2(image, sam2_model, keep_model_loaded, coordinates_positive=None
|
||||
else:
|
||||
input_box = final_box[i] if i < len(final_box) else final_box[0]
|
||||
|
||||
# Handle point coordinates based on individual_objects mode
|
||||
if individual_objects and coordinates_positive is not None:
|
||||
point_coords = final_coords[i] if i < len(final_coords) else final_coords[0]
|
||||
point_labels = final_labels[i] if i < len(final_labels) else final_labels[0]
|
||||
elif coordinates_positive is not None:
|
||||
point_coords = final_coords
|
||||
point_labels = final_labels
|
||||
else:
|
||||
point_coords = None
|
||||
point_labels = None
|
||||
|
||||
out_masks, scores, logits = model.predict(
|
||||
point_coords=final_coords if coordinates_positive is not None else None,
|
||||
point_labels=final_labels if coordinates_positive is not None else None,
|
||||
point_coords=point_coords,
|
||||
point_labels=point_labels,
|
||||
box=input_box,
|
||||
multimask_output=True if not individual_objects else False,
|
||||
mask_input=input_mask[i].unsqueeze(0) if mask is not None else None,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
[project]
|
||||
name = "comfyui-grounding"
|
||||
description = "Object grounding / detection and segmentation nodes for ComfyUI with GroundingDINO, OWLv2, Florence-2, YOLO-World, SAM2, and SA2VA support"
|
||||
version = "1.0.0"
|
||||
license = {file = "LICENSE"}
|
||||
requires-python = ">=3.10"
|
||||
|
||||
# Core dependencies for ComfyUI-Grounding
|
||||
dependencies = [
|
||||
# For GroundingDINO, MM-GroundingDINO, and OWLv2 (HuggingFace transformers)
|
||||
"transformers>=4.30.0",
|
||||
"timm>=0.9.0",
|
||||
|
||||
# For YOLO-World
|
||||
"ultralytics>=8.2.0",
|
||||
|
||||
# For model downloads and SAM2
|
||||
"hf_transfer",
|
||||
"huggingface_hub>=0.20.0",
|
||||
|
||||
# For fine-tuning and flash attention
|
||||
"peft",
|
||||
"flash_attn",
|
||||
|
||||
# Configuration and utilities
|
||||
"pyyaml>=6.0",
|
||||
"tqdm>=4.65.0",
|
||||
|
||||
# Core PyTorch (usually already installed with ComfyUI)
|
||||
"torch>=2.0.0",
|
||||
"torchvision",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/PozzettiAndrea/ComfyUI-Grounding"
|
||||
Documentation = "https://github.com/PozzettiAndrea/ComfyUI-Grounding/wiki"
|
||||
"Bug Tracker" = "https://github.com/PozzettiAndrea/ComfyUI-Grounding/issues"
|
||||
|
||||
# Comfy Registry configuration
|
||||
[tool.comfy]
|
||||
PublisherId = "pozzettiandrea"
|
||||
DisplayName = "ComfyUI-Grounding"
|
||||
Icon = ""
|
||||
@@ -0,0 +1,23 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
|
||||
# Prevent pytest from recursing into these directories
|
||||
norecursedirs = .git .github nodes workflows .eggs *.egg-info dist build web
|
||||
|
||||
markers =
|
||||
unit: Unit tests (fast, no model loading)
|
||||
real_model: Tests that download and use real models (slow)
|
||||
|
||||
addopts =
|
||||
-v
|
||||
--strict-markers
|
||||
--tb=short
|
||||
--import-mode=importlib
|
||||
|
||||
# Ignore warnings from dependencies
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning
|
||||
ignore::PendingDeprecationWarning
|
||||
@@ -0,0 +1,12 @@
|
||||
# Testing dependencies
|
||||
pytest>=8.0.0
|
||||
pytest-mock>=3.12.0
|
||||
pytest-asyncio>=0.23.0
|
||||
|
||||
# Code quality tools
|
||||
ruff>=0.2.0
|
||||
black>=24.0.0
|
||||
isort>=5.13.0
|
||||
|
||||
# Pre-commit hooks (optional)
|
||||
pre-commit>=3.6.0
|
||||
@@ -0,0 +1,54 @@
|
||||
# ComfyUI-Grounding Tests
|
||||
|
||||
## Running Tests
|
||||
|
||||
**Unit tests (fast):**
|
||||
```bash
|
||||
pytest tests/unit/ -v
|
||||
```
|
||||
|
||||
**Real model tests (slow, downloads models):**
|
||||
```bash
|
||||
pytest tests/integration_real/ -v -m real_model
|
||||
```
|
||||
|
||||
**Real model tests with GPU:**
|
||||
```bash
|
||||
pytest tests/integration_real/ -v -m real_model --use-gpu
|
||||
```
|
||||
|
||||
## Test List
|
||||
|
||||
### Unit Tests (6 tests)
|
||||
- `test_node_registration.py` - Node class registration
|
||||
- `test_model_registry.py` - Model registry validation
|
||||
- `test_input_validation.py` - Input validation
|
||||
- `test_cache.py` - Model caching
|
||||
- `test_conversions.py` - Format conversions
|
||||
- `test_drawing.py` - Bounding box drawing
|
||||
|
||||
### Real Model Tests (16 tests)
|
||||
|
||||
**Florence-2** (7 tests)
|
||||
- Load with eager attention
|
||||
- Load with SDPA attention
|
||||
- Detection
|
||||
- Model caching
|
||||
- Attention implementations comparison
|
||||
- Mask generation
|
||||
- Different prompts
|
||||
|
||||
**GroundingDINO** (3 tests)
|
||||
- Load SwinT model
|
||||
- Detection
|
||||
- Model caching
|
||||
|
||||
**OWLv2** (3 tests)
|
||||
- Load Base Patch16 model
|
||||
- Detection
|
||||
- Model caching
|
||||
|
||||
**YOLO-World** (3 tests)
|
||||
- Load v8s model
|
||||
- Detection
|
||||
- Model caching
|
||||
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Pytest configuration and fixtures for ComfyUI-Grounding tests
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
import torch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""Add custom command line options"""
|
||||
parser.addoption(
|
||||
"--use-gpu",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Run tests on GPU instead of CPU (much faster for real model tests)"
|
||||
)
|
||||
|
||||
|
||||
# Add the custom node directory to Python path so we can import nodes package
|
||||
custom_nodes_dir = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(custom_nodes_dir))
|
||||
|
||||
# Mock ComfyUI modules at module level BEFORE pytest starts
|
||||
# This prevents import errors when pytest tries to load __init__.py files
|
||||
mock_folder_paths = type("folder_paths", (), {})()
|
||||
mock_folder_paths.models_dir = "/tmp/test_models"
|
||||
mock_folder_paths.get_folder_paths = lambda x: ["/tmp/test_models"]
|
||||
sys.modules["folder_paths"] = mock_folder_paths
|
||||
|
||||
mock_comfy = type("comfy", (), {})()
|
||||
mock_comfy_utils = type("utils", (), {})()
|
||||
mock_comfy_utils.load_torch_file = lambda x: {}
|
||||
mock_comfy_utils.ProgressBar = MagicMock()
|
||||
mock_comfy_utils.common_upscale = MagicMock()
|
||||
mock_comfy.utils = mock_comfy_utils
|
||||
|
||||
mock_comfy_mm = type("model_management", (), {})()
|
||||
|
||||
# Device selection: Check environment variable set by session fixture
|
||||
def _get_test_device():
|
||||
"""Get device for testing - GPU if --use-gpu flag is set, else CPU"""
|
||||
use_gpu = os.environ.get("PYTEST_USE_GPU", "0") == "1"
|
||||
if use_gpu and torch.cuda.is_available():
|
||||
return torch.device("cuda")
|
||||
return torch.device("cpu")
|
||||
|
||||
mock_comfy_mm.get_torch_device = _get_test_device
|
||||
mock_comfy_mm.soft_empty_cache = lambda: None
|
||||
mock_comfy_mm.load_models_gpu = lambda x: None
|
||||
mock_comfy.model_management = mock_comfy_mm
|
||||
|
||||
sys.modules["comfy"] = mock_comfy
|
||||
sys.modules["comfy.utils"] = mock_comfy_utils
|
||||
sys.modules["comfy.model_management"] = mock_comfy_mm
|
||||
|
||||
# Mock grounding_init module that __init__.py tries to import
|
||||
mock_grounding_init = type("grounding_init", (), {})()
|
||||
mock_grounding_init.init = lambda: None
|
||||
sys.modules["grounding_init"] = mock_grounding_init
|
||||
|
||||
|
||||
def pytest_ignore_collect(collection_path, path, config):
|
||||
"""Ignore __init__.py files during collection"""
|
||||
if collection_path.name == "__init__.py":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def setup_test_device(request):
|
||||
"""Configure test device based on --use-gpu flag"""
|
||||
use_gpu = request.config.getoption("--use-gpu")
|
||||
if use_gpu:
|
||||
os.environ["PYTEST_USE_GPU"] = "1"
|
||||
if torch.cuda.is_available():
|
||||
print(f"\n🚀 Using GPU: {torch.cuda.get_device_name(0)}")
|
||||
else:
|
||||
print("\n⚠️ --use-gpu specified but CUDA not available, using CPU")
|
||||
else:
|
||||
os.environ["PYTEST_USE_GPU"] = "0"
|
||||
print("\n💻 Using CPU (use --use-gpu for GPU acceleration)")
|
||||
|
||||
yield
|
||||
|
||||
# Cleanup
|
||||
os.environ.pop("PYTEST_USE_GPU", None)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def setup_mock_comfy():
|
||||
"""Set up mock ComfyUI modules for testing - runs once per session"""
|
||||
# Ensure mocks persist throughout test session
|
||||
return True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_comfy_environment():
|
||||
"""Provide access to mocked ComfyUI environment (already set up at module level)"""
|
||||
return sys.modules["folder_paths"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def small_image():
|
||||
"""Create a small test image tensor for faster tests"""
|
||||
return torch.rand(1, 224, 224, 3)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_model_cache():
|
||||
"""Clear model cache between tests"""
|
||||
from nodes.utils.cache import MODEL_CACHE
|
||||
MODEL_CACHE.clear()
|
||||
yield
|
||||
MODEL_CACHE.clear()
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Configure pytest with custom markers"""
|
||||
config.addinivalue_line(
|
||||
"markers", "unit: Unit tests (fast, no model loading)"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "integration: Integration tests with mocked models"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "real_model: Tests that download and use real models (slow)"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "workflow: Workflow validation tests"
|
||||
)
|
||||
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
Real model integration tests for Florence2
|
||||
These tests download and use actual models - marked as slow
|
||||
"""
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_florence2_base_load_eager(mock_comfy_environment):
|
||||
"""Test loading real Florence-2 Base model with eager attention"""
|
||||
from nodes import GroundingModelLoader
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# Load Florence-2 Base with eager attention
|
||||
model_dict = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
assert model_dict is not None
|
||||
assert "type" in model_dict
|
||||
assert model_dict["type"] == "florence2"
|
||||
assert "model" in model_dict
|
||||
assert "processor" in model_dict
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_florence2_base_load_sdpa(mock_comfy_environment):
|
||||
"""Test loading real Florence-2 Base model with SDPA attention"""
|
||||
from nodes import GroundingModelLoader
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# Load Florence-2 Base with SDPA attention
|
||||
model_dict = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="sdpa"
|
||||
)[0]
|
||||
|
||||
assert model_dict is not None
|
||||
assert "type" in model_dict
|
||||
assert model_dict["type"] == "florence2"
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_florence2_base_detection(mock_comfy_environment, small_image):
|
||||
"""Test real detection with Florence-2 Base model"""
|
||||
from nodes import GroundingModelLoader, GroundingDetector
|
||||
|
||||
# Load model
|
||||
loader = GroundingModelLoader()
|
||||
model_dict = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
# Run detection
|
||||
detector = GroundingDetector()
|
||||
bboxes, annotated_img, labels, masks = detector.detect(
|
||||
model=model_dict,
|
||||
image=small_image,
|
||||
prompt="person",
|
||||
confidence_threshold=0.3,
|
||||
bbox_output_format="dict_with_data",
|
||||
output_masks=False
|
||||
)
|
||||
|
||||
# Verify outputs structure
|
||||
assert bboxes is not None
|
||||
assert annotated_img is not None
|
||||
assert annotated_img.shape == small_image.shape
|
||||
assert isinstance(labels, str)
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_florence2_model_caching(mock_comfy_environment, reset_model_cache):
|
||||
"""Test that Florence-2 models are properly cached"""
|
||||
from nodes import GroundingModelLoader
|
||||
from nodes.utils.cache import MODEL_CACHE
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# First load
|
||||
model1 = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
cache_size_after_first = len(MODEL_CACHE)
|
||||
|
||||
# Second load (should use cache)
|
||||
model2 = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
cache_size_after_second = len(MODEL_CACHE)
|
||||
|
||||
# Cache should have model
|
||||
assert cache_size_after_first > 0
|
||||
# Cache shouldn't grow on second load
|
||||
assert cache_size_after_second == cache_size_after_first
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_florence2_attention_implementations_equivalent(mock_comfy_environment, small_image):
|
||||
"""Test that different attention implementations produce similar results"""
|
||||
from nodes import GroundingModelLoader, GroundingDetector
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
detector = GroundingDetector()
|
||||
|
||||
# Load with eager
|
||||
model_eager = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
bboxes_eager, _, _, _ = detector.detect(
|
||||
model=model_eager,
|
||||
image=small_image,
|
||||
prompt="object",
|
||||
confidence_threshold=0.3,
|
||||
bbox_output_format="dict_with_data",
|
||||
output_masks=False
|
||||
)
|
||||
|
||||
# Load with SDPA
|
||||
model_sdpa = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="sdpa"
|
||||
)[0]
|
||||
|
||||
bboxes_sdpa, _, _, _ = detector.detect(
|
||||
model=model_sdpa,
|
||||
image=small_image,
|
||||
prompt="object",
|
||||
confidence_threshold=0.3,
|
||||
bbox_output_format="dict_with_data",
|
||||
output_masks=False
|
||||
)
|
||||
|
||||
# Both should produce valid outputs (results may vary slightly)
|
||||
assert bboxes_eager is not None
|
||||
assert bboxes_sdpa is not None
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_florence2_mask_generation(mock_comfy_environment, small_image):
|
||||
"""Test Florence-2 mask generation capability (from bounding boxes)"""
|
||||
from nodes import GroundingModelLoader, GroundingDetector
|
||||
|
||||
# Load regular bbox detection model
|
||||
loader = GroundingModelLoader()
|
||||
model_dict = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
# Run detection with output_masks=True
|
||||
detector = GroundingDetector()
|
||||
bboxes, annotated_img, labels, masks = detector.detect(
|
||||
model=model_dict,
|
||||
image=small_image,
|
||||
prompt="object",
|
||||
confidence_threshold=0.3,
|
||||
bbox_output_format="dict_with_data",
|
||||
output_masks=True
|
||||
)
|
||||
|
||||
# Verify outputs structure
|
||||
assert bboxes is not None
|
||||
assert masks is not None
|
||||
assert annotated_img is not None
|
||||
assert isinstance(labels, str)
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_florence2_different_prompts(mock_comfy_environment, small_image):
|
||||
"""Test Florence-2 with different prompt formats"""
|
||||
from nodes import GroundingModelLoader, GroundingDetector
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
model_dict = loader.load_model(
|
||||
model="Florence-2: Base (0.23B params)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
detector = GroundingDetector()
|
||||
|
||||
prompts = [
|
||||
"person",
|
||||
"person and dog",
|
||||
"Locate person",
|
||||
"Find the person in the image"
|
||||
]
|
||||
|
||||
for prompt in prompts:
|
||||
bboxes, _, _, _ = detector.detect(
|
||||
model=model_dict,
|
||||
image=small_image,
|
||||
prompt=prompt,
|
||||
confidence_threshold=0.3,
|
||||
bbox_output_format="dict_with_data",
|
||||
output_masks=False
|
||||
)
|
||||
|
||||
# Each prompt should produce valid output
|
||||
assert bboxes is not None
|
||||
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
Real model integration tests for GroundingDINO
|
||||
These tests download and use actual models - marked as slow
|
||||
"""
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_grounding_dino_swint_load(mock_comfy_environment):
|
||||
"""Test loading real GroundingDINO SwinT model"""
|
||||
from nodes import GroundingModelLoader
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# Load smallest GroundingDINO model
|
||||
model_dict = loader.load_model(
|
||||
model="GroundingDINO: SwinT OGC (694MB)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
assert model_dict is not None
|
||||
assert "type" in model_dict
|
||||
assert model_dict["type"] == "grounding_dino"
|
||||
assert "model" in model_dict
|
||||
assert "processor" in model_dict
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_grounding_dino_swint_detection(mock_comfy_environment, small_image):
|
||||
"""Test real detection with GroundingDINO SwinT model"""
|
||||
from nodes import GroundingModelLoader, GroundingDetector
|
||||
|
||||
# Load model
|
||||
loader = GroundingModelLoader()
|
||||
model_dict = loader.load_model(
|
||||
model="GroundingDINO: SwinT OGC (694MB)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
# Run detection
|
||||
detector = GroundingDetector()
|
||||
bboxes, annotated_img, labels, masks = detector.detect(
|
||||
model=model_dict,
|
||||
image=small_image,
|
||||
prompt="person . dog .",
|
||||
confidence_threshold=0.3,
|
||||
bbox_output_format="dict_with_data",
|
||||
output_masks=False
|
||||
)
|
||||
|
||||
# Verify outputs structure (may or may not find objects in random image)
|
||||
assert bboxes is not None
|
||||
assert annotated_img is not None
|
||||
assert annotated_img.shape == small_image.shape
|
||||
assert isinstance(labels, str)
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_grounding_dino_model_caching(mock_comfy_environment, reset_model_cache):
|
||||
"""Test that GroundingDINO models are properly cached"""
|
||||
from nodes import GroundingModelLoader
|
||||
from nodes.utils.cache import MODEL_CACHE
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# First load
|
||||
model1 = loader.load_model(
|
||||
model="GroundingDINO: SwinT OGC (694MB)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
cache_size_after_first = len(MODEL_CACHE)
|
||||
|
||||
# Second load (should use cache)
|
||||
model2 = loader.load_model(
|
||||
model="GroundingDINO: SwinT OGC (694MB)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
cache_size_after_second = len(MODEL_CACHE)
|
||||
|
||||
# Cache should have model
|
||||
assert cache_size_after_first > 0
|
||||
# Cache shouldn't grow on second load
|
||||
assert cache_size_after_second == cache_size_after_first
|
||||
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
Real model integration tests for OWLv2
|
||||
These tests download and use actual models - marked as slow
|
||||
"""
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_owlv2_base_load(mock_comfy_environment):
|
||||
"""Test loading real OWLv2 Base Patch16 model"""
|
||||
from nodes import GroundingModelLoader
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# Load smallest OWLv2 model
|
||||
model_dict = loader.load_model(
|
||||
model="OWLv2: Base Patch16",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
assert model_dict is not None
|
||||
assert "type" in model_dict
|
||||
assert model_dict["type"] == "owlv2"
|
||||
assert "model" in model_dict
|
||||
assert "processor" in model_dict
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_owlv2_base_detection(mock_comfy_environment, small_image):
|
||||
"""Test real detection with OWLv2 Base model"""
|
||||
from nodes import GroundingModelLoader, GroundingDetector
|
||||
|
||||
# Load model
|
||||
loader = GroundingModelLoader()
|
||||
model_dict = loader.load_model(
|
||||
model="OWLv2: Base Patch16",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
# Run detection
|
||||
detector = GroundingDetector()
|
||||
bboxes, annotated_img, labels, masks = detector.detect(
|
||||
model=model_dict,
|
||||
image=small_image,
|
||||
prompt="person",
|
||||
confidence_threshold=0.3,
|
||||
bbox_output_format="dict_with_data",
|
||||
output_masks=False
|
||||
)
|
||||
|
||||
# Verify outputs structure
|
||||
assert bboxes is not None
|
||||
assert annotated_img is not None
|
||||
assert annotated_img.shape == small_image.shape
|
||||
assert isinstance(labels, str)
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_owlv2_model_caching(mock_comfy_environment, reset_model_cache):
|
||||
"""Test that OWLv2 models are properly cached"""
|
||||
from nodes import GroundingModelLoader
|
||||
from nodes.utils.cache import MODEL_CACHE
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# First load
|
||||
model1 = loader.load_model(
|
||||
model="OWLv2: Base Patch16",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
cache_size_after_first = len(MODEL_CACHE)
|
||||
|
||||
# Second load (should use cache)
|
||||
model2 = loader.load_model(
|
||||
model="OWLv2: Base Patch16",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
cache_size_after_second = len(MODEL_CACHE)
|
||||
|
||||
# Cache should have model
|
||||
assert cache_size_after_first > 0
|
||||
# Cache shouldn't grow on second load
|
||||
assert cache_size_after_second == cache_size_after_first
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Real model integration tests for YOLO-World
|
||||
These tests download and use actual models - marked as slow
|
||||
"""
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_yolo_world_v2_s_load(mock_comfy_environment):
|
||||
"""Test loading real YOLO-World v2 S (small) model"""
|
||||
from nodes import GroundingModelLoader
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# Load smallest YOLO-World model
|
||||
model_dict = loader.load_model(
|
||||
model="YOLO-World: v8s (Small)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
assert model_dict is not None
|
||||
assert "type" in model_dict
|
||||
assert model_dict["type"] == "yolo_world"
|
||||
assert "model" in model_dict
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_yolo_world_v2_s_detection(mock_comfy_environment, small_image):
|
||||
"""Test real detection with YOLO-World v2 S model"""
|
||||
from nodes import GroundingModelLoader, GroundingDetector
|
||||
|
||||
# Load model
|
||||
loader = GroundingModelLoader()
|
||||
model_dict = loader.load_model(
|
||||
model="YOLO-World: v8s (Small)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
# Run detection
|
||||
detector = GroundingDetector()
|
||||
bboxes, annotated_img, labels, masks = detector.detect(
|
||||
model=model_dict,
|
||||
image=small_image,
|
||||
prompt="person",
|
||||
confidence_threshold=0.3,
|
||||
bbox_output_format="dict_with_data",
|
||||
output_masks=False
|
||||
)
|
||||
|
||||
# Verify outputs structure
|
||||
assert bboxes is not None
|
||||
assert annotated_img is not None
|
||||
assert annotated_img.shape == small_image.shape
|
||||
assert isinstance(labels, str)
|
||||
|
||||
|
||||
@pytest.mark.real_model
|
||||
def test_yolo_world_model_caching(mock_comfy_environment, reset_model_cache):
|
||||
"""Test that YOLO-World models are properly cached"""
|
||||
from nodes import GroundingModelLoader
|
||||
from nodes.utils.cache import MODEL_CACHE
|
||||
|
||||
loader = GroundingModelLoader()
|
||||
|
||||
# First load
|
||||
model1 = loader.load_model(
|
||||
model="YOLO-World: v8s (Small)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
cache_size_after_first = len(MODEL_CACHE)
|
||||
|
||||
# Second load (should use cache)
|
||||
model2 = loader.load_model(
|
||||
model="YOLO-World: v8s (Small)",
|
||||
florence2_attn="eager"
|
||||
)[0]
|
||||
|
||||
cache_size_after_second = len(MODEL_CACHE)
|
||||
|
||||
# Cache should have model
|
||||
assert cache_size_after_first > 0
|
||||
# Cache shouldn't grow on second load
|
||||
assert cache_size_after_second == cache_size_after_first
|
||||
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Unit tests for model caching in ComfyUI-Grounding
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_model_cache_can_store_and_retrieve(mock_comfy_environment, reset_model_cache):
|
||||
"""Test that MODEL_CACHE can store and retrieve models"""
|
||||
from nodes.utils.cache import MODEL_CACHE
|
||||
|
||||
# Test storing a value
|
||||
test_key = "test_model_key"
|
||||
test_value = {"model": "test", "type": "grounding_dino"}
|
||||
|
||||
MODEL_CACHE[test_key] = test_value
|
||||
|
||||
# Test retrieving the value
|
||||
assert test_key in MODEL_CACHE
|
||||
assert MODEL_CACHE[test_key] == test_value
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Unit tests for conversion utilities in ComfyUI-Grounding
|
||||
"""
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_boxes_to_masks_basic(mock_comfy_environment):
|
||||
"""Test basic boxes_to_masks conversion"""
|
||||
import numpy as np
|
||||
from nodes.utils.conversions import boxes_to_masks
|
||||
|
||||
# Create test boxes as numpy arrays: [[x1, y1, x2, y2]]
|
||||
boxes = [[np.array([10, 10, 50, 50]), np.array([60, 60, 100, 100])]]
|
||||
image_shape = (1, 128, 128, 3)
|
||||
|
||||
masks = boxes_to_masks(boxes, image_shape)
|
||||
|
||||
# Check output shape: (num_boxes, height, width)
|
||||
assert masks.shape[0] == 2, "Should have 2 masks"
|
||||
assert masks.shape[1] == 128, "Mask height should match image height"
|
||||
assert masks.shape[2] == 128, "Mask width should match image width"
|
||||
|
||||
# Check dtype
|
||||
assert masks.dtype == torch.float32 or masks.dtype == torch.bool
|
||||
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Unit tests for input validation in ComfyUI-Grounding nodes
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_grounding_detector_input_types(mock_comfy_environment):
|
||||
"""Test GroundingDetector INPUT_TYPES"""
|
||||
from nodes import GroundingDetector
|
||||
|
||||
input_types = GroundingDetector.INPUT_TYPES()
|
||||
|
||||
# Check required inputs
|
||||
required = input_types["required"]
|
||||
assert "model" in required
|
||||
assert "image" in required
|
||||
assert "prompt" in required
|
||||
assert "confidence_threshold" in required
|
||||
|
||||
# Check optional inputs
|
||||
optional = input_types["optional"]
|
||||
assert "single_box_mode" in optional
|
||||
assert "bbox_output_format" in optional
|
||||
assert "output_masks" in optional
|
||||
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Unit tests for model registry in ComfyUI-Grounding
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_model_registry_count(mock_comfy_environment):
|
||||
"""Test that we have exactly 19 models registered"""
|
||||
from nodes.config.bbox_models import MODEL_REGISTRY
|
||||
|
||||
assert len(MODEL_REGISTRY) == 19, f"Expected 19 models, found {len(MODEL_REGISTRY)}"
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Unit tests for node registration in ComfyUI-Grounding
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_node_class_mappings_exists(mock_comfy_environment):
|
||||
"""Test that NODE_CLASS_MAPPINGS is defined and contains all expected nodes"""
|
||||
from nodes import NODE_CLASS_MAPPINGS
|
||||
|
||||
expected_nodes = [
|
||||
"GroundingModelLoader",
|
||||
"GroundingDetector",
|
||||
"BboxVisualizer",
|
||||
"DownloadAndLoadSAM2Model",
|
||||
"Sam2Segmentation",
|
||||
"GroundingMaskModelLoader",
|
||||
"GroundingMaskDetector"
|
||||
]
|
||||
|
||||
assert NODE_CLASS_MAPPINGS is not None, "NODE_CLASS_MAPPINGS is not defined"
|
||||
|
||||
for node_name in expected_nodes:
|
||||
assert node_name in NODE_CLASS_MAPPINGS, f"{node_name} not found in NODE_CLASS_MAPPINGS"
|
||||
assert NODE_CLASS_MAPPINGS[node_name] is not None, f"{node_name} maps to None"
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"id": "b29971c7-6074-4707-895e-9f2360e11e2d",
|
||||
"id": "605c8e58-85ce-480e-ae26-54cf0f1d8ce7",
|
||||
"revision": 0,
|
||||
"last_node_id": 35,
|
||||
"last_link_id": 48,
|
||||
"last_node_id": 17,
|
||||
"last_link_id": 19,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 18,
|
||||
"id": 3,
|
||||
"type": "GroundingModelLoader",
|
||||
"pos": [
|
||||
761.1567094157901,
|
||||
37.11377863503034
|
||||
-324.99609375,
|
||||
-3.8984375
|
||||
],
|
||||
"size": [
|
||||
272.064453125,
|
||||
106
|
||||
270,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
@@ -24,43 +24,298 @@
|
||||
"name": "model",
|
||||
"type": "GROUNDING_MODEL",
|
||||
"links": [
|
||||
27
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "c9c72642cd7affb18d5aa54f21ee7d3acf7412eb",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "GroundingModelLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Florence-2: Large (0.77B params)",
|
||||
true,
|
||||
"eager"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"type": "LoadImagesFromFolderKJ",
|
||||
"id": 12,
|
||||
"type": "DownloadAndLoadSAM2Model",
|
||||
"pos": [
|
||||
681.3852212888811,
|
||||
257.2916550578389
|
||||
134.1244248881893,
|
||||
-152.4815917189195
|
||||
],
|
||||
"size": [
|
||||
290.5201171875,
|
||||
262
|
||||
270,
|
||||
130
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "sam2_model",
|
||||
"type": "SAM2MODEL",
|
||||
"links": [
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "DownloadAndLoadSAM2Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"sam2.1_hiera_large.safetensors",
|
||||
"single_image",
|
||||
"cuda",
|
||||
"fp16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "JoinImageWithAlpha",
|
||||
"pos": [
|
||||
806.4560194385198,
|
||||
47.17325913006609
|
||||
],
|
||||
"size": [
|
||||
176.86484375,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "alpha",
|
||||
"type": "MASK",
|
||||
"link": 17
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
19
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "JoinImageWithAlpha"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "Sam2Segmentation",
|
||||
"pos": [
|
||||
507.4435292430558,
|
||||
34.683721146105555
|
||||
],
|
||||
"size": [
|
||||
272.087890625,
|
||||
254
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "sam2_model",
|
||||
"type": "SAM2MODEL",
|
||||
"link": 10
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 11
|
||||
},
|
||||
{
|
||||
"name": "coordinates_positive",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "coordinates_negative",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "bboxes",
|
||||
"shape": 7,
|
||||
"type": "BBOX",
|
||||
"link": 12
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
16
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "Sam2Segmentation"
|
||||
},
|
||||
"widgets_values": [
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "GroundingDetector",
|
||||
"pos": [
|
||||
68.70780892432435,
|
||||
37.906621234841865
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
364
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "GROUNDING_MODEL",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "bboxes",
|
||||
"type": "BBOX",
|
||||
"links": [
|
||||
12
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "annotated_image",
|
||||
"type": "IMAGE",
|
||||
"links": []
|
||||
},
|
||||
{
|
||||
"name": "labels",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "masks",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "GroundingDetector"
|
||||
},
|
||||
"widgets_values": [
|
||||
"main object",
|
||||
0.3,
|
||||
false,
|
||||
true,
|
||||
"list_only",
|
||||
false,
|
||||
42,
|
||||
42,
|
||||
42,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
634.4085072101292,
|
||||
-31.37835895949953
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 16
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
17
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "InvertMask"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "LoadImagesFromFolderKJ",
|
||||
"pos": [
|
||||
-319.8692780804565,
|
||||
159.9365003928532
|
||||
],
|
||||
"size": [
|
||||
290.5201171875,
|
||||
262
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
41,
|
||||
43
|
||||
8,
|
||||
11,
|
||||
13
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -81,13 +336,13 @@
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-kjnodes",
|
||||
"ver": "1.1.8",
|
||||
"ver": "1.1.9",
|
||||
"Node name for S&R": "LoadImagesFromFolderKJ"
|
||||
},
|
||||
"widgets_values": [
|
||||
"/workspace/depthy/ComfyUI/custom_nodes/ComfyUI-Grounding/docs/tobatch/",
|
||||
1200,
|
||||
1200,
|
||||
"/workspace/groundingnode/ComfyUI/custom_nodes/ComfyUI-Grounding/assets/tobatch/",
|
||||
1024,
|
||||
1024,
|
||||
"pad",
|
||||
0,
|
||||
0,
|
||||
@@ -95,314 +350,117 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"type": "MaskPreview",
|
||||
"id": 17,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
2238.8322620750596,
|
||||
-304.2753331354774
|
||||
1001.6651677560029,
|
||||
55.417983277011885
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
246
|
||||
330.90445054815405,
|
||||
258
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 45
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 19
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "MaskPreview"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"type": "DownloadAndLoadSAM2Model",
|
||||
"pos": [
|
||||
1361.2896454187016,
|
||||
-358.19085905073246
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
130
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "sam2_model",
|
||||
"type": "SAM2MODEL",
|
||||
"links": [
|
||||
42
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "7bfc6241cba9a88ffc5ec2b837fd30d2af73d95c",
|
||||
"Node name for S&R": "DownloadAndLoadSAM2Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"sam2_hiera_large.safetensors",
|
||||
"single_image",
|
||||
"cuda",
|
||||
"fp16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1717.3721807161592,
|
||||
-103.37955289789586
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 39
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.65",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "GroundingDetector",
|
||||
"pos": [
|
||||
1147.1057310247927,
|
||||
-143.3627616665786
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
268
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "GROUNDING_MODEL",
|
||||
"link": 27
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "bboxes",
|
||||
"type": "BBOX",
|
||||
"links": [
|
||||
48
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "annotated_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
39
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "labels",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "masks",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
47
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "c9c72642cd7affb18d5aa54f21ee7d3acf7412eb",
|
||||
"Node name for S&R": "GroundingDetector"
|
||||
},
|
||||
"widgets_values": [
|
||||
"main object",
|
||||
0.35,
|
||||
0.35,
|
||||
true,
|
||||
"list_only",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"type": "Sam2Segmentation",
|
||||
"pos": [
|
||||
1780.1313139653494,
|
||||
-413.51370234675784
|
||||
],
|
||||
"size": [
|
||||
272.087890625,
|
||||
254
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "sam2_model",
|
||||
"type": "SAM2MODEL",
|
||||
"link": 42
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 43
|
||||
},
|
||||
{
|
||||
"name": "coordinates_positive",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "coordinates_negative",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "bboxes",
|
||||
"shape": 7,
|
||||
"type": "BBOX",
|
||||
"link": 48
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": 47
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
45
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "7bfc6241cba9a88ffc5ec2b837fd30d2af73d95c",
|
||||
"Node name for S&R": "Sam2Segmentation"
|
||||
},
|
||||
"widgets_values": [
|
||||
true,
|
||||
false,
|
||||
2,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
27,
|
||||
18,
|
||||
1,
|
||||
3,
|
||||
0,
|
||||
19,
|
||||
1,
|
||||
0,
|
||||
"GROUNDING_MODEL"
|
||||
],
|
||||
[
|
||||
39,
|
||||
19,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
1,
|
||||
30,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
41,
|
||||
31,
|
||||
0,
|
||||
19,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
42,
|
||||
32,
|
||||
10,
|
||||
12,
|
||||
0,
|
||||
33,
|
||||
11,
|
||||
0,
|
||||
"SAM2MODEL"
|
||||
],
|
||||
[
|
||||
43,
|
||||
31,
|
||||
11,
|
||||
9,
|
||||
0,
|
||||
33,
|
||||
11,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
45,
|
||||
33,
|
||||
12,
|
||||
1,
|
||||
0,
|
||||
34,
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
47,
|
||||
19,
|
||||
3,
|
||||
33,
|
||||
5,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
48,
|
||||
19,
|
||||
0,
|
||||
33,
|
||||
11,
|
||||
4,
|
||||
"BBOX"
|
||||
],
|
||||
[
|
||||
13,
|
||||
9,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
16,
|
||||
11,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
17,
|
||||
15,
|
||||
0,
|
||||
14,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
19,
|
||||
14,
|
||||
0,
|
||||
17,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.9483253215897468,
|
||||
"scale": 0.9278994587027772,
|
||||
"offset": [
|
||||
-802.6149662434965,
|
||||
550.1068245397717
|
||||
508.01216790085954,
|
||||
282.0476732486773
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.28.8"
|
||||
@@ -1,117 +1,84 @@
|
||||
{
|
||||
"id": "b29971c7-6074-4707-895e-9f2360e11e2d",
|
||||
"id": "38945c5c-4e77-4d6c-b916-6a2f0a1b6a89",
|
||||
"revision": 0,
|
||||
"last_node_id": 30,
|
||||
"last_link_id": 40,
|
||||
"last_node_id": 32,
|
||||
"last_link_id": 39,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 29,
|
||||
"type": "LoadImage",
|
||||
"id": 28,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
691.3480657963805,
|
||||
244.71705551823334
|
||||
98.71468314873522,
|
||||
-76.55255233775446
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
314
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
"inputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
38,
|
||||
40
|
||||
]
|
||||
},
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 33
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
"links": [
|
||||
34
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.65",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "MaskPreview",
|
||||
"id": 31,
|
||||
"type": "PreviewAny",
|
||||
"pos": [
|
||||
1252.4602357504743,
|
||||
329.44736978995775
|
||||
110.50039305540561,
|
||||
281.573065740487
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
246.00000000000003
|
||||
76
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"name": "source",
|
||||
"type": "*",
|
||||
"link": 37
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.65",
|
||||
"Node name for S&R": "MaskPreview"
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"id": 29,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1937.3573937178255,
|
||||
228.10370816997101
|
||||
533.6126667407317,
|
||||
103.92480629195937
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
246.00000000000003
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 32
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.65",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1664.5457193908678,
|
||||
-114.32244507189236
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
246
|
||||
279.1381383513526,
|
||||
299.2129823737602
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
@@ -120,106 +87,152 @@
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 39
|
||||
"link": 35
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.65",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "BboxVisualizer",
|
||||
"id": 30,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1608.8053963516986,
|
||||
258.9104840919287
|
||||
532.7368358026316,
|
||||
-228.9107803208185
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
78
|
||||
289.0763407319919,
|
||||
290.26959173790755
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 36
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"type": "LoadImagesFromFolderKJ",
|
||||
"pos": [
|
||||
-309.5514298801789,
|
||||
161.68998793772164
|
||||
],
|
||||
"size": [
|
||||
290.5201171875,
|
||||
262
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
38,
|
||||
39
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "image_path",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-kjnodes",
|
||||
"ver": "1.1.9",
|
||||
"Node name for S&R": "LoadImagesFromFolderKJ"
|
||||
},
|
||||
"widgets_values": [
|
||||
"/workspace/groundingnode/ComfyUI/custom_nodes/ComfyUI-Grounding/assets/tobatch",
|
||||
1024,
|
||||
1024,
|
||||
"pad",
|
||||
0,
|
||||
0,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "JoinImageWithAlpha",
|
||||
"pos": [
|
||||
259.332162129211,
|
||||
-105.19057150244885
|
||||
],
|
||||
"size": [
|
||||
176.86484375,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 40
|
||||
"link": 39
|
||||
},
|
||||
{
|
||||
"name": "bboxes",
|
||||
"type": "BBOX",
|
||||
"link": 31
|
||||
"name": "alpha",
|
||||
"type": "MASK",
|
||||
"link": 34
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "annotated_image",
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
32
|
||||
36
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "c9c72642cd7affb18d5aa54f21ee7d3acf7412eb",
|
||||
"Node name for S&R": "BboxVisualizer"
|
||||
},
|
||||
"widgets_values": [
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "GroundingModelLoader",
|
||||
"pos": [
|
||||
761.1567094157901,
|
||||
37.11377863503034
|
||||
],
|
||||
"size": [
|
||||
272.064453125,
|
||||
106
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "GROUNDING_MODEL",
|
||||
"links": [
|
||||
27
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "c9c72642cd7affb18d5aa54f21ee7d3acf7412eb",
|
||||
"Node name for S&R": "GroundingModelLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"GroundingDINO: SwinB (938MB)",
|
||||
true,
|
||||
"eager"
|
||||
]
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "JoinImageWithAlpha"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "GroundingDetector",
|
||||
"type": "GroundingMaskDetector",
|
||||
"pos": [
|
||||
1147.1057310247927,
|
||||
-143.3627616665786
|
||||
106.9620845129382,
|
||||
-14.471635853967237
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
268
|
||||
248
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
@@ -227,8 +240,8 @@
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "GROUNDING_MODEL",
|
||||
"link": 27
|
||||
"type": "MASK_MODEL",
|
||||
"link": 21
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
@@ -237,28 +250,23 @@
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "bboxes",
|
||||
"type": "BBOX",
|
||||
"links": [
|
||||
31
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "annotated_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
39
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "labels",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "masks",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
33
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "overlaid_mask",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
35
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
37
|
||||
]
|
||||
@@ -266,55 +274,105 @@
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "c9c72642cd7affb18d5aa54f21ee7d3acf7412eb",
|
||||
"Node name for S&R": "GroundingDetector"
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "GroundingMaskDetector"
|
||||
},
|
||||
"widgets_values": [
|
||||
"character",
|
||||
0.1,
|
||||
0.01,
|
||||
true,
|
||||
"dict_with_data",
|
||||
true
|
||||
"segment the main object in the image.",
|
||||
0.3,
|
||||
3,
|
||||
2048,
|
||||
1644266233,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "GroundingMaskModelLoader",
|
||||
"pos": [
|
||||
-299.81672209985305,
|
||||
-40.87788547709978
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MASK_MODEL",
|
||||
"links": [
|
||||
21
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "GroundingMaskModelLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"SA2VA: 1B",
|
||||
"bf16"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
27,
|
||||
21,
|
||||
18,
|
||||
0,
|
||||
19,
|
||||
0,
|
||||
"GROUNDING_MODEL"
|
||||
"MASK_MODEL"
|
||||
],
|
||||
[
|
||||
31,
|
||||
33,
|
||||
19,
|
||||
0,
|
||||
21,
|
||||
1,
|
||||
"BBOX"
|
||||
28,
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
32,
|
||||
21,
|
||||
34,
|
||||
28,
|
||||
0,
|
||||
22,
|
||||
26,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
35,
|
||||
19,
|
||||
1,
|
||||
29,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
36,
|
||||
26,
|
||||
0,
|
||||
30,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
37,
|
||||
19,
|
||||
3,
|
||||
28,
|
||||
2,
|
||||
31,
|
||||
0,
|
||||
"MASK"
|
||||
"*"
|
||||
],
|
||||
[
|
||||
38,
|
||||
29,
|
||||
32,
|
||||
0,
|
||||
19,
|
||||
1,
|
||||
@@ -322,17 +380,9 @@
|
||||
],
|
||||
[
|
||||
39,
|
||||
19,
|
||||
1,
|
||||
30,
|
||||
32,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
40,
|
||||
29,
|
||||
0,
|
||||
21,
|
||||
26,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
@@ -341,13 +391,13 @@
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8390550503631276,
|
||||
"scale": 1.2894499156462411,
|
||||
"offset": [
|
||||
-372.4858306734141,
|
||||
284.24946587737446
|
||||
386.94731260454324,
|
||||
226.3186553038392
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.28.7"
|
||||
"frontendVersion": "1.28.8"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
{
|
||||
"id": "23662dd8-0bfd-4ab0-833e-1775efda6729",
|
||||
"revision": 0,
|
||||
"last_node_id": 17,
|
||||
"last_link_id": 19,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "GroundingModelLoader",
|
||||
"pos": [
|
||||
-324.99609375,
|
||||
-3.8984375
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "GROUNDING_MODEL",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "GroundingModelLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Florence-2: Large (0.77B params)",
|
||||
"eager"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "DownloadAndLoadSAM2Model",
|
||||
"pos": [
|
||||
134.1244248881893,
|
||||
-152.4815917189195
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
130
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "sam2_model",
|
||||
"type": "SAM2MODEL",
|
||||
"links": [
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "DownloadAndLoadSAM2Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"sam2.1_hiera_large.safetensors",
|
||||
"single_image",
|
||||
"cuda",
|
||||
"fp16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "JoinImageWithAlpha",
|
||||
"pos": [
|
||||
806.4560194385198,
|
||||
47.17325913006609
|
||||
],
|
||||
"size": [
|
||||
176.86484375,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "alpha",
|
||||
"type": "MASK",
|
||||
"link": 17
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
19
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "JoinImageWithAlpha"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "Sam2Segmentation",
|
||||
"pos": [
|
||||
507.4435292430558,
|
||||
34.683721146105555
|
||||
],
|
||||
"size": [
|
||||
272.087890625,
|
||||
254
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "sam2_model",
|
||||
"type": "SAM2MODEL",
|
||||
"link": 10
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 11
|
||||
},
|
||||
{
|
||||
"name": "coordinates_positive",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "coordinates_negative",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "bboxes",
|
||||
"shape": 7,
|
||||
"type": "BBOX",
|
||||
"link": 12
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
16
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "Sam2Segmentation"
|
||||
},
|
||||
"widgets_values": [
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "GroundingDetector",
|
||||
"pos": [
|
||||
68.70780892432435,
|
||||
37.906621234841865
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
364
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "GROUNDING_MODEL",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "bboxes",
|
||||
"type": "BBOX",
|
||||
"links": [
|
||||
12
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "annotated_image",
|
||||
"type": "IMAGE",
|
||||
"links": []
|
||||
},
|
||||
{
|
||||
"name": "labels",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "masks",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "PozzettiAndrea/ComfyUI-Grounding",
|
||||
"ver": "a947f4961c39cc150a98738fa83a0494e52f3de0",
|
||||
"Node name for S&R": "GroundingDetector"
|
||||
},
|
||||
"widgets_values": [
|
||||
"main object",
|
||||
0.3,
|
||||
false,
|
||||
true,
|
||||
"list_only",
|
||||
false,
|
||||
2793006679,
|
||||
42,
|
||||
42,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
634.4085072101292,
|
||||
-31.37835895949953
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 16
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
17
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "InvertMask"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "LoadImagesFromFolderKJ",
|
||||
"pos": [
|
||||
-319.8692780804565,
|
||||
159.9365003928532
|
||||
],
|
||||
"size": [
|
||||
290.5201171875,
|
||||
262
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
8,
|
||||
11,
|
||||
13
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "image_path",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-kjnodes",
|
||||
"ver": "1.1.9",
|
||||
"Node name for S&R": "LoadImagesFromFolderKJ"
|
||||
},
|
||||
"widgets_values": [
|
||||
"/workspace/groundingnode/ComfyUI/custom_nodes/ComfyUI-Grounding/assets/tobatch/",
|
||||
1024,
|
||||
1024,
|
||||
"pad",
|
||||
0,
|
||||
0,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1001.6651677560029,
|
||||
55.417983277011885
|
||||
],
|
||||
"size": [
|
||||
330.90445054815405,
|
||||
258
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 19
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.67",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"GROUNDING_MODEL"
|
||||
],
|
||||
[
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
10,
|
||||
12,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"SAM2MODEL"
|
||||
],
|
||||
[
|
||||
11,
|
||||
9,
|
||||
0,
|
||||
11,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
12,
|
||||
1,
|
||||
0,
|
||||
11,
|
||||
4,
|
||||
"BBOX"
|
||||
],
|
||||
[
|
||||
13,
|
||||
9,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
16,
|
||||
11,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
17,
|
||||
15,
|
||||
0,
|
||||
14,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
19,
|
||||
14,
|
||||
0,
|
||||
17,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.9278994587027772,
|
||||
"offset": [
|
||||
508.01216790085954,
|
||||
282.0476732486773
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.28.8"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||