Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better node errors using error messages in output types #2445

Merged
merged 2 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/src/navi.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ def intersect(*items: ExpressionJson) -> ExpressionJson:
return {"type": "intersection", "items": list(items)}


def intersect_with_error(*items: ExpressionJson) -> ExpressionJson:
return union(intersect(*items), *[intersect("Error", item) for item in items])


def named(name: str, fields: dict[str, ExpressionJson] | None = None) -> ExpressionJson:
return {"type": "named", "name": name, "fields": fields}

Expand Down
2 changes: 1 addition & 1 deletion backend/src/nodes/properties/outputs/file_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(
if of_input is None
else f"splitFilePath(Input{of_input}.path).dir"
)
directory_type = navi.intersect(directory_type, output_type)
directory_type = navi.intersect_with_error(directory_type, output_type)
super().__init__(directory_type, label, associated_type=str)

def get_broadcast_type(self, value: str):
Expand Down
8 changes: 5 additions & 3 deletions backend/src/nodes/properties/outputs/generic_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(
output_type: navi.ExpressionJson = "number",
):
super().__init__(
navi.intersect("number", output_type),
navi.intersect_with_error("number", output_type),
label,
associated_type=Union[int, float],
)
Expand All @@ -36,7 +36,7 @@ def __init__(
label: str,
output_type: navi.ExpressionJson = "string",
):
super().__init__(navi.intersect("string", output_type), label)
super().__init__(navi.intersect_with_error("string", output_type), label)

