{"record":{"id":"02a1c867bd1b61a5","repo":"invoke-ai/InvokeAI","slug":"unexpected-mask-shape-tuple-mask-b1hw-01-shape","errorCode":null,"errorMessage":"Unexpected mask shape: {tuple(mask_b1hw_01.shape)} (expected B,H,W or B,1,H,W)","messagePattern":"Unexpected mask shape: (.+?) \\(expected B,H,W or B,1,H,W\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"invokeai/backend/anima/control_net_lllite.py","lineNumber":100,"sourceCode":"    target_h, target_w = target_cond_hw(latent_h, latent_w, patch_spatial)\n    if img.shape[-2] != target_h or img.shape[-1] != target_w:\n        img = F.interpolate(img, size=(target_h, target_w), mode=\"bicubic\", align_corners=False)\n        img = img.clamp(0.0, 1.0)\n    return img * 2.0 - 1.0\n\n\ndef prepare_mask(mask_b1hw_01: torch.Tensor, latent_h: int, latent_w: int, patch_spatial: int = 2) -> torch.Tensor:\n    \"\"\"Mask (B, 1, H, W) or (B, H, W) in [0, 1] -> (1, 1, H_t, W_t) in {0.0, 1.0}.\n\n    1 = inpaint area, 0 = keep. The caller is responsible for the ``*2-1``\n    rescale before concat with RGB (see :func:`build_inpaint_cond_image`).\n    \"\"\"\n    if mask_b1hw_01.ndim == 3:\n        m = mask_b1hw_01.unsqueeze(1)\n    elif mask_b1hw_01.ndim == 4 and mask_b1hw_01.shape[1] == 1:\n        m = mask_b1hw_01\n    else:\n        raise ValueError(f\"Unexpected mask shape: {tuple(mask_b1hw_01.shape)} (expected B,H,W or B,1,H,W)\")\n    m = m[:1].float()\n    target_h, target_w = target_cond_hw(latent_h, latent_w, patch_spatial)\n    if m.shape[-2] != target_h or m.shape[-1] != target_w:\n        m = F.interpolate(m, size=(target_h, target_w), mode=\"nearest\")\n    return (m >= 0.5).float()\n\n\ndef build_inpaint_cond_image(rgb_pm1: torch.Tensor, mask01: torch.Tensor, masked_input: bool) -> torch.Tensor:\n    \"\"\"rgb_pm1: (1, 3, H, W) in [-1, 1], mask01: (1, 1, H, W) in {0, 1}. Returns (1, 4, H, W).\n\n    The mask channel is rescaled to [-1, +1] (matching the RGB range), and if\n    ``masked_input`` is set the RGB is zeroed where ``mask >= 0.5``.\n    \"\"\"\n    if masked_input:\n        keep = (mask01 < 0.5).to(rgb_pm1.dtype)\n        rgb_pm1 = rgb_pm1 * keep\n    mask_pm1 = mask01.to(rgb_pm1.dtype) * 2.0 - 1.0\n    return torch.cat([rgb_pm1, mask_pm1], dim=1)","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/backend/anima/control_net_lllite.py#L82-L118","documentation":"prepare_mask accepts a mask as (B,H,W) or (B,1,H,W) floats in [0,1]; anything else raises ValueError. It binarizes at 0.5 and resizes (nearest) to the latent-derived spatial target, so it must be able to interpret the tensor's layout unambiguously.","triggerScenarios":"Calling prepare_mask (or _build_lllite_cond_image) with an RGB (B,3,H,W) tensor, a (1,H,W)-only-mask confusion where ndim==4 but channel != 1, a (H,W) 2-D tensor, or a (B,4,H,W) RGBA mask.","commonSituations":"Passing the cond image where a mask is expected, forgetting unsqueeze(0) on a single (H,W) mask, or loading a multi-channel mask from an RGBA PNG without selecting one channel.","solutions":["Pass shape (B,H,W) or (B,1,H,W); squeeze extra channels: mask[:, :1] or mask[:, 0].","Add the batch dim for a single mask: mask.unsqueeze(0).","Convert RGB masks to single-channel first (e.g. take luminance or alpha).","Binarize/normalize values to [0,1] so the >=0.5 threshold behaves as intended."],"exampleFix":"# before\nm = prepare_mask(mask_rgb, latent_h, latent_w)  # (B,3,H,W) -> ValueError\n# after\nmask_gray = mask_rgb.mean(dim=1, keepdim=True)  # (B,1,H,W)\nm = prepare_mask(mask_gray, latent_h, latent_w)","handlingStrategy":"validation","validationCode":"import torch\ndef validate_mask(mask: torch.Tensor) -> None:\n    if not isinstance(mask, torch.Tensor):\n        raise TypeError(\"mask must be a torch.Tensor\")\n    ok = mask.ndim == 3 or (mask.ndim == 4 and mask.shape[1] == 1)\n    if not ok:\n        raise ValueError(f\"expected (B,H,W) or (B,1,H,W), got {tuple(mask.shape)}\")\n    if mask.ndim == 3:\n        mask = mask.unsqueeze(1)\n    if mask.min() < 0.0 or mask.max() > 1.0:\n        raise ValueError(\"mask values must be in [0,1]\")","typeGuard":"def is_mask_b1hw(t) -> bool:\n    import torch\n    return isinstance(t, torch.Tensor) and (t.ndim == 3 or (t.ndim == 4 and t.shape[1] == 1))","tryCatchPattern":"try:\n    m = prepare_mask(mask, latent_h, latent_w)\nexcept ValueError as e:\n    raise ValueError(f\"bad mask: {e}; provide (B,H,W) or (B,1,H,W) in [0,1]\") from e","preventionTips":["Never pass the RGB cond image where a mask is expected.","Reduce RGBA masks to one channel before calling.","Add unsqueeze(0) for single masks loaded as (H,W).","Pre-binarize masks if you need a hard edge (function thresholds at 0.5)."],"tags":["pytorch","shape-mismatch","controlnet","validation"],"backgroundTag":"tensor-shape-mismatch","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}