Skip to content

Commit 354d462

Browse files
authored
Merge pull request #24 from StableLlama/recalculate_files
Version 0.4.7
2 parents c0097b9 + 541d7b4 commit 354d462

File tree

4 files changed

+132
-43
lines changed

4 files changed

+132
-43
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "basic_data_handling"
7-
version = "0.4.6"
7+
version = "0.4.7"
88
description = """Basic Python functions for manipulating data that every programmer is used to, lightweight with no additional dependencies.
99
1010
Supported data types:

src/basic_data_handling/control_flow_nodes.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ class ExecutionOrder(ComfyNodeABC):
308308
the execution order of nodes in the workflow. You only need to chain this node
309309
with the other execution order nodes in the desired order and add any
310310
output of the nodes you want to force execution order on.
311+
312+
This node also passes through any input connected to "any node output" as
313+
its second output.
311314
"""
312315
@classmethod
313316
def INPUT_TYPES(cls):
@@ -318,13 +321,16 @@ def INPUT_TYPES(cls):
318321
}
319322
}
320323

321-
RETURN_TYPES = ("E/O",)
324+
RETURN_TYPES = ("E/O", IO.ANY)
325+
RETURN_NAMES = ("E/O", "passthrough")
326+
FUNCTION = "execution_order"
322327
CATEGORY = "Basic/flow control"
323328
DESCRIPTION = cleandoc(__doc__ or "")
324329
FUNCTION = "execute"
325330

326-
def execute(self, **kwargs) -> tuple[Any]:
327-
return (None,)
331+
def execute(self, **kwargs: list[Any]) -> tuple[None, Any]:
332+
any_node_output = kwargs.get('any node output', [])
333+
return (None, any_node_output)
328334

329335

330336
NODE_CLASS_MAPPINGS = {

src/basic_data_handling/path_nodes.py

Lines changed: 115 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ def load_image_helper(path: str):
2828
pass
2929

3030
if not os.path.exists(path):
31-
raise FileNotFoundError(f"Basic data handling: Image file not found: {path}")
31+
return None
3232

3333
# Open and process the image
34-
img = Image.open(path)
35-
img = ImageOps.exif_transpose(img)
36-
37-
return img
34+
try:
35+
img = Image.open(path)
36+
img = ImageOps.exif_transpose(img)
37+
return img
38+
except Exception:
39+
return None
3840

3941

4042
def extract_mask_from_alpha(img):
@@ -675,19 +677,33 @@ def INPUT_TYPES(cls):
675677
},
676678
}
677679

678-
RETURN_TYPES = (IO.STRING,)
679-
RETURN_NAMES = ("text",)
680+
RETURN_TYPES = (IO.STRING, IO.BOOLEAN)
681+
RETURN_NAMES = ("text", "exists")
680682
CATEGORY = "Basic/Path"
681683
DESCRIPTION = cleandoc(__doc__ or "")
682684
FUNCTION = "load_text"
683685

686+
@classmethod
687+
def IS_CHANGED(cls, path):
688+
try:
689+
if os.path.exists(path):
690+
return os.path.getmtime(path)
691+
except Exception:
692+
pass
693+
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
694+
684695
def load_text(self, path: str):
685-
if not os.path.exists(path):
686-
raise FileNotFoundError(f"Basic data handling: String file not found: {path}")
696+
exists = os.path.exists(path)
687697

688-
with open(path, "r", encoding="utf-8") as f:
689-
text = f.read()
690-
return (text,)
698+
if not exists:
699+
return ("", False)
700+
701+
try:
702+
with open(path, "r", encoding="utf-8") as f:
703+
text = f.read()
704+
return (text, True)
705+
except Exception:
706+
return ("", False)
691707

692708

693709
class PathLoadImageRGB(ComfyNodeABC):
@@ -705,26 +721,40 @@ def INPUT_TYPES(cls):
705721
},
706722
}
707723

708-
RETURN_TYPES = (IO.IMAGE,)
709-
RETURN_NAMES = ("image",)
724+
RETURN_TYPES = (IO.IMAGE, IO.BOOLEAN)
725+
RETURN_NAMES = ("image", "exists")
710726
CATEGORY = "Basic/Path"
711727
DESCRIPTION = cleandoc(__doc__ or "")
712728
FUNCTION = "load_image_rgb"
713729

730+
@classmethod
731+
def IS_CHANGED(cls, path):
732+
try:
733+
if os.path.exists(path):
734+
return os.path.getmtime(path)
735+
except Exception:
736+
pass
737+
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
738+
714739
def load_image_rgb(self, path: str):
715740
import numpy as np
716741
import torch
717742