def get_broadcast_type(self, value: str):
return navi.literal(value)
Expand Down Expand Up @@ -73,7 +73,9 @@ def __init__(
channels: int | None = None,
):
super().__init__(
output_type=navi.intersect(color_type, navi.Color(channels=channels)),
output_type=navi.intersect_with_error(
color_type, navi.Color(channels=channels)
),
label=label,
kind="generic",
)
Expand Down
2 changes: 1 addition & 1 deletion backend/src/nodes/properties/outputs/numpy_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(
assume_normalized: bool = False,
):
super().__init__(
navi.intersect(image_type, navi.Image(channels=channels)),
navi.intersect_with_error(image_type, navi.Image(channels=channels)),
label,
kind=kind,
has_handle=has_handle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,24 @@
let source = Input0;
let guide = Input1;

let valid = bool::and(
// guide image must be larger than source image
guide.width > source.width,
let kScale = bool::and(
// guide image's size must be `k * source.size` for `k>1`
guide.width / source.width == int,
guide.width / source.width == guide.height / source.height
);

Image {
width: guide.width,
height: guide.height,
channels: source.channels,
} & if valid { any } else { never }
if guide.width <= source.width {
error("The guide image must be larger than the source image.")
} else if bool::not(kScale) {
error("The size of the guide image must be an integer multiple of the size of the source image (e.g. 2x, 3x, 4x, ...).")
} else {
Image {
width: guide.width,
height: guide.height,
channels: source.channels,
}
}
"""
).with_never_reason(
"The guide image must be larger than the source image, and the size of the guide image must be an integer multiple of the size of the source image (e.g. 2x, 3x, 4x, ...)."
),
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,18 @@ class StretchMode(Enum):
outputs=[
ImageOutput(
image_type="""
let valid: bool = match Input1 {
let minMaxRangeValid: bool = match Input1 {
StretchMode::Manual => Input4 < Input5,
_ => true,
};

if valid { Input0 } else { never }
if minMaxRangeValid {
Input0
} else {
error("Minimum must be less than Maximum.")
}
""",
).with_never_reason("Minimum must be less than the Maximum."),
),
],
)
def stretch_contrast_node(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,24 @@
outputs=[
ImageOutput(
image_type="""
let anyImages = bool::or(Input0 == Image, Input1 == Image, Input2 == Image, Input3 == Image);
def isImage(i: any) = match i { Image => true, _ => false };
let anyImages = bool::or(isImage(Input0), isImage(Input1), isImage(Input2), isImage(Input3));

def getWidth(i: any) = match i { Image => i.width, _ => Image.width };
def getHeight(i: any) = match i { Image => i.height, _ => Image.height };
if bool::not(anyImages) {
error("At least one channel must be an image.")
} else {
def getWidth(i: any) = match i { Image => i.width, _ => Image.width };
def getHeight(i: any) = match i { Image => i.height, _ => Image.height };

let valid = if anyImages { any } else { never };

valid & Image {
width: getWidth(Input0) & getWidth(Input1) & getWidth(Input2) & getWidth(Input3),
height: getHeight(Input0) & getHeight(Input1) & getHeight(Input2) & getHeight(Input3),
Image {
width: getWidth(Input0) & getWidth(Input1) & getWidth(Input2) & getWidth(Input3),
height: getHeight(Input0) & getHeight(Input1) & getHeight(Input2) & getHeight(Input3),
}
}
""",
channels=4,
assume_normalized=True,
).with_never_reason(
"All input channels must have the same size, and at least one input channel must be an image."
)
).with_never_reason("All input channels must have the same size.")
],
)
def combine_rgba_node(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,15 @@
let fg = Input2;
let bg = Input3;

let valid = bool::and(
fg > bg,
image.width == trimap.width,
image.height == trimap.height,
);

if valid {
Image { width: image.width, height: image.height }
if fg <= bg {
error("The foreground threshold must be greater than the background threshold.")
} else if bool::or(image.width != trimap.width, image.height != trimap.height) {
error("The image and trimap must have the same size.")
} else {
never
Image { width: image.width, height: image.height }
}
""",
channels=4,
).with_never_reason(
"The image and trimap must have the same size,"
" and the foreground threshold must be greater than the background threshold."
),
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,24 @@
outputs=[
ImageOutput(
image_type="""
let anyImages = bool::or(Input0 == Image, Input1 == Image);
def isImage(i: any) = match i { Image => true, _ => false };
let anyImages = bool::or(isImage(Input0), isImage(Input1));

def getWidth(i: any) = match i { Image => i.width, _ => Image.width };
def getHeight(i: any) = match i { Image => i.height, _ => Image.height };
if bool::not(anyImages) {
error("At least one input must be an image.")
} else {
def getWidth(i: any) = match i { Image => i.width, _ => Image.width };
def getHeight(i: any) = match i { Image => i.height, _ => Image.height };

let valid = if anyImages { any } else { never };

valid & Image {
width: getWidth(Input0) & getWidth(Input1),
height: getHeight(Input0) & getHeight(Input1),
Image {
width: getWidth(Input0) & getWidth(Input1),
height: getHeight(Input0) & getHeight(Input1),
}
}
""",
channels=4,
assume_normalized=True,
).with_never_reason(
"RGB and Alpha must have the same size, and at least one must be an image."
)
).with_never_reason("RGB and Alpha must have the same size.")
],
)
def merge_transparency_node(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,21 @@ def quantize_image(image: np.ndarray, palette: np.ndarray):
outputs=[
ImageOutput(
image_type="""
let valid = bool::and(
Input0.width >= Input1.width,
number::mod(Input0.width, Input1.width) == 0,
Input0.height >= Input1.height,
number::mod(Input0.height, Input1.height) == 0,
Input0.channels == Input1.channels,
);

Image {
width: max(Input0.width, Input1.width),
height: max(Input0.height, Input1.height),
channels: Input0.channels,
} & if valid { any } else { never }""",
if Input0.channels != Input1.channels {
error("The target image and reference image must have the same number of channels.")
} else if bool::or(Input0.width < Input1.width, Input0.height < Input1.height) {
error("The target image must be larger than the reference image.")
} else if bool::or(number::mod(Input0.width, Input1.width) != 0, number::mod(Input0.height, Input1.height) != 0) {
error("The size of the target image must be an integer multiple of the size of the reference image (e.g. 2x, 3x, 4x, 8x).")
} else {
Image {
width: max(Input0.width, Input1.width),
height: max(Input0.height, Input1.height),
channels: Input0.channels,
}
}
""",
assume_normalized=True,
).with_never_reason(
"Target image must be larger than reference image in both dimensions, must have dimensions that are a multiple of each other, and must have the same number of channels."
)
],
)
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
},
"dependencies": {
"@babel/plugin-transform-react-jsx": "^7.17.12",
"@chainner/navi": "^0.6.2",
"@chainner/navi": "^0.7.1",
"@chakra-ui/icons": "^2.1.1",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.9.0",
Expand Down
8 changes: 2 additions & 6 deletions src/common/nodes/checkNodeValidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,8 @@ export const checkNodeValidity = ({
}

// eslint-disable-next-line no-unreachable-loop
for (const { outputId } of functionInstance.outputErrors) {
const output = schema.outputs.find((o) => o.id === outputId)!;

return invalid(
`Some inputs are incompatible with each other. ${output.neverReason ?? ''}`
);
for (const { message } of functionInstance.outputErrors) {
return invalid(`Some inputs are incompatible with each other. ${message ?? ''}`);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/common/types/chainner-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import {
const code = `
struct null;

struct Error { message: string }
def error(message: invStrSet("")): Error {
Error { message: message }
}

struct Seed;

struct Directory { path: string }
Expand Down
Loading