diff --git a/Doc/ARCHITECTURE.md b/Doc/ARCHITECTURE.md index bca91b2..d7028c9 100644 --- a/Doc/ARCHITECTURE.md +++ b/Doc/ARCHITECTURE.md @@ -14,8 +14,8 @@ be edited independently. | `mask/` | Mask editing, pixel processing, SAM, and matting integrations | | `media/` | Image/blob conversion, in-memory image cache, preview, upload, and export helpers | | `persistence/` | IndexedDB state, reference tracking, and state worker | -| `shared/` | Cross-domain types, configuration, errors, and logging adapters | -| `utils/` | Small compatibility and UI helpers that have not yet earned a domain | +| `shared/` | Cross-domain types and errors | +| `utils/` | Small cross-cutting and UI helpers that have not yet earned a domain | | `log_system/` | Canonical logging implementation | ## Canonical modules diff --git a/Doc/BUILDING.md b/Doc/BUILDING.md index 4add015..612c9d8 100644 --- a/Doc/BUILDING.md +++ b/Doc/BUILDING.md @@ -38,7 +38,7 @@ The frontend is organized by responsibility: - `src/CanvasView.ts` is the stable ComfyUI bootstrap. Its implementation is in `src/app/CanvasView.ts`. - `src/app/LayerForgeConnections.ts` contains graph identity, workflow connections, and ComfyUI event/WebSocket integration used by the canvas. -- `src/canvas/` contains the canvas facade, layers, state, rendering, selection, tools, and interactions. +- `src/canvas/` contains the canvas runtime, layers, state, rendering, selection, tools, and interactions. - `src/canvas/CanvasHistory.ts` contains the bounded undo/redo snapshot model. - `src/io/` contains ComfyUI input and output handling. - `src/mask/` contains mask editing, mask algorithms, and detector/editor integrations. @@ -51,7 +51,7 @@ The frontend is organized by responsibility: - `src/persistence/` contains IndexedDB and image-reference persistence. - `src/persistence/contracts.ts` contains shared persisted-state and worker message contracts. -- `src/shared/` contains shared types, configuration, error handling, and compatibility exports. +- `src/shared/` contains shared types and error handling. - `src/utils/` contains smaller cross-cutting browser and ComfyUI helpers. ComfyUI scans the generated web directory recursively. Only `js/CanvasView.js` invokes `registerLayerForgeExtension`; `js/app/CanvasView.js` exports the registration function without invoking it, preventing duplicate extension registration. diff --git a/js/mask/MaskEditorIntegration.js b/js/mask/MaskEditorIntegration.js index a0ff1a2..eac53f7 100644 --- a/js/mask/MaskEditorIntegration.js +++ b/js/mask/MaskEditorIntegration.js @@ -36,7 +36,7 @@ export class MaskEditorIntegration { }); this.savedMaskState = await this.saveMaskState(); this.maskEditorCancelled = false; - if (!predefinedMask && this.maskTool && this.maskTool.maskCanvas) { + if (!predefinedMask && this.maskTool) { try { log.debug('Creating mask from current mask tool'); predefinedMask = await this.createMaskFromCurrentMask(); @@ -304,10 +304,10 @@ export class MaskEditorIntegration { * @returns {Promise} Promise zwracający obiekt Image z maską */ async createMaskFromCurrentMask() { - if (!this.maskTool || !this.maskTool.maskCanvas) { + if (!this.maskTool) { throw new Error("No mask canvas available"); } - return loadImage(this.maskTool.maskCanvas.toDataURL()); + return loadImage(this.maskTool.getMask().toDataURL()); } waitWhileMaskEditing() { if (mask_editor_showing(app)) { @@ -326,10 +326,10 @@ export class MaskEditorIntegration { * @returns {Object} Zapisany stan maski */ async saveMaskState() { - if (!this.maskTool || !this.maskTool.maskCanvas) { + if (!this.maskTool) { return null; } - const maskCanvas = this.maskTool.maskCanvas; + const maskCanvas = this.maskTool.getMask(); const savedCanvas = cloneCanvas(maskCanvas); return { maskData: savedCanvas, @@ -348,8 +348,12 @@ export class MaskEditorIntegration { return; } if (savedState.maskData) { - const maskCtx = this.maskTool.maskCtx; - maskCtx.clearRect(0, 0, this.maskTool.maskCanvas.width, this.maskTool.maskCanvas.height); + const maskCanvas = this.maskTool.getMask(); + const maskCtx = maskCanvas.getContext('2d', { willReadFrequently: true }); + if (!maskCtx) { + return; + } + maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); maskCtx.drawImage(savedState.maskData, 0, 0); } if (savedState.maskPosition) { diff --git a/js/mask/MaskTool.js b/js/mask/MaskTool.js index aebacb7..7197038 100644 --- a/js/mask/MaskTool.js +++ b/js/mask/MaskTool.js @@ -63,13 +63,6 @@ export class MaskTool { this.pendingPreviewParams = null; this.initMaskCanvas(); } - // Temporary compatibility getters - will be replaced with chunked system - get maskCanvas() { - return this.activeMaskCanvas; - } - get maskCtx() { - return this.activeMaskCtx; - } initPreviewCanvas() { if (this.previewCanvas.parentElement) { this.previewCanvas.parentElement.removeChild(this.previewCanvas); @@ -1386,7 +1379,7 @@ export class MaskTool { } resize(width, height) { this.initPreviewCanvas(); - const oldMask = this.maskCanvas; + const oldMask = this.activeMaskCanvas; const oldX = this.x; const oldY = this.y; const oldWidth = oldMask.width; diff --git a/js/shared/config.js b/js/shared/config.js deleted file mode 100644 index 37e3bd1..0000000 --- a/js/shared/config.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Backward-compatible exports for the LayerForge logging configuration. - * - * The canonical configuration lives in `src/log_system/config.ts`, matching - * the Model Resolver project layout. - */ -export { DEFAULT_LOGGER_NAME, LOG_LEVEL, LOG_MODULE_NAME, USE_COLORS, } from '../log_system/config.js'; diff --git a/python/log_system/__init__.py b/python/log_system/__init__.py deleted file mode 100644 index d2412e6..0000000 --- a/python/log_system/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -@author: Azornes -@title: AzLogs -@version: 2.0.1 -@description: Logging Initializer -""" - -from .log_funcs import create_module_logger -from .logger import LogLevel, logger - -__all__ = [ - "LogLevel", - "create_module_logger", - "logger", -] diff --git a/python/matting/__init__.py b/python/matting/__init__.py deleted file mode 100644 index 9922394..0000000 --- a/python/matting/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Public compatibility facade for LayerForge background-removal support.""" - -import folder_paths -from aiohttp import web - -from .api import ( - check_matting_model, - get_matting_progress, - get_matting_settings, - matting, - register_matting_routes, - save_matting_settings, -) -from .backends.birefnet import ( - _download_birefnet_checkpoint, - _ensure_birefnet_checkpoint, - _find_existing_birefnet_default_checkpoint, - _find_existing_birefnet_remote_checkpoint, - _find_local_birefnet_model, - _get_birefnet_remote_model, - _get_comfy_birefnet_loader, - _is_native_birefnet_checkpoint, - _iter_birefnet_checkpoint_paths, -) -from .backends.rmbg import ( - RMBG2Model, - _download_rmbg_model, - _ensure_rmbg_model, - _find_existing_rmbg_model, - _find_local_rmbg_model, - _get_rmbg_model_loader, - _get_rmbg_model_status_message, - _get_rmbg_transformers_status, - _get_rmbg_remote_model, - _is_rmbg_model_directory, -) -from .catalog import ( - _BIREFNET_DEFAULT_LOCAL_FILENAME, - _BIREFNET_FILENAME, - _BIREFNET_MODEL_CATALOG, - _BIREFNET_PROJECT_URL, - _BIREFNET_REMOTE_PREFIX, - _BIREFNET_REPOSITORY, - _BIREFNET_REQUIRED_KEYS, - _RMBG_MODEL_CATALOG, - _RMBG_REMOTE_PREFIX, -) -from .options import _get_birefnet_model_options -from .paths import ( - _get_birefnet_base_paths, - _get_birefnet_default_checkpoint_path, - _get_birefnet_download_dir, - _get_birefnet_remote_checkpoint_path, - _get_managed_birefnet_paths, - _get_rmbg_model_directory, - _get_rmbg_model_roots, -) -from .service import BiRefNetMatting -from .settings import ( - DEFAULT_SETTINGS, - SETTINGS_FILE, - get_huggingface_token, - get_public_settings, - load_settings, - save_settings, -) - - -__all__ = [ - "BiRefNetMatting", - "RMBG2Model", - "check_matting_model", - "get_matting_progress", - "get_matting_settings", - "matting", - "register_matting_routes", - "save_matting_settings", - "DEFAULT_SETTINGS", - "SETTINGS_FILE", - "get_huggingface_token", - "get_public_settings", - "load_settings", - "save_settings", - "_BIREFNET_DEFAULT_LOCAL_FILENAME", - "_BIREFNET_FILENAME", - "_BIREFNET_MODEL_CATALOG", - "_BIREFNET_PROJECT_URL", - "_BIREFNET_REMOTE_PREFIX", - "_BIREFNET_REPOSITORY", - "_BIREFNET_REQUIRED_KEYS", - "_RMBG_MODEL_CATALOG", - "_RMBG_REMOTE_PREFIX", - "_download_birefnet_checkpoint", - "_download_rmbg_model", - "_ensure_birefnet_checkpoint", - "_ensure_rmbg_model", - "_find_existing_birefnet_default_checkpoint", - "_find_existing_birefnet_remote_checkpoint", - "_find_existing_rmbg_model", - "_find_local_birefnet_model", - "_find_local_rmbg_model", - "_get_birefnet_model_options", - "_get_birefnet_remote_checkpoint_path", - "_get_birefnet_remote_model", - "_get_comfy_birefnet_loader", - "_get_rmbg_model_directory", - "_get_rmbg_model_loader", - "_get_rmbg_model_status_message", - "_get_rmbg_transformers_status", - "_get_rmbg_remote_model", - "_is_native_birefnet_checkpoint", - "_is_rmbg_model_directory", -] diff --git a/python/node.py b/python/node.py index 9e91c2b..8506fe6 100644 --- a/python/node.py +++ b/python/node.py @@ -19,7 +19,7 @@ _OUTPUT_IMAGE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".bmp", ".gif") _MAX_IMAGE_INPUTS = 32 try: - from .log_system import create_module_logger + from .log_system.log_funcs import create_module_logger log = create_module_logger(__name__) log.info("Logger initialized for LayerForge node") diff --git a/python/routes.py b/python/routes.py index 76f5755..9f346bb 100644 --- a/python/routes.py +++ b/python/routes.py @@ -7,7 +7,7 @@ from aiohttp import web from PIL import Image from server import PromptServer -from .matting import register_matting_routes +from .matting.api import register_matting_routes from .image_serialization import file_to_data_url, pil_to_data_url from .node import log diff --git a/src/mask/MaskEditorIntegration.ts b/src/mask/MaskEditorIntegration.ts index 1f2a577..a015a36 100644 --- a/src/mask/MaskEditorIntegration.ts +++ b/src/mask/MaskEditorIntegration.ts @@ -51,7 +51,7 @@ export class MaskEditorIntegration { this.savedMaskState = await this.saveMaskState(); this.maskEditorCancelled = false; - if (!predefinedMask && this.maskTool && this.maskTool.maskCanvas) { + if (!predefinedMask && this.maskTool) { try { log.debug('Creating mask from current mask tool'); predefinedMask = await this.createMaskFromCurrentMask(); @@ -369,11 +369,11 @@ export class MaskEditorIntegration { * @returns {Promise} Promise zwracający obiekt Image z maską */ async createMaskFromCurrentMask() { - if (!this.maskTool || !this.maskTool.maskCanvas) { + if (!this.maskTool) { throw new Error("No mask canvas available"); } - return loadImage(this.maskTool.maskCanvas.toDataURL()); + return loadImage(this.maskTool.getMask().toDataURL()); } waitWhileMaskEditing() { @@ -394,11 +394,11 @@ export class MaskEditorIntegration { * @returns {Object} Zapisany stan maski */ async saveMaskState() { - if (!this.maskTool || !this.maskTool.maskCanvas) { + if (!this.maskTool) { return null; } - const maskCanvas = this.maskTool.maskCanvas; + const maskCanvas = this.maskTool.getMask(); const savedCanvas = cloneCanvas(maskCanvas); return { @@ -420,8 +420,12 @@ export class MaskEditorIntegration { } if (savedState.maskData) { - const maskCtx = this.maskTool.maskCtx; - maskCtx.clearRect(0, 0, this.maskTool.maskCanvas.width, this.maskTool.maskCanvas.height); + const maskCanvas = this.maskTool.getMask(); + const maskCtx = maskCanvas.getContext('2d', {willReadFrequently: true}); + if (!maskCtx) { + return; + } + maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); maskCtx.drawImage(savedState.maskData, 0, 0); } diff --git a/src/mask/MaskTool.ts b/src/mask/MaskTool.ts index bec306c..f5d4a43 100644 --- a/src/mask/MaskTool.ts +++ b/src/mask/MaskTool.ts @@ -152,15 +152,6 @@ export class MaskTool { this.initMaskCanvas(); } - // Temporary compatibility getters - will be replaced with chunked system - get maskCanvas(): HTMLCanvasElement { - return this.activeMaskCanvas; - } - - get maskCtx(): CanvasRenderingContext2D { - return this.activeMaskCtx; - } - initPreviewCanvas(): void { if (this.previewCanvas.parentElement) { this.previewCanvas.parentElement.removeChild(this.previewCanvas); @@ -1736,7 +1727,7 @@ export class MaskTool { resize(width: number, height: number): void { this.initPreviewCanvas(); - const oldMask = this.maskCanvas; + const oldMask = this.activeMaskCanvas; const oldX = this.x; const oldY = this.y; const oldWidth = oldMask.width; diff --git a/src/shared/config.ts b/src/shared/config.ts deleted file mode 100644 index 22a0976..0000000 --- a/src/shared/config.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Backward-compatible exports for the LayerForge logging configuration. - * - * The canonical configuration lives in `src/log_system/config.ts`, matching - * the Model Resolver project layout. - */ -export { - DEFAULT_LOGGER_NAME, - LOG_LEVEL, - LOG_MODULE_NAME, - USE_COLORS, -} from '../log_system/config.js'; diff --git a/src/utils/CommonUtils.ts b/src/utils/CommonUtils.ts index 29866bf..5b97514 100644 --- a/src/utils/CommonUtils.ts +++ b/src/utils/CommonUtils.ts @@ -1,5 +1,4 @@ import type { Layer, Point } from '../shared/types'; -export type { Point } from '../shared/types'; /** * CommonUtils - Wspólne funkcje pomocnicze diff --git a/tests/test_batch_and_mask_refactor_characterization.mjs b/tests/test_batch_and_mask_refactor_characterization.mjs index 149e694..ff90536 100644 --- a/tests/test_batch_and_mask_refactor_characterization.mjs +++ b/tests/test_batch_and_mask_refactor_characterization.mjs @@ -39,12 +39,13 @@ test('mask editor variants share one drawing helper but retain their own side ef assert.match(newEditorBody, /messageBroker\.publish\('saveState'\)/); assert.doesNotMatch(oldEditorBody, /messageBroker\.publish\('saveState'\)/); assert.match(maskEditorSource, /private async renderProcessedMask\(/); + const renderHelperBody = getMethodBody(maskEditorSource, 'renderProcessedMask', 'processMaskForEditor'); assert.equal( - (maskEditorSource.match(/maskCtx\.clearRect\(0, 0, maskCanvas\.width, maskCanvas\.height\)/g) ?? []).length, + (renderHelperBody.match(/maskCtx\.clearRect\(0, 0, maskCanvas\.width, maskCanvas\.height\)/g) ?? []).length, 1 ); assert.equal( - (maskEditorSource.match(/maskCtx\.drawImage\(processedMask, 0, 0\)/g) ?? []).length, + (renderHelperBody.match(/maskCtx\.drawImage\(processedMask, 0, 0\)/g) ?? []).length, 1 ); }); diff --git a/tests/test_entrypoint_integration.py b/tests/test_entrypoint_integration.py index 783d516..ddb7174 100644 --- a/tests/test_entrypoint_integration.py +++ b/tests/test_entrypoint_integration.py @@ -87,7 +87,7 @@ def _install_runtime_stubs(monkeypatch, tmp_path): monkeypatch.setitem(sys.modules, "tqdm", tqdm) # Avoid creating project log files while the entry point is imported. - from python.log_system import logger + from python.log_system.logger import logger monkeypatch.setattr(logger, "configure", lambda config: logger) @@ -112,10 +112,12 @@ def _import_layerforge(monkeypatch, tmp_path): entrypoint=package, node=sys.modules[f"{package_name}.python.node"], image_utils=sys.modules[f"{package_name}.python.image_utils"], - matting=sys.modules[f"{package_name}.python.matting"], matting_api=sys.modules[f"{package_name}.python.matting.api"], matting_birefnet=sys.modules[f"{package_name}.python.matting.backends.birefnet"], matting_rmbg=sys.modules[f"{package_name}.python.matting.backends.rmbg"], + matting_catalog=sys.modules[f"{package_name}.python.matting.catalog"], + matting_options=sys.modules[f"{package_name}.python.matting.options"], + matting_paths=sys.modules[f"{package_name}.python.matting.paths"], matting_settings=sys.modules[f"{package_name}.python.matting.settings"], matting_service=sys.modules[f"{package_name}.python.matting.service"], matting_progress=sys.modules[f"{package_name}.python.matting.progress"], @@ -420,7 +422,6 @@ def test_websocket_and_cached_image_decoding_preserve_image_and_mask_modes(layer def test_matting_adapter_uses_native_loader_and_bhwc_input(layerforge_runtime, monkeypatch): import torch - node_module = layerforge_runtime.matting service_module = layerforge_runtime.matting_service calls = {} @@ -440,10 +441,10 @@ def test_matting_adapter_uses_native_loader_and_bhwc_input(layerforge_runtime, m "_ensure_birefnet_checkpoint", lambda model_path=None: "native-birefnet.safetensors", ) - node_module.BiRefNetMatting._model_cache.clear() + service_module.BiRefNetMatting._model_cache.clear() image = torch.rand((1, 3, 2, 4), dtype=torch.float32) - matted_image, alpha_mask = node_module.BiRefNetMatting().execute( + matted_image, alpha_mask = service_module.BiRefNetMatting().execute( image, model_path=None, threshold=0, @@ -458,7 +459,6 @@ def test_matting_adapter_uses_native_loader_and_bhwc_input(layerforge_runtime, m def test_matting_adapter_translates_comfyui_interrupt(layerforge_runtime, monkeypatch): import torch - node_module = layerforge_runtime.matting service_module = layerforge_runtime.matting_service class FakeInterruptProcessingException(BaseException): @@ -486,10 +486,10 @@ def test_matting_adapter_translates_comfyui_interrupt(layerforge_runtime, monkey "_ensure_birefnet_checkpoint", lambda model_path=None: "interrupting-birefnet.safetensors", ) - node_module.BiRefNetMatting._model_cache.clear() + service_module.BiRefNetMatting._model_cache.clear() with pytest.raises(service_module.MattingInterruptedError, match="interrupted"): - node_module.BiRefNetMatting().execute( + service_module.BiRefNetMatting().execute( torch.ones((1, 3, 2, 4), dtype=torch.float32), model_path=None, threshold=0, @@ -498,12 +498,11 @@ def test_matting_adapter_translates_comfyui_interrupt(layerforge_runtime, monkey def test_matting_model_status_preserves_unsupported_and_error_responses(layerforge_runtime, monkeypatch): - node_module = layerforge_runtime.matting api_module = layerforge_runtime.matting_api - responses = _record_matting_responses(monkeypatch, node_module) + responses = _record_matting_responses(monkeypatch, api_module) monkeypatch.setattr(api_module, "_get_comfy_birefnet_loader", lambda: None) - unsupported = _run_matting_model_check(node_module) + unsupported = _run_matting_model_check(api_module) assert unsupported.payload["available"] is False assert unsupported.payload["reason"] == "unsupported_comfyui" @@ -519,7 +518,7 @@ def test_matting_model_status_preserves_unsupported_and_error_responses(layerfor raise RuntimeError("catalog unavailable") monkeypatch.setattr(api_module, "_get_birefnet_model_options", fail_to_read_options) - failed = _run_matting_model_check(node_module) + failed = _run_matting_model_check(api_module) assert failed.payload == { "available": False, @@ -531,9 +530,8 @@ def test_matting_model_status_preserves_unsupported_and_error_responses(layerfor def test_matting_model_status_preserves_remote_model_responses(layerforge_runtime, monkeypatch): - node_module = layerforge_runtime.matting api_module = layerforge_runtime.matting_api - responses = _record_matting_responses(monkeypatch, node_module) + responses = _record_matting_responses(monkeypatch, api_module) model_options = [{"path": "remote:portrait", "label": "Portrait"}] remote_model = {"label": "Portrait"} @@ -546,7 +544,7 @@ def test_matting_model_status_preserves_remote_model_responses(layerforge_runtim lambda model: "/models/portrait.safetensors", ) - ready = _run_matting_model_check(node_module, "remote:portrait") + ready = _run_matting_model_check(api_module, "remote:portrait") assert ready.payload == { "available": True, @@ -559,7 +557,7 @@ def test_matting_model_status_preserves_remote_model_responses(layerforge_runtim assert ready.status == 200 monkeypatch.setattr(api_module, "_find_existing_birefnet_remote_checkpoint", lambda model: None) - missing = _run_matting_model_check(node_module, "remote:portrait") + missing = _run_matting_model_check(api_module, "remote:portrait") assert missing.payload == { "available": False, @@ -574,9 +572,8 @@ def test_matting_model_status_preserves_remote_model_responses(layerforge_runtim def test_matting_model_status_preserves_local_and_automatic_responses(layerforge_runtime, monkeypatch): - node_module = layerforge_runtime.matting api_module = layerforge_runtime.matting_api - responses = _record_matting_responses(monkeypatch, node_module) + responses = _record_matting_responses(monkeypatch, api_module) model_options = [{"path": "/models/custom.safetensors", "label": "custom.safetensors"}] monkeypatch.setattr(api_module, "_get_comfy_birefnet_loader", lambda: object()) @@ -588,7 +585,7 @@ def test_matting_model_status_preserves_local_and_automatic_responses(layerforge lambda model_path=None: "/models/custom.safetensors", ) - selected = _run_matting_model_check(node_module, "/models/custom.safetensors") + selected = _run_matting_model_check(api_module, "/models/custom.safetensors") assert selected.payload == { "available": True, @@ -601,7 +598,7 @@ def test_matting_model_status_preserves_local_and_automatic_responses(layerforge assert selected.status == 200 monkeypatch.setattr(api_module, "_find_local_birefnet_model", lambda model_path=None: None) - unavailable = _run_matting_model_check(node_module, "/models/missing.safetensors") + unavailable = _run_matting_model_check(api_module, "/models/missing.safetensors") assert unavailable.payload == { "available": False, @@ -617,7 +614,7 @@ def test_matting_model_status_preserves_local_and_automatic_responses(layerforge "_find_local_birefnet_model", lambda model_path=None: "/models/automatic.safetensors", ) - automatic = _run_matting_model_check(node_module) + automatic = _run_matting_model_check(api_module) assert automatic.payload == { "available": True, @@ -634,7 +631,7 @@ def test_matting_model_status_preserves_local_and_automatic_responses(layerforge "_get_birefnet_base_paths", lambda: ["/models/background_removal"], ) - not_downloaded = _run_matting_model_check(node_module) + not_downloaded = _run_matting_model_check(api_module) assert not_downloaded.payload == { "available": False, @@ -650,7 +647,6 @@ def test_matting_model_status_preserves_local_and_automatic_responses(layerforge def test_matting_adapter_supports_inverted_and_mask_only_modes(layerforge_runtime, monkeypatch): import torch - node_module = layerforge_runtime.matting service_module = layerforge_runtime.matting_service class NativeBiRefNet: @@ -667,10 +663,10 @@ def test_matting_adapter_supports_inverted_and_mask_only_modes(layerforge_runtim "_ensure_birefnet_checkpoint", lambda model_path=None: "native-birefnet.safetensors", ) - node_module.BiRefNetMatting._model_cache.clear() + service_module.BiRefNetMatting._model_cache.clear() image = torch.ones((1, 3, 2, 4), dtype=torch.float32) - matting = node_module.BiRefNetMatting() + matting = service_module.BiRefNetMatting() removed_foreground, inverted_mask = matting.execute( image, @@ -703,12 +699,13 @@ def test_matting_adapter_supports_inverted_and_mask_only_modes(layerforge_runtim def test_matting_model_options_include_downloadable_official_variants(layerforge_runtime, monkeypatch): - node_module = layerforge_runtime.matting birefnet_module = layerforge_runtime.matting_birefnet + options_module = layerforge_runtime.matting_options + catalog_module = layerforge_runtime.matting_catalog monkeypatch.setattr(birefnet_module, "_iter_birefnet_checkpoint_paths", lambda: iter(())) - options = node_module._get_birefnet_model_options() + options = options_module._get_birefnet_model_options() remote_options = [option for option in options if option["source"] == "remote"] assert remote_options @@ -719,7 +716,7 @@ def test_matting_model_options_include_downloadable_official_variants(layerforge birefnet_options = [option for option in remote_options if option["backend"] == "birefnet"] assert all(option["project_url"] == "https://github.com/ZhengPeng7/BiRefNet" for option in birefnet_options) assert any(option["path"] == "remote:portrait" for option in remote_options) - portrait = next(option for option in node_module._BIREFNET_MODEL_CATALOG if option["id"] == "portrait") + portrait = next(option for option in catalog_module._BIREFNET_MODEL_CATALOG if option["id"] == "portrait") assert portrait["local_filename"] == "BiRefNet-portrait.safetensors" rmbg_option = next(option for option in remote_options if option["path"] == "remote:rmbg_2_0") @@ -729,10 +726,10 @@ def test_matting_model_options_include_downloadable_official_variants(layerforge def test_selected_remote_matting_model_is_sent_to_downloader(layerforge_runtime, monkeypatch): - node_module = layerforge_runtime.matting birefnet_module = layerforge_runtime.matting_birefnet + catalog_module = layerforge_runtime.matting_catalog selected_model = next( - model for model in node_module._BIREFNET_MODEL_CATALOG if model["id"] == "portrait" + model for model in catalog_module._BIREFNET_MODEL_CATALOG if model["id"] == "portrait" ) downloaded = {} @@ -751,20 +748,23 @@ def test_selected_remote_matting_model_is_sent_to_downloader(layerforge_runtime, def test_remote_matting_checkpoint_path_uses_background_removal_root(layerforge_runtime): - node_module = layerforge_runtime.matting - model = next(model for model in node_module._BIREFNET_MODEL_CATALOG if model["id"] == "portrait") + import folder_paths - expected = Path(layerforge_runtime.matting.folder_paths.models_dir) / "background_removal" / model[ + catalog_module = layerforge_runtime.matting_catalog + paths_module = layerforge_runtime.matting_paths + model = next(model for model in catalog_module._BIREFNET_MODEL_CATALOG if model["id"] == "portrait") + + expected = Path(folder_paths.models_dir) / "background_removal" / model[ "local_filename" ] - assert Path(node_module._get_birefnet_remote_checkpoint_path(model)) == expected + assert Path(paths_module._get_birefnet_remote_checkpoint_path(model)) == expected def test_remote_matting_download_uses_background_removal_root(layerforge_runtime, monkeypatch, tmp_path): - node_module = layerforge_runtime.matting birefnet_module = layerforge_runtime.matting_birefnet - model = next(model for model in node_module._BIREFNET_MODEL_CATALOG if model["id"] == "portrait") + catalog_module = layerforge_runtime.matting_catalog + model = next(model for model in catalog_module._BIREFNET_MODEL_CATALOG if model["id"] == "portrait") download = {} def fake_hf_hub_download(**kwargs): @@ -834,16 +834,18 @@ def test_matting_progress_status_is_available_without_websocket_delivery(layerfo def test_rmbg_model_path_uses_background_removal_subdirectory(layerforge_runtime): - node_module = layerforge_runtime.matting - model = node_module._RMBG_MODEL_CATALOG[0] + import folder_paths - expected = Path(layerforge_runtime.matting.folder_paths.models_dir) / "background_removal" / "RMBG-2.0" + catalog_module = layerforge_runtime.matting_catalog + paths_module = layerforge_runtime.matting_paths + model = catalog_module._RMBG_MODEL_CATALOG[0] - assert Path(node_module._get_rmbg_model_directory(model)) == expected + expected = Path(folder_paths.models_dir) / "background_removal" / "RMBG-2.0" + + assert Path(paths_module._get_rmbg_model_directory(model)) == expected def test_rmbg_checkpoint_is_not_selected_as_native_birefnet(layerforge_runtime, monkeypatch, tmp_path): - node_module = layerforge_runtime.matting birefnet_module = layerforge_runtime.matting_birefnet model_dir = Path(tmp_path) / "models" / "background_removal" / "RMBG-2.0" model_dir.mkdir(parents=True) @@ -858,9 +860,9 @@ def test_rmbg_checkpoint_is_not_selected_as_native_birefnet(layerforge_runtime, def test_rmbg_download_uses_background_removal_subdirectory(layerforge_runtime, monkeypatch, tmp_path): - node_module = layerforge_runtime.matting rmbg_module = layerforge_runtime.matting_rmbg - model = node_module._RMBG_MODEL_CATALOG[0] + catalog_module = layerforge_runtime.matting_catalog + model = catalog_module._RMBG_MODEL_CATALOG[0] download = {} def fake_snapshot_download(**kwargs): @@ -882,7 +884,7 @@ def test_rmbg_download_uses_background_removal_subdirectory(layerforge_runtime, monkeypatch.setitem(sys.modules, "huggingface_hub", huggingface_hub) monkeypatch.setattr(rmbg_module, "get_huggingface_token", lambda: "hf-test-token") - result = node_module._download_rmbg_model(model) + result = rmbg_module._download_rmbg_model(model) expected_dir = Path(tmp_path) / "models" / "background_removal" / "RMBG-2.0" assert Path(download["local_dir"]) == expected_dir @@ -890,7 +892,7 @@ def test_rmbg_download_uses_background_removal_subdirectory(layerforge_runtime, assert "*.safetensors" in download["allow_patterns"] assert download["token"] == "hf-test-token" assert Path(result) == expected_dir - assert node_module._is_rmbg_model_directory(result) + assert rmbg_module._is_rmbg_model_directory(result) def test_rmbg_adapter_returns_batched_hw_mask(layerforge_runtime): @@ -900,7 +902,7 @@ def test_rmbg_adapter_returns_batched_hw_mask(layerforge_runtime): def __call__(self, image): return (None, torch.zeros((image.shape[0], 1, 1024, 1024), device=image.device)) - adapter = layerforge_runtime.matting.RMBG2Model( + adapter = layerforge_runtime.matting_rmbg.RMBG2Model( FakeRMBGModel(), torch.device("cpu"), ) @@ -913,14 +915,13 @@ def test_rmbg_adapter_returns_batched_hw_mask(layerforge_runtime): def test_rmbg_model_status_does_not_require_native_birefnet_loader(layerforge_runtime, monkeypatch): - node_module = layerforge_runtime.matting api_module = layerforge_runtime.matting_api - _record_matting_responses(monkeypatch, node_module) + _record_matting_responses(monkeypatch, api_module) monkeypatch.setattr(api_module, "_get_rmbg_model_loader", lambda: object()) monkeypatch.setattr(api_module, "_find_existing_rmbg_model", lambda model: None) - status = _run_matting_model_check(node_module, "remote:rmbg_2_0") + status = _run_matting_model_check(api_module, "remote:rmbg_2_0") assert status.payload["available"] is False assert status.payload["reason"] == "not_downloaded" diff --git a/tests/test_image_loading_characterization.mjs b/tests/test_image_loading_characterization.mjs index e8270a8..977d326 100644 --- a/tests/test_image_loading_characterization.mjs +++ b/tests/test_image_loading_characterization.mjs @@ -121,7 +121,7 @@ test('CanvasState layer source loading has no local Image construction', () => { }); test('Mask editor image loaders preserve their rejection and cleanup policies', () => { - assert.match(maskEditorSource, /return loadImage\(this\.maskTool\.maskCanvas\.toDataURL\(\)\)/); + assert.match(maskEditorSource, /return loadImage\(this\.maskTool\.getMask\(\)\.toDataURL\(\)\)/); assert.match(maskEditorSource, /resultImage = await loadImage\(this\.node\.imgs\[0\]\.src\)/); assert.match(maskEditorSource, /this\.node\.imgs = \[\]/); }); diff --git a/tests/test_point_type_characterization.mjs b/tests/test_point_type_characterization.mjs index d836b63..5e16cb2 100644 --- a/tests/test_point_type_characterization.mjs +++ b/tests/test_point_type_characterization.mjs @@ -11,8 +11,8 @@ const typesSource = await readFile( 'utf8', ); -test('Point has one canonical definition and CommonUtils re-exports it', () => { +test('Point has one canonical definition in shared types', () => { assert.doesNotMatch(commonUtilsSource, /export interface Point/); - assert.match(commonUtilsSource, /export type \{ Point \} from ['"]\.\.\/shared\/types['"];?/); + assert.doesNotMatch(commonUtilsSource, /export type \{ Point \} from ['"]\.\.\/shared\/types['"];?/); assert.match(typesSource, /export interface Point \{\s+x: number;\s+y: number;\s+\}/); });