718743
img = load_image_helper(path)
719744

745+
if img is None:
746+
# Create an empty 1x1 image
747+
empty_tensor = torch.zeros((1, 1, 1, 3), dtype=torch.float32)
748+
return (empty_tensor, False)
749+
720750
# Convert to RGB (removing alpha if present)
721751
img_rgb = img.convert("RGB")
722752

723753
# Convert to tensor format expected by ComfyUI
724754
image_tensor = np.array(img_rgb).astype(np.float32) / 255.0
725755
image_tensor = torch.from_numpy(image_tensor)[None,]
726756

727-
return (image_tensor,)
757+
return (image_tensor, True)
728758

729759

730760
class PathLoadImageRGBA(ComfyNodeABC):
@@ -743,18 +773,33 @@ def INPUT_TYPES(cls):
743773
},
744774
}
745775

746-
RETURN_TYPES = (IO.IMAGE, IO.MASK)
747-
RETURN_NAMES = ("image", "mask")
776+
RETURN_TYPES = (IO.IMAGE, IO.MASK, IO.BOOLEAN)
777+
RETURN_NAMES = ("image", "mask", "exists")
748778
CATEGORY = "Basic/Path"
749779
DESCRIPTION = cleandoc(__doc__ or "")
750780
FUNCTION = "load_image_rgba"
751781

782+
@classmethod
783+
def IS_CHANGED(cls, path):
784+
try:
785+
if os.path.exists(path):
786+
return os.path.getmtime(path)
787+
except Exception:
788+
pass
789+
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
790+
752791
def load_image_rgba(self, path: str):
753792
import numpy as np
754793
import torch
755794

756795
img = load_image_helper(path)
757796

797+
if img is None:
798+
# Create empty 1x1 image and mask
799+
empty_image = torch.zeros((1, 1, 1, 3), dtype=torch.float32)
800+
empty_mask = torch.zeros((1, 1, 1), dtype=torch.float32)
801+
return (empty_image, empty_mask, False)
802+
758803
# Convert to RGB for the image
759804
img_rgb = img.convert("RGB")
760805

@@ -765,7 +810,7 @@ def load_image_rgba(self, path: str):
765810
# Extract alpha channel as mask
766811
mask_tensor = extract_mask_from_alpha(img)
767812

768-
return (image_tensor, mask_tensor)
813+
return (image_tensor, mask_tensor, True)
769814

770815

771816
class PathLoadMaskFromAlpha(ComfyNodeABC):
@@ -784,16 +829,33 @@ def INPUT_TYPES(cls):
784829
},
785830
}
786831

787-
RETURN_TYPES = (IO.MASK,)
788-
RETURN_NAMES = ("mask",)
832+
RETURN_TYPES = (IO.MASK, IO.BOOLEAN)
833+
RETURN_NAMES = ("mask", "exists")
789834
CATEGORY = "Basic/Path"
790835
DESCRIPTION = cleandoc(__doc__ or "")
791836
FUNCTION = "load_mask_from_alpha"
792837

838+
@classmethod
839+
def IS_CHANGED(cls, path):
840+
try:
841+
if os.path.exists(path):
842+
return os.path.getmtime(path)
843+
except Exception:
844+
pass
845+
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
846+
793847
def load_mask_from_alpha(self, path: str):
848+
import torch
849+
794850
img = load_image_helper(path)
851+
852+
if img is None:
853+
# Return empty 1x1 mask
854+
empty_mask = torch.zeros((1, 1, 1), dtype=torch.float32)
855+
return (empty_mask, False)
856+
795857
mask_tensor = extract_mask_from_alpha(img)
796-
return (mask_tensor,)
858+
return (mask_tensor, True)
797859

798860

799861
class PathLoadMaskFromGreyscale(ComfyNodeABC):
@@ -815,21 +877,38 @@ def INPUT_TYPES(cls):
815877
},
816878
}
817879

818-
RETURN_TYPES = (IO.MASK,)
819-
RETURN_NAMES = ("mask",)
880+
RETURN_TYPES = (IO.MASK, IO.BOOLEAN)
881+
RETURN_NAMES = ("mask", "exists")
820882
CATEGORY = "Basic/Path"
821883
DESCRIPTION = cleandoc(__doc__ or "")
822884
FUNCTION = "load_mask_from_greyscale"
823885

