Merge pull request #24 from StableLlama/recalculate_files
Version 0.4.7
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "basic_data_handling"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
description = """Basic Python functions for manipulating data that every programmer is used to, lightweight with no additional dependencies.
|
||||
|
||||
Supported data types:
|
||||
|
||||
@@ -308,6 +308,9 @@ class ExecutionOrder(ComfyNodeABC):
|
||||
the execution order of nodes in the workflow. You only need to chain this node
|
||||
with the other execution order nodes in the desired order and add any
|
||||
output of the nodes you want to force execution order on.
|
||||
|
||||
This node also passes through any input connected to "any node output" as
|
||||
its second output.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -318,13 +321,16 @@ class ExecutionOrder(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("E/O",)
|
||||
RETURN_TYPES = ("E/O", IO.ANY)
|
||||
RETURN_NAMES = ("E/O", "passthrough")
|
||||
FUNCTION = "execution_order"
|
||||
CATEGORY = "Basic/flow control"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "execute"
|
||||
|
||||
def execute(self, **kwargs) -> tuple[Any]:
|
||||
return (None,)
|
||||
def execute(self, **kwargs: list[Any]) -> tuple[None, Any]:
|
||||
any_node_output = kwargs.get('any node output', [])
|
||||
return (None, any_node_output)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
|
||||
@@ -28,13 +28,15 @@ def load_image_helper(path: str):
|
||||
pass
|
||||
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"Basic data handling: Image file not found: {path}")
|
||||
return None
|
||||
|
||||
# Open and process the image
|
||||
img = Image.open(path)
|
||||
img = ImageOps.exif_transpose(img)
|
||||
|
||||
return img
|
||||
try:
|
||||
img = Image.open(path)
|
||||
img = ImageOps.exif_transpose(img)
|
||||
return img
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def extract_mask_from_alpha(img):
|
||||
@@ -675,19 +677,33 @@ class PathLoadStringFile(ComfyNodeABC):
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("text",)
|
||||
RETURN_TYPES = (IO.STRING, IO.BOOLEAN)
|
||||
RETURN_NAMES = ("text", "exists")
|
||||
CATEGORY = "Basic/Path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "load_text"
|
||||
|
||||
def load_text(self, path: str):
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"Basic data handling: String file not found: {path}")
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, path):
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
return os.path.getmtime(path)
|
||||
except Exception:
|
||||
pass
|
||||
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
return (text,)
|
||||
def load_text(self, path: str):
|
||||
exists = os.path.exists(path)
|
||||
|
||||
if not exists:
|
||||
return ("", False)
|
||||
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
return (text, True)
|
||||
except Exception:
|
||||
return ("", False)
|
||||
|
||||
|
||||
class PathLoadImageRGB(ComfyNodeABC):
|
||||
@@ -705,18 +721,32 @@ class PathLoadImageRGB(ComfyNodeABC):
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.IMAGE,)
|
||||
RETURN_NAMES = ("image",)
|
||||
RETURN_TYPES = (IO.IMAGE, IO.BOOLEAN)
|
||||
RETURN_NAMES = ("image", "exists")
|
||||
CATEGORY = "Basic/Path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "load_image_rgb"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, path):
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
return os.path.getmtime(path)
|
||||
except Exception:
|
||||
pass
|
||||
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
|
||||
|
||||
def load_image_rgb(self, path: str):
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
img = load_image_helper(path)
|
||||
|
||||
if img is None:
|
||||
# Create an empty 1x1 image
|
||||
empty_tensor = torch.zeros((1, 1, 1, 3), dtype=torch.float32)
|
||||
return (empty_tensor, False)
|
||||
|
||||
# Convert to RGB (removing alpha if present)
|
||||
img_rgb = img.convert("RGB")
|
||||
|
||||
@@ -724,7 +754,7 @@ class PathLoadImageRGB(ComfyNodeABC):
|
||||
image_tensor = np.array(img_rgb).astype(np.float32) / 255.0
|
||||
image_tensor = torch.from_numpy(image_tensor)[None,]
|
||||
|
||||
return (image_tensor,)
|
||||
return (image_tensor, True)
|
||||
|
||||
|
||||
class PathLoadImageRGBA(ComfyNodeABC):
|
||||
@@ -743,18 +773,33 @@ class PathLoadImageRGBA(ComfyNodeABC):
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.IMAGE, IO.MASK)
|
||||
RETURN_NAMES = ("image", "mask")
|
||||
RETURN_TYPES = (IO.IMAGE, IO.MASK, IO.BOOLEAN)
|
||||
RETURN_NAMES = ("image", "mask", "exists")
|
||||
CATEGORY = "Basic/Path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "load_image_rgba"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, path):
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
return os.path.getmtime(path)
|
||||
except Exception:
|
||||
pass
|
||||
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
|
||||
|
||||
def load_image_rgba(self, path: str):
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
img = load_image_helper(path)
|
||||
|
||||
if img is None:
|
||||
# Create empty 1x1 image and mask
|
||||
empty_image = torch.zeros((1, 1, 1, 3), dtype=torch.float32)
|
||||
empty_mask = torch.zeros((1, 1, 1), dtype=torch.float32)
|
||||
return (empty_image, empty_mask, False)
|
||||
|
||||
# Convert to RGB for the image
|
||||
img_rgb = img.convert("RGB")
|
||||
|
||||
@@ -765,7 +810,7 @@ class PathLoadImageRGBA(ComfyNodeABC):
|
||||
# Extract alpha channel as mask
|
||||
mask_tensor = extract_mask_from_alpha(img)
|
||||
|
||||
return (image_tensor, mask_tensor)
|
||||
return (image_tensor, mask_tensor, True)
|
||||
|
||||
|
||||
class PathLoadMaskFromAlpha(ComfyNodeABC):
|
||||
@@ -784,16 +829,33 @@ class PathLoadMaskFromAlpha(ComfyNodeABC):
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.MASK,)
|
||||
RETURN_NAMES = ("mask",)
|
||||
RETURN_TYPES = (IO.MASK, IO.BOOLEAN)
|
||||
RETURN_NAMES = ("mask", "exists")
|
||||
CATEGORY = "Basic/Path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "load_mask_from_alpha"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, path):
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
return os.path.getmtime(path)
|
||||
except Exception:
|
||||
pass
|
||||
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
|
||||
|
||||
def load_mask_from_alpha(self, path: str):
|
||||
import torch
|
||||
|
||||
img = load_image_helper(path)
|
||||
|
||||
if img is None:
|
||||
# Return empty 1x1 mask
|
||||
empty_mask = torch.zeros((1, 1, 1), dtype=torch.float32)
|
||||
return (empty_mask, False)
|
||||
|
||||
mask_tensor = extract_mask_from_alpha(img)
|
||||
return (mask_tensor,)
|
||||
return (mask_tensor, True)
|
||||
|
||||
|
||||
class PathLoadMaskFromGreyscale(ComfyNodeABC):
|
||||
@@ -815,21 +877,38 @@ class PathLoadMaskFromGreyscale(ComfyNodeABC):
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.MASK,)
|
||||
RETURN_NAMES = ("mask",)
|
||||
RETURN_TYPES = (IO.MASK, IO.BOOLEAN)
|
||||
RETURN_NAMES = ("mask", "exists")
|
||||
CATEGORY = "Basic/Path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "load_mask_from_greyscale"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, path):
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
return os.path.getmtime(path)
|
||||
except Exception:
|
||||
pass
|
||||
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
|
||||
|
||||
def load_mask_from_greyscale(self, path: str, invert: bool = False):
|
||||
import torch
|
||||
|
||||
img = load_image_helper(path)
|
||||
|
||||
if img is None:
|
||||
# Return empty 1x1 mask
|
||||
empty_mask = torch.zeros((1, 1, 1), dtype=torch.float32)
|
||||
return (empty_mask, False)
|
||||
|
||||
mask_tensor = extract_mask_from_greyscale(img)
|
||||
|
||||
# Optionally invert the mask (1.0 - mask)
|
||||
if invert:
|
||||
mask_tensor = 1.0 - mask_tensor
|
||||
|
||||
return (mask_tensor,)
|
||||
return (mask_tensor, True)
|
||||
|
||||
|
||||
class PathSaveStringFile(ComfyNodeABC):
|
||||
@@ -852,8 +931,8 @@ class PathSaveStringFile(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN)
|
||||
RETURN_NAMES = ("success")
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("success",)
|
||||
CATEGORY = "Basic/Path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "save_text"
|
||||
@@ -861,6 +940,7 @@ class PathSaveStringFile(ComfyNodeABC):
|
||||
|
||||
def save_text(self, text: str, path: str, create_dirs: bool = True, encoding: str = "utf-8"):
|
||||
if not path:
|
||||
print("Basic data handling: Save failed - no path specified")
|
||||
return (False,)
|
||||
|
||||
try:
|
||||
@@ -872,6 +952,7 @@ class PathSaveStringFile(ComfyNodeABC):
|
||||
with open(path, "w", encoding=encoding) as f:
|
||||
f.write(text)
|
||||
|
||||
print(f"Basic data handling: Successfully saved text to {path}")
|
||||
return (True,)
|
||||
except Exception as e:
|
||||
print(f"Basic data handling: Error saving text file: {e}")
|
||||
@@ -899,8 +980,8 @@ class PathSaveImageRGB(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN)
|
||||
RETURN_NAMES = ("success")
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("success",)
|
||||
CATEGORY = "Basic/Path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "save_image"
|
||||
@@ -908,6 +989,7 @@ class PathSaveImageRGB(ComfyNodeABC):
|
||||
|
||||
def save_image(self, images, path: str, format: str = "png", quality: int = 95, create_dirs: bool = True):
|
||||
if not path:
|
||||
print("Basic data handling: Save failed - no path specified")
|
||||
return (False,)
|
||||
|
||||
# If the path doesn't have an extension or it doesn't match the format, add it
|
||||
@@ -957,6 +1039,7 @@ class PathSaveImageRGB(ComfyNodeABC):
|
||||
else:
|
||||
pil_img.save(path, format=format.upper())
|
||||
|
||||
print(f"Basic data handling: Successfully saved image to {path}")
|
||||
return (True,)
|
||||
except Exception as e:
|
||||
print(f"Basic data handling: Error saving image: {e}")
|
||||
@@ -987,8 +1070,8 @@ class PathSaveImageRGBA(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN)
|
||||
RETURN_NAMES = ("success")
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("success",)
|
||||
CATEGORY = "Basic/Path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "save_image_with_mask"
|
||||
@@ -998,6 +1081,7 @@ class PathSaveImageRGBA(ComfyNodeABC):
|
||||
quality: int = 95, invert_mask: bool = False,
|
||||
create_dirs: bool = True):
|
||||
if not path:
|
||||
print("Basic data handling: Save failed - no path specified")
|
||||
return (False,)
|
||||
|
||||
# Check format compatibility - needs to support alpha channel
|
||||
@@ -1065,6 +1149,7 @@ class PathSaveImageRGBA(ComfyNodeABC):
|
||||
else:
|
||||
pil_img_rgba.save(path, format=format.upper())
|
||||
|
||||
print(f"Basic data handling: Successfully saved image with mask to {path}")
|
||||
return (True,)
|
||||
except Exception as e:
|
||||
print(f"Basic data handling: Error saving image with mask: {e}")
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_path_load_save_string_file(tmp_path):
|
||||
# Test loading the string back
|
||||
load_node = PathLoadStringFile()
|
||||
loaded_string = load_node.load_text(file_path)
|
||||
assert loaded_string == (test_string,)
|
||||
assert loaded_string == (test_string, True)
|
||||
|
||||
# Test creating directories when saving
|
||||
nested_path = str(tmp_path / "nested" / "dir" / "test.txt")
|
||||
@@ -52,12 +52,10 @@ def test_path_load_save_string_file(tmp_path):
|
||||
utf8_text = "UTF-8 text with special chars: 你好, ñ, é, ö"
|
||||
utf8_path = str(tmp_path / "utf8_test.txt")
|
||||
assert save_node.save_text(utf8_text, utf8_path, encoding="utf-8") == (True,)
|
||||
assert load_node.load_text(utf8_path) == (utf8_text,)
|
||||
assert load_node.load_text(utf8_path) == (utf8_text, True)
|
||||
|
||||
# Test error handling
|
||||
with pytest.raises(FileNotFoundError):
|
||||
load_node.load_text(str(tmp_path / "nonexistent.txt"))
|
||||
|
||||
assert load_node.load_text(str(tmp_path / "nonexistent.txt")) == ("", False)
|
||||
|
||||
|
||||
def test_path_abspath():
|
||||
@@ -304,7 +302,7 @@ def test_path_load_mask_nodes(tmp_path, monkeypatch):
|
||||
|
||||
# Verify the mask shape
|
||||
assert isinstance(alpha_mask, tuple)
|
||||
assert len(alpha_mask) == 1
|
||||
assert len(alpha_mask) == 2
|
||||
assert isinstance(alpha_mask[0], torch.Tensor)
|
||||
assert alpha_mask[0].shape == (1, img_size[1], img_size[0])
|
||||
|
||||
@@ -314,7 +312,7 @@ def test_path_load_mask_nodes(tmp_path, monkeypatch):
|
||||
|
||||
# Verify the mask shape
|
||||
assert isinstance(gray_mask, tuple)
|
||||
assert len(gray_mask) == 1
|
||||
assert len(gray_mask) == 2
|
||||
assert isinstance(gray_mask[0], torch.Tensor)
|
||||
assert gray_mask[0].shape == (1, img_size[1], img_size[0])
|
||||
|
||||
@@ -352,7 +350,7 @@ def test_path_load_save_image_rgba(tmp_path, monkeypatch):
|
||||
|
||||
# Test loading an image with alpha
|
||||
load_node = PathLoadImageRGBA()
|
||||
loaded_img, loaded_mask = load_node.load_image_rgba(img_path)
|
||||
loaded_img, loaded_mask, success = load_node.load_image_rgba(img_path)
|
||||
|
||||
# Verify that the returned objects are tensors with the right shapes
|
||||
assert isinstance(loaded_img, torch.Tensor)
|
||||
@@ -410,7 +408,7 @@ def test_path_load_save_image_rgb(tmp_path, monkeypatch):
|
||||
|
||||
# Verify that the returned object is a tensor with the right shape
|
||||
assert isinstance(loaded_img, tuple)
|
||||
assert len(loaded_img) == 1
|
||||
assert len(loaded_img) == 2
|
||||
assert isinstance(loaded_img[0], torch.Tensor)
|
||||
assert loaded_img[0].shape == (1, img_size[1], img_size[0], 3) # (batch, height, width, channels)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user