quick fixes
This commit is contained in:
@@ -19,7 +19,7 @@ You can also get the node from comfy manager under the name of More math.
|
||||
|
||||
- functions and variables in math expressions
|
||||
- Conversion between INT and FLOAT; AUDIO and IMAGE (red - real - strenght of cosine of frequency; blue - imaginary - strenght of sine of frequency; green - log1p of amplitude - just so it looks good to humans)
|
||||
- Nodes for FLOAT, CONDITIONING, LATENT, IMAGE, MASK, NOISE, AUDIO, VIDEO, MODEL, CLIP, VAE and SIGMAS
|
||||
- Nodes for FLOAT, CONDITIONING, LATENT, IMAGE, MASK, NOISE, AUDIO, VIDEO, MODEL, CLIP, VAE, SIGMAS and GUIDER
|
||||
- Vector Math: Support for List literals `[v1, v2, ...]` and operations between lists/scalars/tensors
|
||||
- Custom functions `funcname(variable,variable,...)->expression;` they can be used in any later defined custom function or in expression. Shadowing inbuilt functions do not work.
|
||||
|
||||
@@ -185,7 +185,14 @@ You can also get the node from comfy manager under the name of More math.
|
||||
- **NOISE**
|
||||
- refer to `IMAGE and LATENT` for most variables
|
||||
- `I` or `input_latent` – latent used as input to generate noise before noise is generated into it
|
||||
- **CONDITIONING and FLOAT**
|
||||
- **GUIDER**
|
||||
- refer to `IMAGE and LATENT` for visual part (positions, sizes, etc.)
|
||||
- `sigma` - current sigma value
|
||||
- `seed` - seed used for noise generation
|
||||
- `steps` - total number of sampling steps
|
||||
- `current_step` - current step index (0 to steps)
|
||||
|
||||
- **CONDITIONING, SIGMAS and FLOAT**
|
||||
- no additional variables
|
||||
- **MODEL, CLIP and VAE**
|
||||
- `L` or `layer` - a position of layer from beginning of object
|
||||
|
||||
@@ -14,6 +14,9 @@ from .helper_functions import (
|
||||
from comfy_api.latest import io
|
||||
import comfy.sampler_helpers
|
||||
import comfy.model_patcher
|
||||
import comfy.utils
|
||||
import comfy.hooks
|
||||
import comfy.samplers
|
||||
|
||||
|
||||
class GuiderMathNode(io.ComfyNode):
|
||||
@@ -30,7 +33,7 @@ class GuiderMathNode(io.ComfyNode):
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="G", template=io.Autogrow.TemplatePrefix(io.Guider.Input("guider"), prefix="G", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.String.Input(id="Guider", default="G0*(1-F0)+G1*F0", tooltip="Expression to apply on input guiders. Aliases: a=G0, b=G1, c=G2, d=G3, w=F0, x=F1, y=F2, z=F3"),
|
||||
io.String.Input(id="Guider", default="G0*(1-F0)+G1*F0", tooltip="Expression to apply on input guiders. Aliases: a=G0, b=G1, c=G2, d=G3, w=F0, x=F1, y=F2, z=F3. Context: steps, current_step"),
|
||||
],
|
||||
outputs=[
|
||||
io.Guider.Output(),
|
||||
@@ -80,7 +83,10 @@ class MathGuider:
|
||||
self.expression = expression
|
||||
self.tree = parse_expr(expression)
|
||||
self.inner_model = None # Will be set during sample
|
||||
|
||||
self.sigmas = None
|
||||
self.current_step = 0
|
||||
self.steps = 0
|
||||
|
||||
@property
|
||||
def model_patcher(self):
|
||||
# Return the model patcher of the first valid guider
|
||||
@@ -89,12 +95,11 @@ class MathGuider:
|
||||
if g is not None and hasattr(g, "model_patcher"):
|
||||
return g.model_patcher
|
||||
# If no guider has it (e.g. all None or bare wrappers), try to return shared inner model's patcher if available?
|
||||
# But usually we need it before inner_model is set.
|
||||
# But usually we need it before inner_model is set.
|
||||
# So we just return None which might fail later if caller doesn't check.
|
||||
return None
|
||||
|
||||
def __call__(self, x, sigma, model_options={}, seed=None):
|
||||
print("__call__")
|
||||
# Collect predictions from all guiders
|
||||
g_results = {}
|
||||
for k, guider in self.G.items():
|
||||
@@ -151,9 +156,10 @@ class MathGuider:
|
||||
"batch_count": eval_samples.shape[0],
|
||||
"N": eval_samples.shape[channel_dim] if channel_dim < ndim else 0,
|
||||
"channel_count": eval_samples.shape[channel_dim] if channel_dim < ndim else 0,
|
||||
"sigma": sigma, # sigma is scalar or tensor? usually tensor broadcastable
|
||||
"test_sigma": sigma,
|
||||
"sigma": sigma.item(), # sigma is scalar or tensor? usually tensor broadcastable
|
||||
"seed": seed if seed is not None else 0,
|
||||
"steps": self.steps,
|
||||
"current_step": self.current_step,
|
||||
}
|
||||
|
||||
# Add dynamic inputs and aliases
|
||||
@@ -173,11 +179,13 @@ class MathGuider:
|
||||
|
||||
visitor = UnifiedMathVisitor(variables, eval_samples.shape)
|
||||
result_tensor = visitor.visit(self.tree)
|
||||
self.current_step = self.current_step + 1;
|
||||
# Result should be noise prediction, matching x shape
|
||||
return as_tensor(result_tensor, eval_samples.shape)
|
||||
return as_tensor(result_tensor, eval_samples.shape).to(x.device)
|
||||
|
||||
def sample(self, noise, latent_image, sampler, sigmas, denoise_mask=None, callback=None, disable_pbar=False, seed=None):
|
||||
print("sample")
|
||||
self.sigmas = sigmas
|
||||
self.steps = len(sigmas)
|
||||
if sigmas.shape[-1] == 0:
|
||||
return latent_image
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class UnifiedMathVisitor(MathExprVisitor):
|
||||
def __init__(self, variables, shape=None, device=None, functions=None):
|
||||
self.variables = variables
|
||||
self.spatial_variables = variables.copy()
|
||||
self.shape = shape if shape is not None else ()
|
||||
self.shape = shape if shape is not None else (1,)
|
||||
if device is None:
|
||||
self.device = next((v.device for v in variables.values() if isinstance(v, torch.Tensor)), torch.device("cpu"))
|
||||
else:
|
||||
@@ -23,12 +23,16 @@ class UnifiedMathVisitor(MathExprVisitor):
|
||||
def _is_list(self, val):
|
||||
return isinstance(val, (list, tuple))
|
||||
|
||||
def _promote_to_tensor(self, val):
|
||||
def _promote_to_tensor(self, val,brodcast=False):
|
||||
if self._is_tensor(val):
|
||||
return val.contiguous()
|
||||
if self._is_list(val):
|
||||
return torch.tensor(val, device=self.device)
|
||||
return torch.broadcast_to(torch.tensor(val, device=self.device), self.shape).contiguous()
|
||||
if brodcast:
|
||||
t = list(self.shape)
|
||||
t[0]=1
|
||||
return torch.full(t,val,device=self.device)
|
||||
return torch.tensor(val, device=self.device)
|
||||
|
||||
def _bin_op(self, a, b, torch_op, scalar_op):
|
||||
"""
|
||||
@@ -476,7 +480,7 @@ class UnifiedMathVisitor(MathExprVisitor):
|
||||
promoted = [self._promote_to_tensor(x) for x in vals]
|
||||
if len(promoted) == 1:
|
||||
return torch.min(promoted[0])
|
||||
return torch.min(torch.stack(torch.broadcast_tensors(*promoted)))
|
||||
return torch.min(torch.stack(torch.broadcast_tensors(*promoted))).item()
|
||||
|
||||
def visitSMaxFunc(self, ctx):
|
||||
args = [self.visit(e) for e in ctx.expr()]
|
||||
@@ -486,7 +490,7 @@ class UnifiedMathVisitor(MathExprVisitor):
|
||||
return args[0]
|
||||
if self._is_list(args[0]):
|
||||
return max(args[0]) # max of list
|
||||
return torch.max(args[0]) # Global max of single tensor
|
||||
return torch.max(args[0]).item() # Global max of single tensor
|
||||
|
||||
# Multiple args
|
||||
if all(not self._is_tensor(x) and not self._is_list(x) for x in args):
|
||||
@@ -1047,10 +1051,13 @@ class UnifiedMathVisitor(MathExprVisitor):
|
||||
def visitAppendFunc(self, ctx):
|
||||
a = self.visit(ctx.expr(0))
|
||||
b = self.visit(ctx.expr(1))
|
||||
|
||||
if(self._is_tensor(a) and a.numel()==1):
|
||||
a = a.Item()
|
||||
if(self._is_tensor(b) and b.numel()==1):
|
||||
b = b.Item()
|
||||
if self._is_tensor(a) or self._is_tensor(b):
|
||||
a = self._promote_to_tensor(a)
|
||||
b = self._promote_to_tensor(b)
|
||||
a = self._promote_to_tensor(a,True)
|
||||
b = self._promote_to_tensor(b,True)
|
||||
if a.ndim == 0: a = a.unsqueeze(0)
|
||||
if b.ndim == 0: b = b.unsqueeze(0)
|
||||
return torch.cat((a, b), dim=0)
|
||||
|
||||
@@ -75,5 +75,27 @@ class TestMathGuider(unittest.TestCase):
|
||||
math_guider = MathGuider(G, F, "G0")
|
||||
self.assertIsNone(math_guider.model_patcher)
|
||||
|
||||
def test_math_guider_steps_context(self):
|
||||
# Mock sigmas: [10.0, 5.0, 0.0] -> 2 steps
|
||||
sigmas = torch.tensor([10.0, 5.0, 0.0])
|
||||
g0 = MockGuider(1.0)
|
||||
G = {"G0": g0}
|
||||
|
||||
math_guider = MathGuider(G, {}, "current_step / steps")
|
||||
math_guider.sigmas = sigmas # sets sigmas directly for testing
|
||||
|
||||
# Step 0: sigma = 10.0
|
||||
x = torch.zeros((1, 1, 1, 1))
|
||||
res0 = math_guider(x, torch.tensor(10.0))
|
||||
self.assertTrue(torch.allclose(res0, torch.tensor(0.0 / 2.0)))
|
||||
|
||||
# Step 1: sigma = 5.0
|
||||
res1 = math_guider(x, torch.tensor(5.0))
|
||||
self.assertTrue(torch.allclose(res1, torch.tensor(1.0 / 2.0)))
|
||||
|
||||
# Intermediate sigma should find closest
|
||||
res_near = math_guider(x, torch.tensor(4.8))
|
||||
self.assertTrue(torch.allclose(res_near, torch.tensor(1.0 / 2.0)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user