mirror of
https://github.com/justUmen/Bjornulf_custom_nodes.git
synced 2026-03-25 22:35:43 -03:00
v1.1.6
This commit is contained in:
276
masks_nodes.py
276
masks_nodes.py
@@ -218,4 +218,280 @@ class BoundingRectangleMask:
|
||||
else:
|
||||
raise ValueError("Mask must be 2D (H, W) or 3D (N, H, W)")
|
||||
|
||||
return (torch.from_numpy(result),)
|
||||
|
||||
class BoundingRectangleMaskBlur:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"up": ("INT", {"default": 0, "min": -10000, "max": 10000}),
|
||||
"down": ("INT", {"default": 0, "min": -10000, "max": 10000}),
|
||||
"right": ("INT", {"default": 0, "min": -10000, "max": 10000}),
|
||||
"left": ("INT", {"default": 0, "min": -10000, "max": 10000}),
|
||||
"blur_up": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}),
|
||||
"blur_down": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}),
|
||||
"blur_left": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}),
|
||||
"blur_right": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}),
|
||||
"tapered_corners": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "process"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def _get_bounding_box(self, mask_np):
|
||||
"""Extract bounding box coordinates from active mask pixels."""
|
||||
active = mask_np > 0.5
|
||||
if not np.any(active):
|
||||
return None
|
||||
|
||||
rows_with_active = np.any(active, axis=1)
|
||||
cols_with_active = np.any(active, axis=0)
|
||||
|
||||
min_row = np.where(rows_with_active)[0][0]
|
||||
max_row = np.where(rows_with_active)[0][-1]
|
||||
min_col = np.where(cols_with_active)[0][0]
|
||||
max_col = np.where(cols_with_active)[0][-1]
|
||||
|
||||
return min_row, max_row, min_col, max_col
|
||||
|
||||
def _expand_bounding_box(self, bbox, up, down, left, right, shape):
|
||||
"""Expand bounding box by specified amounts, clamped to image bounds."""
|
||||
min_row, max_row, min_col, max_col = bbox
|
||||
H, W = shape
|
||||
|
||||
min_row_adj = max(0, min_row - up)
|
||||
max_row_adj = min(H - 1, max_row + down)
|
||||
min_col_adj = max(0, min_col - left)
|
||||
max_col_adj = min(W - 1, max_col + right)
|
||||
|
||||
# Check for invalid bounds
|
||||
if min_row_adj > max_row_adj or min_col_adj > max_col_adj:
|
||||
return None
|
||||
|
||||
return min_row_adj, max_row_adj, min_col_adj, max_col_adj
|
||||
|
||||
def _create_directional_blur_mask(self, mask, direction, blur_amount):
|
||||
"""Create a mask blurred in one specific direction."""
|
||||
if blur_amount <= 0:
|
||||
return np.zeros_like(mask)
|
||||
|
||||
H, W = mask.shape
|
||||
|
||||
# Find the edge of the mask in the specified direction
|
||||
mask_binary = mask > 0.5
|
||||
if not np.any(mask_binary):
|
||||
return np.zeros_like(mask)
|
||||
|
||||
# Create blur based on direction
|
||||
if direction == 'up':
|
||||
# Find top edge
|
||||
top_rows = np.any(mask_binary, axis=1)
|
||||
if not np.any(top_rows):
|
||||
return np.zeros_like(mask)
|
||||
top_edge = np.where(top_rows)[0][0]
|
||||
|
||||
# Create gradient going upward from top edge
|
||||
result = np.zeros_like(mask)
|
||||
for row in range(top_edge + 1):
|
||||
distance = top_edge - row
|
||||
strength = np.exp(-(distance ** 2) / (2 * blur_amount ** 2))
|
||||
result[row, :] = strength * np.any(mask_binary[top_edge:, :], axis=0)
|
||||
|
||||
elif direction == 'down':
|
||||
# Find bottom edge
|
||||
top_rows = np.any(mask_binary, axis=1)
|
||||
if not np.any(top_rows):
|
||||
return np.zeros_like(mask)
|
||||
bottom_edge = np.where(top_rows)[0][-1]
|
||||
|
||||
# Create gradient going downward from bottom edge
|
||||
result = np.zeros_like(mask)
|
||||
for row in range(bottom_edge, H):
|
||||
distance = row - bottom_edge
|
||||
strength = np.exp(-(distance ** 2) / (2 * blur_amount ** 2))
|
||||
result[row, :] = strength * np.any(mask_binary[:bottom_edge+1, :], axis=0)
|
||||
|
||||
elif direction == 'left':
|
||||
# Find left edge
|
||||
left_cols = np.any(mask_binary, axis=0)
|
||||
if not np.any(left_cols):
|
||||
return np.zeros_like(mask)
|
||||
left_edge = np.where(left_cols)[0][0]
|
||||
|
||||
# Create gradient going leftward from left edge
|
||||
result = np.zeros_like(mask)
|
||||
for col in range(left_edge + 1):
|
||||
distance = left_edge - col
|
||||
strength = np.exp(-(distance ** 2) / (2 * blur_amount ** 2))
|
||||
result[:, col] = strength * np.any(mask_binary[:, left_edge:], axis=1)
|
||||
|
||||
elif direction == 'right':
|
||||
# Find right edge
|
||||
left_cols = np.any(mask_binary, axis=0)
|
||||
if not np.any(left_cols):
|
||||
return np.zeros_like(mask)
|
||||
right_edge = np.where(left_cols)[0][-1]
|
||||
|
||||
# Create gradient going rightward from right edge
|
||||
result = np.zeros_like(mask)
|
||||
for col in range(right_edge, W):
|
||||
distance = col - right_edge
|
||||
strength = np.exp(-(distance ** 2) / (2 * blur_amount ** 2))
|
||||
result[:, col] = strength * np.any(mask_binary[:, :right_edge+1], axis=1)
|
||||
|
||||
return result
|
||||
|
||||
def _create_corner_blend(self, mask, blur_up, blur_down, blur_left, blur_right):
|
||||
"""Create smooth corner blending for diagonal blur combinations using individual blur values."""
|
||||
H, W = mask.shape
|
||||
result = np.zeros_like(mask)
|
||||
|
||||
# Find mask boundaries
|
||||
mask_binary = mask > 0.5
|
||||
if not np.any(mask_binary):
|
||||
return result
|
||||
|
||||
rows_with_mask = np.any(mask_binary, axis=1)
|
||||
cols_with_mask = np.any(mask_binary, axis=0)
|
||||
|
||||
if not np.any(rows_with_mask) or not np.any(cols_with_mask):
|
||||
return result
|
||||
|
||||
top_edge = np.where(rows_with_mask)[0][0]
|
||||
bottom_edge = np.where(rows_with_mask)[0][-1]
|
||||
left_edge = np.where(cols_with_mask)[0][0]
|
||||
right_edge = np.where(cols_with_mask)[0][-1]
|
||||
|
||||
# Create coordinate grids
|
||||
y_coords, x_coords = np.mgrid[0:H, 0:W]
|
||||
|
||||
# Top-left corner
|
||||
if blur_up > 0 and blur_left > 0:
|
||||
# Calculate separate distances and strengths for each direction
|
||||
dist_from_top = np.maximum(0, top_edge - y_coords)
|
||||
dist_from_left = np.maximum(0, left_edge - x_coords)
|
||||
|
||||
# Calculate strength based on individual blur values
|
||||
strength_top = np.exp(-(dist_from_top**2) / (2 * blur_up**2))
|
||||
strength_left = np.exp(-(dist_from_left**2) / (2 * blur_left**2))
|
||||
|
||||
# Combine strengths multiplicatively for smooth corner transition
|
||||
strength = strength_top * strength_left
|
||||
|
||||
# Only apply in the top-left quadrant
|
||||
corner_mask = (y_coords <= top_edge) & (x_coords <= left_edge)
|
||||
result = np.maximum(result, strength * corner_mask)
|
||||
|
||||
# Top-right corner
|
||||
if blur_up > 0 and blur_right > 0:
|
||||
dist_from_top = np.maximum(0, top_edge - y_coords)
|
||||
dist_from_right = np.maximum(0, x_coords - right_edge)
|
||||
|
||||
strength_top = np.exp(-(dist_from_top**2) / (2 * blur_up**2))
|
||||
strength_right = np.exp(-(dist_from_right**2) / (2 * blur_right**2))
|
||||
strength = strength_top * strength_right
|
||||
|
||||
corner_mask = (y_coords <= top_edge) & (x_coords >= right_edge)
|
||||
result = np.maximum(result, strength * corner_mask)
|
||||
|
||||
# Bottom-left corner
|
||||
if blur_down > 0 and blur_left > 0:
|
||||
dist_from_bottom = np.maximum(0, y_coords - bottom_edge)
|
||||
dist_from_left = np.maximum(0, left_edge - x_coords)
|
||||
|
||||
strength_bottom = np.exp(-(dist_from_bottom**2) / (2 * blur_down**2))
|
||||
strength_left = np.exp(-(dist_from_left**2) / (2 * blur_left**2))
|
||||
strength = strength_bottom * strength_left
|
||||
|
||||
corner_mask = (y_coords >= bottom_edge) & (x_coords <= left_edge)
|
||||
result = np.maximum(result, strength * corner_mask)
|
||||
|
||||
# Bottom-right corner
|
||||
if blur_down > 0 and blur_right > 0:
|
||||
dist_from_bottom = np.maximum(0, y_coords - bottom_edge)
|
||||
dist_from_right = np.maximum(0, x_coords - right_edge)
|
||||
|
||||
strength_bottom = np.exp(-(dist_from_bottom**2) / (2 * blur_down**2))
|
||||
strength_right = np.exp(-(dist_from_right**2) / (2 * blur_right**2))
|
||||
strength = strength_bottom * strength_right
|
||||
|
||||
corner_mask = (y_coords >= bottom_edge) & (x_coords >= right_edge)
|
||||
result = np.maximum(result, strength * corner_mask)
|
||||
|
||||
return result
|
||||
|
||||
def _apply_directional_blur(self, mask, blur_up, blur_down, blur_left, blur_right, tapered_corners):
|
||||
"""Apply independent directional blur with optional smooth corner blending."""
|
||||
result = mask.copy()
|
||||
|
||||
# Create blur masks for each direction
|
||||
blur_masks = []
|
||||
|
||||
if blur_up > 0:
|
||||
blur_masks.append(self._create_directional_blur_mask(mask, 'up', blur_up))
|
||||
|
||||
if blur_down > 0:
|
||||
blur_masks.append(self._create_directional_blur_mask(mask, 'down', blur_down))
|
||||
|
||||
if blur_left > 0:
|
||||
blur_masks.append(self._create_directional_blur_mask(mask, 'left', blur_left))
|
||||
|
||||
if blur_right > 0:
|
||||
blur_masks.append(self._create_directional_blur_mask(mask, 'right', blur_right))
|
||||
|
||||
# Combine all blur masks with the original
|
||||
for blur_mask in blur_masks:
|
||||
result = np.maximum(result, blur_mask)
|
||||
|
||||
# Add smooth corner blending only if tapered_corners is enabled
|
||||
if tapered_corners:
|
||||
corner_blend = self._create_corner_blend(mask, blur_up, blur_down, blur_left, blur_right)
|
||||
result = np.maximum(result, corner_blend)
|
||||
|
||||
return result
|
||||
|
||||
def process_single(self, mask_np, up, down, right, left, blur_up, blur_down, blur_left, blur_right, tapered_corners):
|
||||
"""Process a single mask with bounding box expansion and directional blur."""
|
||||
# Get bounding box of active pixels
|
||||
bbox = self._get_bounding_box(mask_np)
|
||||
if bbox is None:
|
||||
return np.zeros_like(mask_np, dtype=np.float32)
|
||||
|
||||
# Expand bounding box
|
||||
expanded_bbox = self._expand_bounding_box(bbox, up, down, left, right, mask_np.shape)
|
||||
if expanded_bbox is None:
|
||||
return np.zeros_like(mask_np, dtype=np.float32)
|
||||
|
||||
# Create base rectangular mask
|
||||
min_row_adj, max_row_adj, min_col_adj, max_col_adj = expanded_bbox
|
||||
new_mask = np.zeros_like(mask_np, dtype=np.float32)
|
||||
new_mask[min_row_adj:max_row_adj + 1, min_col_adj:max_col_adj + 1] = 1.0
|
||||
|
||||
# Apply directional blur
|
||||
new_mask = self._apply_directional_blur(new_mask, blur_up, blur_down, blur_left, blur_right, tapered_corners)
|
||||
|
||||
return new_mask
|
||||
|
||||
def process(self, mask, up, down, right, left, blur_up, blur_down, blur_left, blur_right, tapered_corners):
|
||||
"""Main processing function supporting both 2D and 3D masks."""
|
||||
mask_np = mask.cpu().numpy()
|
||||
|
||||
if mask_np.ndim == 2:
|
||||
result = self.process_single(mask_np, up, down, right, left, blur_up, blur_down, blur_left, blur_right, tapered_corners)
|
||||
result = result[None, ...]
|
||||
elif mask_np.ndim == 3:
|
||||
results = []
|
||||
for i in range(mask_np.shape[0]):
|
||||
single_result = self.process_single(
|
||||
mask_np[i], up, down, right, left, blur_up, blur_down, blur_left, blur_right, tapered_corners
|
||||
)
|
||||
results.append(single_result)
|
||||
result = np.stack(results, axis=0)
|
||||
else:
|
||||
raise ValueError("Mask must be 2D (H, W) or 3D (N, H, W)")
|
||||
|
||||
return (torch.from_numpy(result),)
|
||||
Reference in New Issue
Block a user