886+
@classmethod
887+
def IS_CHANGED(cls, path):
888+
try:
889+
if os.path.exists(path):
890+
return os.path.getmtime(path)
891+
except Exception:
892+
pass
893+
return float("NaN") # Return NaN if file doesn't exist or can't access modification time
894+
824895
def load_mask_from_greyscale(self, path: str, invert: bool = False):
896+
import torch
897+
825898
img = load_image_helper(path)
899+
900+
if img is None:
901+
# Return empty 1x1 mask
902+
empty_mask = torch.zeros((1, 1, 1), dtype=torch.float32)
903+
return (empty_mask, False)
904+
826905
mask_tensor = extract_mask_from_greyscale(img)
827906

828907
# Optionally invert the mask (1.0 - mask)
829908
if invert:
830909
mask_tensor = 1.0 - mask_tensor
831910

832-
return (mask_tensor,)
911+
return (mask_tensor, True)
833912

834913

835914
class PathSaveStringFile(ComfyNodeABC):
@@ -852,15 +931,16 @@ def INPUT_TYPES(cls):
852931
}
853932
}
854933

855-
RETURN_TYPES = (IO.BOOLEAN)
856-
RETURN_NAMES = ("success")
934+
RETURN_TYPES = (IO.BOOLEAN,)
935+
RETURN_NAMES = ("success",)
857936
CATEGORY = "Basic/Path"
858937
DESCRIPTION = cleandoc(__doc__ or "")
859938
FUNCTION = "save_text"
860939
OUTPUT_NODE = True
861940

862941
def save_text(self, text: str, path: str, create_dirs: bool = True, encoding: str = "utf-8"):
863942
if not path:
943+
print("Basic data handling: Save failed - no path specified")
864944
return (False,)
865945

866946
try:
@@ -872,6 +952,7 @@ def save_text(self, text: str, path: str, create_dirs: bool = True, encoding: st
872952
with open(path, "w", encoding=encoding) as f:
873953
f.write(text)
874954

955+
print(f"Basic data handling: Successfully saved text to {path}")
875956
return (True,)
876957
except Exception as e:
877958
print(f"Basic data handling: Error saving text file: {e}")
@@ -899,15 +980,16 @@ def INPUT_TYPES(cls):
899980
}
900981
}
901982

902-
RETURN_TYPES = (IO.BOOLEAN)
903-
RETURN_NAMES = ("success")
983+
RETURN_TYPES = (IO.BOOLEAN,)
984+
RETURN_NAMES = ("success",)
904985
CATEGORY = "Basic/Path"
905986
DESCRIPTION = cleandoc(__doc__ or "")
906987
FUNCTION = "save_image"
907988
OUTPUT_NODE = True
908989

909990
def save_image(self, images, path: str, format: str = "png", quality: int = 95, create_dirs: bool = True):
910991
if not path:
992+
print("Basic data handling: Save failed - no path specified")
911993
return (False,)
912994

913995
# If the path doesn't have an extension or it doesn't match the format, add it
@@ -957,6 +1039,7 @@ def save_image(self, images, path: str, format: str = "png", quality: int = 95,
9571039
else:
9581040
pil_img.save(path, format=format.upper())
9591041

1042+
print(f"Basic data handling: Successfully saved image to {path}")
9601043
return (True,)
9611044
except Exception as e:
9621045
print(f"Basic data handling: Error saving image: {e}")
@@ -987,8 +1070,8 @@ def INPUT_TYPES(cls):
9871070
}
9881071
}
9891072

990-
RETURN_TYPES = (IO.BOOLEAN)
991-
RETURN_NAMES = ("success")
1073+
RETURN_TYPES = (IO.BOOLEAN,)
1074+
RETURN_NAMES = ("success",)
9921075
CATEGORY = "Basic/Path"
9931076
DESCRIPTION = cleandoc(__doc__ or "")
9941077
FUNCTION = "save_image_with_mask"
@@ -998,6 +1081,7 @@ def save_image_with_mask(self, images, mask, path: str, format: str = "png",
9981081
quality: int = 95, invert_mask: bool = False,
9991082
create_dirs: bool = True):
10001083
if not path:
1084+
print("Basic data handling: Save failed - no path specified")
10011085
return (False,)
10021086

10031087
# Check format compatibility - needs to support alpha channel
@@ -1065,6 +1149,7 @@ def save_image_with_mask(self, images, mask, path: str, format: str = "png",
10651149
else:
10661150
pil_img_rgba.save(path, format=format.upper())
10671151

1152+
print(f"Basic data handling: Successfully saved image with mask to {path}")
10681153
return (True,)
10691154
except Exception as e:
10701155
print(f"Basic data handling: Error saving image with mask: {e}")

0 commit comments

Comments
 (0)