diff --git a/comfy_extras/nodes_upscale_model.py b/comfy_extras/nodes_upscale_model.py index 45841336d..f97d1a116 100644 --- a/comfy_extras/nodes_upscale_model.py +++ b/comfy_extras/nodes_upscale_model.py @@ -72,9 +72,9 @@ class ImageUpscaleWithModel(io.ComfyNode): device = upscale_model.patcher.load_device alpha = None - if image.shape[-1] > upscale_model.input_channels: - alpha = image[..., upscale_model.input_channels:upscale_model.input_channels + 1] - image = image[..., :upscale_model.input_channels] + if image.shape[-1] == 4: + alpha = image[..., 3:4] + image = image[..., :3] memory_required = (512 * 512 * 3) * image.element_size() * max(upscale_model.scale, 1.0) * 384.0 #The 384.0 is an estimate of how much some of these models take, TODO: make it more accurate memory_required += image.nelement() * image.element_size() diff --git a/tests-unit/comfy_extras_test/upscale_model_alpha_test.py b/tests-unit/comfy_extras_test/upscale_model_alpha_test.py index 29d1fef78..13b00886c 100644 --- a/tests-unit/comfy_extras_test/upscale_model_alpha_test.py +++ b/tests-unit/comfy_extras_test/upscale_model_alpha_test.py @@ -1,3 +1,4 @@ +import pytest import torch from comfy.cli_args import args @@ -19,11 +20,21 @@ class StubUpscaleModel: class patcher: load_device = torch.device("cpu") + def __init__(self): + self.seen_channels = None + def __call__(self, image): + self.seen_channels = image.shape[1] assert image.shape[1] == self.input_channels, "model was handed a non-RGB tensor" return torch.nn.functional.interpolate(image, scale_factor=self.scale, mode="nearest") +class GrayscaleUpscaleModel(StubUpscaleModel): + """A model that wants a single channel. It must not be fed a colour channel as alpha.""" + + input_channels = 1 + + def rgba_image(): """16x16 RGBA where the top half is transparent and the bottom half opaque.""" image = torch.zeros(1, 16, 16, 4) @@ -32,14 +43,17 @@ def rgba_image(): return image -def upscale(image, monkeypatch): +def upscale(image, monkeypatch, model=None): monkeypatch.setattr(comfy.model_management, "load_models_gpu", lambda *args, **kwargs: None) - return ImageUpscaleWithModel.execute(StubUpscaleModel(), image).result + model = model or StubUpscaleModel() + return model, ImageUpscaleWithModel.execute(model, image) def test_rgba_input_does_not_reach_the_model_and_alpha_is_upscaled(monkeypatch): - image, mask = upscale(rgba_image(), monkeypatch) + model, out = upscale(rgba_image(), monkeypatch) + image, mask = out.result + assert model.seen_channels == 3 assert image.shape == (1, 32, 32, 3) assert mask.shape == (1, 32, 32) # inverted convention: transparent -> 1, opaque -> 0 @@ -48,15 +62,31 @@ def test_rgba_input_does_not_reach_the_model_and_alpha_is_upscaled(monkeypatch): def test_rgb_input_reports_a_fully_opaque_mask(monkeypatch): - image, mask = upscale(torch.zeros(1, 16, 16, 3), monkeypatch) + model, out = upscale(torch.zeros(1, 16, 16, 3), monkeypatch) + image, mask = out.result + assert model.seen_channels == 3 assert image.shape == (1, 32, 32, 3) assert mask.shape == (1, 32, 32) assert mask.max() == 0.0 +def test_rgb_input_is_untouched_by_a_model_that_wants_fewer_channels(monkeypatch): + """The alpha split keys off the image being RGBA, never off the model's channel count. + + A single channel model must still receive all three colour channels and fail loudly, + rather than having the green channel silently reinterpreted as alpha. + """ + image = torch.zeros(1, 16, 16, 3) + image[..., 1] = 0.7 + + with pytest.raises(AssertionError): + upscale(image, monkeypatch, model=GrayscaleUpscaleModel()) + + def test_mask_round_trips_through_join_image_with_alpha(monkeypatch): - image, mask = upscale(rgba_image(), monkeypatch) + _, out = upscale(rgba_image(), monkeypatch) + image, mask = out.result rgba = JoinImageWithAlpha.execute(image, mask).result[0]