Technical Documentation: chaiNNer-org/chaiNNer
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (`README.md`)
chaiNNer
A node-based image processing GUI aimed at making chaining image processing tasks easy and customizable. Born as an AI upscaling application, chaiNNer has grown into an extremely flexible and powerful programmatic image processing application.
ChaiNNer gives you a level of customization of your image processing workflow that very few others do. Not only do you have full control over your processing pipeline, you can do incredibly complex tasks just by connecting a few nodes together.
ChaiNNer is also cross-platform, meaning you can run it on Windows, MacOS, and Linux.
For help, suggestions, or just to hang out, you can join the chaiNNer Discord server
ChaiNNer is under active development. If you're knowledgeable in TypeScript, React, or Python, feel free to contribute to this project and help us continue to improve it.
Installation
Download the latest release from the Github releases page and run the installer best suited for your system. Simple as that.
You don't even need to have Python installed, as chaiNNer will download an isolated integrated Python build on startup. From there, you can install all the other dependencies via the Dependency Manager.
If you do wish to use your system Python installation still, you can turn the system Python setting on. However, it is much more recommended to use integrated Python. If you do wish to use your system Python, Python 3.10 or later is required (3.11+ recommended).
If you'd like to test the latest changes and tweaks, try out our nightly builds
How To Use
Basic Usage
While it might seem intimidating at first due to all the possible options, chaiNNer is pretty simple to use. For example, this is all you need to do in order to perform an upscale:
Before you get to this point though, you'll need to install one of the neural network frameworks from the dependency manager. You can access this via the button in the upper-right-hand corner. ChaiNNer offers support for PyTorch (with select model architectures), NCNN, ONNX, and TensorRT. For Nvidia users, PyTorch or TensorRT will be the preferred way to upscale. For AMD users, NCNN will be the preferred way to upscale (or PyTorch with ROCm on Linux).
All the other Python dependencies are automatically installed, and chaiNNer even carries its own integrated Python support so that you do not have to modify your existing Python configuration.
Then, all you have to do is drag and drop (or double click) node names in the selection panel to bring them into the editor. Then, drag from one node handle to another to connect the nodes. Each handle is color-coded to its specific type, and while connecting will show you only the compatible connections. This makes it very easy to know what to connect where.
Once you have a working chain set up in the editor, you can press the green "run" button in the top bar to run the chain you have made. You will see the connections between nodes become animated, and start to un-animate as they finish processing. You can stop or pause processing with the red "stop" and yellow "pause" buttons respectively.
Don't forget, there are plenty of non-upscaling tasks you can do with chaiNNer as well!
Tips & Tricks
To select multiple nodes, hold down shift and drag around all the nodes you want to be selected. You can also select an individual node by just clicking on it. When nodes are selected, you can press backspace or delete to delete them from the editor.
To perform batch processing on a folder of images, use the "Load Images" node. To process videos, use the "Load Video" node. It's important to note however that you cannot use both "Load Images" and "Load Video" nodes (or any two nodes that perform batch iteration) together in a chain. You can however combine the output (collector) nodes in the chain, for example using "Save Image" with "Load Video", and "Save Video" with "Load Images".
You can right-click in the editor viewport to show an inline nodes list to select from. You also can get this menu by dragging a connection out to the editor rather than making an actual connection, and it will show compatible nodes to automatically create a connection with.
Helpful Resources
- Kim's chaiNNer Templates
- A collection of useful chain templates that can quickly get you started if you are still new to using chaiNNer.
- OpenModelDB Model Database
- A nice collection of Super-Resolution models that have been trained by the community.
- Interactive Visual Comparison of Upscaling Models
- An online comparison of different models. The author also provides a list of favorites.
Compatibility Notes
MacOS versions 10.x and below are not supported.
Windows versions 8.1 and below are also not supported.
Apple Silicon Macs are supported with PyTorch MPS acceleration. ONNX only supports the CPU Execution Provider, and NCNN may not work properly on some configurations.
Some NCNN users with non-Nvidia GPUs might get all-black outputs. I am not sure what to do to fix this as it appears to be due to the graphics driver crashing as a result of going out of memory. If this happens to you, try manually setting a tiling amount.
To use the Clipboard nodes, Linux users need to have xclip or, for wayland users, wl-copy installed.
GPU Support
Nvidia GPUs: Full support via PyTorch (CUDA), ONNX, and TensorRT. TensorRT offers the best performance for supported models.
AMD GPUs:
- On Linux, AMD GPUs can use PyTorch via ROCm
- NCNN is available on all platforms for AMD GPUs
Apple Silicon (M1/M2/M3): PyTorch MPS acceleration is supported.
Intel GPUs: NCNN inference is supported for Intel GPUs.
CPU: All frameworks support CPU-only mode as a fallback.
For NCNN, make sure to select which GPU you want to use in the settings. It might be defaulting to your integrated graphics!
Model Architecture Support
ChaiNNer currently supports a limited amount of neural network architectures. More architectures will be supported in the future.
PyTorch
As of v0.21.0, chaiNNer uses our new package called Spandrel to support Pytorch model architectures. For a list of what's supported, check out the list there.
NCNN
Single Image Super Resolution
- Technically, almost any SR model should work assuming they follow a typical CNN-based SR structure. However, I have only tested with ESRGAN (and its variants) and with Waifu2x.
ONNX
Single Image Super Resolution
- Similarly to NCNN, technically almost any SR model should work assuming they follow a typical CNN-based SR structure. However, I have only tested with ESRGAN.
Background Removal
TensorRT
TensorRT provides optimized inference for Nvidia GPUs. Models must be converted to TensorRT engine format for use. This offers the best performance on supported hardware.
Troubleshooting
For troubleshooting information, view the troubleshooting document.
Building chaiNNer Yourself
I provide pre-built versions of chaiNNer here on GitHub. However, if you would like to build chaiNNer yourself, simply run npm install (make sure that you have at least npm v7 installed) to install all the nodejs dependencies, and npm run make to build the application.
FAQ
For FAQ information, view the FAQ document.
Documentation
For in-depth documentation covering various aspects of ChaiNNer, including CLI usage, data representation, and a contributor's guide, kindly refer to our ChaiNNer Wiki.
Overview (`backend/tests/README.md`)
Backend Testing Guide
This directory contains comprehensive tests for the chaiNNer backend functionality.
Test Coverage
The test suite covers:
- Chain execution (
test_chain.py,test_executor.py) - Iterator system (
test_iterator.py,test_generator_exception_handling.py)- Generator classes (from_list, from_range, from_iter)
- Collector functionality
- Exception handling during iteration
- Event system (
test_events.py) - Server configuration (
test_server_config.py) - JSON serialization (
test_chain_json.py) - Utilities (
test_util.py,test_response.py,test_logger.py)
Running Tests
Prerequisites
Install test dependencies:
pip install pytest pytest-asyncio pytest-covRun All Tests
From the repository root:
python3 -m pytest backend/tests/Run with Verbose Output
python3 -m pytest backend/tests/ -vRun Specific Test File
python3 -m pytest backend/tests/test_iterator.pyRun Specific Test
python3 -m pytest backend/tests/test_iterator.py::TestGeneratorFromList::test_basic_list_mappingCode Coverage
Generate Coverage Report
Run tests with coverage:
python3 -m pytest backend/tests/ --cov=backend/src --cov-report=termGenerate HTML Coverage Report
python3 -m pytest backend/tests/ --cov=backend/src --cov-report=htmlThis creates an htmlcov/ directory with detailed coverage information. Open htmlcov/index.html in a browser to view.
Coverage Configuration
Coverage settings are configured in pyproject.toml:
- Source directories are specified
- Packages and node implementations are excluded (as they're tested via integration)
- Common exclusion patterns are defined (abstract methods, type checking, etc.)
Current Coverage
As of the latest tests:
- 105 test cases
- 50.46% overall coverage of core backend code
- Key areas covered:
api/iter.py: 100% (Generator and Collector)chain/input.py: 100%events.py: 97.87%response.py: 96.15%server_config.py: 100%
Areas with lower coverage (process.py, api modules) require more complex integration testing or involve server-specific code.
Test Structure
Test Classes
Tests are organized into classes by functionality:
class TestGeneratorFromList:
"""Test Generator.from_list functionality."""
def test_basic_list_mapping(self):
"""Test basic list mapping with simple function."""
# Test implementationFixtures
Common test fixtures are defined using pytest:
@pytest.fixture
def executor_setup(event_loop):
"""Create basic executor setup for tests."""
# Setup code
yield setup_data
# Cleanup codeAsync Tests
Async tests use the @pytest.mark.asyncio decorator:
@pytest.mark.asyncio
async def test_empty_chain_execution(self, executor_setup):
"""Test executing an empty chain."""
# Async test implementationWriting New Tests
When adding new tests:
- Follow existing naming conventions (
test_*.py) - Use descriptive test names that explain what is being tested
- Include docstrings explaining the test purpose
- Organize related tests into classes
- Use appropriate fixtures for setup/teardown
- Test both success and error cases
- Keep tests focused and independent
Example Test
def test_generator_basic_functionality(self):
"""Test that Generator produces correct output."""
gen = Generator.from_list([1, 2, 3], lambda x, i: x * 2)
results = list(gen.supplier())
assert results == [2, 4, 6]
assert gen.expected_length == 3Continuous Integration
Tests should be run as part of CI/CD pipelines to ensure code quality. The test suite is designed to be fast and reliable for automated testing.
Contributing
When contributing new backend functionality:
- Write tests for new features
- Ensure existing tests still pass
- Aim to maintain or improve code coverage
- Document any new test patterns or fixtures
Overview (`backend/src/dependencies/whls/README.md`)
Bundled Wheels
This is where we can store wheel files that are to be bundled with chaiNNer, in order to avoid needing to download them.
Requirements
Bundled wheels must be
- Reasonably small (a few MB max)
- py3-none-any (compatible with any python version and device)
- License compatible (allows bundling)
Goals
- Speed up initial start time by downloading the minimal number of wheel files from the internet
- Not increase chaiNNer's bundle size too much
Structure
The whls folder shall contain individual folders, named according to the package name we use to install via pip. Inside the folder must be the .whl file as well as the project's license.
01 Contributing (`docs/01--Contributing.md`)
Contributing
Getting Started
- Fork the repository on GitHub
- Clone the forked repository to your local machine
- Install the dependencies of the frontend with
npm ci - Install the dev dependencies of the backend with
pip install -r requirements.txt - Install the dependencies of the backend with
python ./backend/src/run.py --close-after-start --install-builtin-packages. The backend will install all of its dependencies with this command, including torch. This can take a while, so be patient.
ChaiNNer is an Electron application that uses Python for the backend. The frontend and backend communicate through a JSON API. The frontend is located in the src folder and the backend is located in the backend folder.
We use VSCode as our IDE of choice, and we strongly recommend you to do the same. We have included the .vscode folder in this repository, which contains some settings that make developing chaiNNer easier.
Running the Application
There are 2 way to run chaiNNer:
Use
npm run devto run the application in development mode. This will run the frontend and backend in parallel. The frontend will be reloaded automatically when you make changes to the code. The backend will be reloaded automatically when you make changes to the code and restart the frontend.This also allows for debugging. You can attach a debugger to the backend by using the
Attach Debugger to chaiNNerlaunch configuration in VSCode. This allows you to set breakpoints in backend code.Note: The backend will use your system python in this mode. This means that you might need to (re)install some dependencies using chaiNNer's dependency manager.
Use
npm startto run the application in production mode. The frontend will automatically start and manage the backend process. The frontend will be reloaded automatically when you make changes to the code. The backend will NOT be reloaded automatically, usenpm run devif you need this.NOTE: ChaiNNer will use its own python installation, the same that a regular installation of chaiNNer also uses.
Generally, use npm run dev when you want to develop the backend or the frontend, and use npm start you want to start chaiNNer in a more production-like mode.
Useful commands
The following commands will only work after you have installed the dependencies of the frontend and the backend, so be sure to follow Getting Started.
npm run dev&npm start- See Running the Applicationnpm run lint- Runs the linter on the frontend and backend code.npm run lint:fix- Runs the linter on the frontend and backend code and fixes all fixable errors. This includes formatting, sorting imports, etc.npm run test- Runs the tests.npm run test:update- Runs the tests and updates any outdated snapshots.
Project structure
The project is split into 2 parts: the frontend and the backend. The frontend is located in the src folder and the backend is located in the backend folder.
Frontend
The frontend is an Electron application written in TypeScript. As such, the frontend is split into 3 parts:
- The code for the main Node.js electron process, which is located in
src/main. - The code for the renderer process, which is located in
src/renderer. This is the web part of the application. We use React and Chakra UI for the frontend. - The code for the shared code between the main and renderer process, which is located in
src/common.
Backend
The backend is a Python application. The backend is roughly split into 3 parts:
backend/src/packagescontains the packages that contain chaiNNer's nodes. Most nodes are in thechaiNNer_standardpackage.backend/src/nodescontains logic shared between nodes. This includes common functionality for task such as upscaling, classes to define inputs and outputs, etc.- The rest of
backend/srccontains the code that glues everything together. This includes the code that defines the JSON API, the code that manages the nodes, the node executor, etc.
Adding a new node
In this section, we will go through the process of adding a new node to an existing package. As an illustrative example, we will add the Gaussian Blur node.
Firstly, we need to determine where to put the file of the node. Nodes are organized into package, categories, and node groups.
A package is a collection of nodes with similar dependencies. E.g. all NCNN nodes are in the
chaiNNer_ncnnpackage. All packages are located atbackend/src/packages/.In our case, we will use OpenCV's
cv2.GaussianBlurfunction to implement the Gaussian Blur node. So we will choose thechaiNNer_standardpackage, because it includes OpenCV as a dependencies.Categories are used to structure nodes in the UI. Packages can contain multiple categories. E.g. the
chaiNNer_standardpackage contains theImageandUtilitycategories. Categories are defined in the__init__.pyfile of the package.In our case, we will add the Gaussian Blur node to the
Image (Filters)category.Node groups are used to group nodes within a category. Categories can contain multiple node groups. E.g. the
Image (Filters)category contains theBlur,Sharpen, andNoisenode groups. Node groups are typically defined in/<category>/__init__.py.In our case, we will add the Gaussian Blur node to the
Blurnode group.
Note: In this example, we will add the new node to an existing category and node group, but you may need to create new categories and node groups for your specific node. This is rather simple, so just follow the structure of the existing categories and node groups.
With package, category, and node group clear, we can now create the file for our node. The file should be located at backend/src/packages//<category>/<node_group>/<node_name>.py. In our case, this is backend/src/packages/chaiNNer_standard/image_filter/blur/gaussian_blur.py.
Let's implement the node:
# gaussian_blur.py
import cv2
import numpy as np
def gaussian_blur_node(img: np.ndarray, radius: float) -> np.ndarray:
return cv2.GaussianBlur(img, (0, 0), sigmaX=radius, sigmaY=radius)The implementation is rather simple, we take an image and a radius as input, and return the blurred image as output.
Now let's register this function as node, so chaiNNer can use it. We need to import our node group, in this case the Blur node group, and use its register function as a decorator like this:
# gaussian_blur.py
import cv2
import numpy as np
from .. import blur_group
@blur_group.register(
# TODO: Add metadata here
)
def gaussian_blur_node(img: np.ndarray, radius: float) -> np.ndarray:
return cv2.GaussianBlur(img, (0, 0), sigmaX=radius, sigmaY=radius)register requires that we supply metadata, so let's add it. We need a name, a description, an icon, and we need to declare all inputs and outputs.
# gaussian_blur.py
import cv2
import numpy as np
from nodes.properties.inputs import ImageInput, NumberInput
from nodes.properties.outputs import ImageOutput
from .. import blur_group
@blur_group.register(
schema_id="chainner:image_filter:gaussian_blur",
name="Gaussian Blur",
description="Apply Gaussian blur to an image.",
icon="MdBlurOn",
inputs=[
ImageInput(),
NumberInput("Radius", minimum=0, default=2, precision=1),
],
outputs=[
ImageOutput(shape_as=0),
],
)
def gaussian_blur_node(img: np.ndarray, radius: float) -> np.ndarray:
return cv2.GaussianBlur(img, (0, 0), sigmaX=radius, sigmaY=radius)Let's go through the metadata:
schema_idis a unique identifier for the node. The schema ID currently doesn't have a defined format, but we typically usechainner:<category>:<node_name>. In our example, we usechainner:image_filter:gaussian_blur.name,description, andiconare all displayed in the UI.inputsdeclares all arguments of the python function. In our case, we have 2 inputs:imgandradius.ImageInputandNumberInputare classes that define the type of the input.ImageInputis used for images, andNumberInputis used for numbers.NumberInputtakes a few arguments.minimumandmaximumdefine the range of the number,defaultdefines the default value, andprecisiondefines the number of decimal places.outputsdeclares all return values of the python function. In our case, we return one image.ImageOutputis used for images.shape_as=0means that the output image has the same size and number of channels as the input image.
For more information about metadata, see the Node Metadata docs.
And that's it. We have successfully implemented a new node. Now we can start chaiNNer and see our new node in action.
For more information about nodes, see the nodes documentation.
02 Data Representation (`docs/02--Data-Representation.md`)
Data representation
ChaiNNer has to deal with multiples types of data (images, text, numbers, etc.). To make data handling easier, ChaiNNer enforces that all node implementations follow certain conventions. These conventions are guaranteed by inputs and enforced by outputs.
Numbers
Depending on the precision of the inputs, chaiNNer will either use float (precision > 0) or int (precision = 0) to represent numbers.
This is guaranteed by all numeric inputs. Numeric outputs do not enforce any such thing and will accept both float and int.
Text
Text is represented as str. If a number is connected to a text input, it will be converted to str.
Images
Images are represented using numpy ndarray and are guaranteed to have the following:
- They are
float32arrays. - All values are finite (no NaN, inf, -inf) and range between 0 and 1 (inclusive).
- The shape of the array is either
(height, width, channels)(withchannels > 1) or(height, width). Grayscale/single-channel images are represented as 2D arrays. - Channels are in BGR and BGRA order.
- The array is readonly. Writing to it will cause a runtime error.
Note: You may not assume that chaiNNer only supports images with 1, 3, or 4 channels. It is possible that chaiNNer will support images with more channels in the future. If your node relies on this assumption, you should use ImageInput's channels argument to enforce it. Simply add channels=[1, 3, 4].
ImageOutput will try to convert the output image to fit the above conventions. It will:
- Convert the image to
float32if it isn't already. Integer formats (e.g.uint8) will automatically be brought into 0 to 1 range. - Clip values between 0 and 1 (inclusive).
- Convert single-channel 3D images to 2D.
Since channel order cannot be checked, you have to guarantee this yourself.
Colors
Colors are represented using chaiNNer's Color class.
The Color class is a wrapper around a tuple of floats that represent the channels of the color. Just like with images, the channels are in BGR and BGRA order. You can generally think of Color as a 1x1 image.
Models
ChaiNNer has types for models of each platform (PyTorch, NCNN, ONNX). Models will be represented using platform-specific classes. See their input and output classes for more information.
03 Nodes (`docs/03--Nodes.md`)
Nodes
Core concepts and idea
ChaiNNer is a visual programming language and development environment. Chains (.chn files) are programs and chaiNNer's UI is the editor for this programming language. The backend is the compiler and executor. Nodes are the functions of this language, they take inputs and produce outputs. Nodes are implemented as Python functions with extra metadata. This metadata is used to display the node in the UI and determine their behavior. E.g. type information is used to determine which connections are valid. Nodes are structured into categories and packages. ChaiNNer uses packages to manage the dependencies of nodes (e.g. pip packages), and categories to organize nodes in the UI.
Rules for nodes
While nodes can generally implement whatever functionality you want, there are some rules that nodes must follow:
Immutability
Nodes are not allowed to modify their inputs. This is because chaiNNer will cache and reuse values for performance reasons. If a node modifies its input, this will break the cache and cause chaiNNer to produce incorrect results.This is especially important for images, which are passed around as numpy arrays. Numpy arrays are mutable, so you need to be careful when working with them. If you need to modify an input, make a copy of it first.
Determinism
Nodes are assumed to be deterministic. This means that a node should always produce the same output for the same input. This is important for caching.If your node needs random numbers, either use a static seed or use a seed input. See the Random Number node for an example of this.
We do plan to add support for non-deterministic nodes in the future, but this is not implemented yet. See #1677.
Side effects
Nodes are assumed to be side effect free. This means that a node should not change the file system (e.g. creating, changing, or deleting files), write to clipboard, open programs/files, or otherwise change the state of the system.If your node does have side effects (e.g. saving a file), you must declare this in the node metadata by using
side_effects=True.
Node metadata
Nodes have rich metadata. This includes a name, description, icon, and more. This metadata is used to display the node in the UI and determine their behavior. E.g. type information is used to determine which connections are valid.
Metadata is used to define a contract between a node, the rest of the backend, and the frontend. This contract is used to verify the correctness of the node implementation, optimize the node graph, and define frontend behavior.
Schema ID
The schema ID of the node. This is used to identify the node in the JSON API. This must be unique across all packages. We recommend using the formatchainner:<category>:<name>for chaiNNer nodes.Note that the chaiNNer team has moved around nodes in the past. As such, you may find nodes that do not follow the above format. We recommend using the format above for new nodes.
Name
The name of the node. This is used to display the node in the UI.When choosing a name, make sure that it is descriptive and unique. If you are implementing a node that already exists in another package, make sure to use the same name. E.g. PyTorch and NCNN both have a Load Model node.
Description
A description of the node. This is currently used in the node's tooltip in the node selector sidebar, as well as the Node Documentation modal. If given an array of strings, only the first string will populate the tooltip, while all strings will populate the modal.Icon
The icon of the node. Use the search bar on React Icons for which icons you want to use. Note that we only support icons from theBs,Cg,Md, andImfamilies.Inputs/Outputs
The inputs and outputs of the node. They describe the name, behavior, and type of the inputs and outputs. See Input and Output for more information.Side effects
Whether the node has side effects. See Rules for nodes for more information.
Inputs and Outputs
Nodes must explicitly declare all their inputs and outputs as part of their metadata. This is done using the inputs and outputs properties. Each argument of the python function has a corresponding input, and the return value of the function has a corresponding output. See The anatomy of a node for an example.
The main purpose of explicitly declaring inputs and outputs is to provide more information about the node. E.g. the type information is used to determine which connections are valid, the minimum/maximum information is used to validate user inputs, and placeholder and defaults are used to improve the user experience. They also make guarantees about input data and enforce conventions.
The most common classes used to declare inputs are NumberInput, TextInput, and ImageInput. There is also SliderInput as an alternative to NumberInput. Many more inputs are available.
The most common classes used to declare outputs are ImageOutput, NumberOutput, and TextOutput. Many more outputs are available.
It is important to note that inputs and outputs have IDs. These IDs are used to identify the input/output in the JSON API. By default, IDs are implicitly assigned based on order. E.g. in the following example, A has the input ID 0, B has the input ID 1, and C has the output ID 0.
inputs=[
ImageInput("A"),
NumberInput("B"),
],
outputs=[
ImageOutput("C"),
],Reordering
Since IDs are used to identify inputs and outputs, reordering them is a breaking change. If you want to reorder inputs or outputs, assign them explicit IDs using the with_id function, and then reorder them.
# 1. Make IDs explicit
inputs=[
ImageInput("A").with_id(0),
NumberInput("B").with_id(1),
],
# 2. Reorder inputs
inputs=[
NumberInput("B").with_id(1),
ImageInput("A").with_id(0),
],Adding a new input in between existing inputs is also a breaking change, because it counts as appending the new input and then moving it in between the existing inputs. To add a new input in between existing inputs, assign explicit IDs to all inputs and then move the new input into the correct position.
# 1. Add input
inputs=[
ImageInput("A"),
NumberInput("B"),
TextInput("T"),
],
# 2. Make IDs explicit
inputs=[
ImageInput("A").with_id(0),
NumberInput("B").with_id(1),
TextInput("T").with_id(2),
],
# 3. Move input
inputs=[
ImageInput("A").with_id(0),
TextInput("T").with_id(2),
NumberInput("B").with_id(1),
],The same principles also apply to outputs.
Input groups
Input groups are a way of grouping inputs together and giving them additional functionality. Groups can change the visual appearance of the inputs, add additional functionality, and more.
The following groups are currently supported:
Conditional
Conditional groups are used to hide/show inputs based on the value of another input. This is useful for inputs that are only relevant in certain situations. E.g. the Save Image node uses conditional groups to hide JPEG-specific options when PNG is selected as the output format.We most commonly use
EnumInputs orDropDownInputs as the input that controls the visibility of the group. This is because they are the easiest to work with.Example:
pythonfrom enum import Enum from nodes.groups import if_enum_group from nodes.properties.inputs import EnumInput, NumberInput class Format(Enum): A = 1 B = 2 C = 3 # inside @node_group.register(...) inputs = [ EnumInput(Format, "Format", default=Format.A).with_id(0), if_enum_group(0, Format.B)( # this weight will only be shown if B is selected NumberInput("B weight", default=50, minimum=0, maximum=100), ), ]Seed
This group is a wrapper around theSeedInputto change its visual appearance for better useability.pythonfrom nodes.group import group from nodes.properties.inputs import SeedInput # inside @node_group.register(...) inputs = [ seed_group(SeedInput()), ]And more. See
backend/src/nodes/groups.pyfor more groups.
Python type hints
We require that all node functions have type hints. This is simply for correctness and to make the code easier to understand. You must add type hints to all arguments and the return value of the function.
def blur_node(img: np.ndarray, radius: float) -> np.ndarray:
...If you forget type hints or get them wrong, you will get a warning or an error when you try to register the node. The input and output metadata is used to validate your type hints. E.g. if you forget the | None for optional inputs, you will get a warning.
Types
All inputs and outputs have types. These types are automatically generated from the input/output class and their supplied arguments. E.g. NumberInput("foo", minimum=0, maximum=100) will have the type int(0..100) (read: an integer between 0 and 100 inclusive).
Types are expressed using a custom type system called Navi. Navi is not relative to Python's type hints or TypeScript types, it's a completely separate system.
More information about Navi can be found in the Navi documentation.
Type narrowing
While all inputs and outputs have automatically generated types, these types might not be precise enough. Especially outputs often have very broad types. E.g. ImageOutput has the type Image, which means "an image of any size". It could be a 1x1 image, or a 10000x10000 image. This is not very useful for the frontend.
To fix this, most output classes have a type argument, typically called output_type or *_type. This argument is used to narrow the type of the output. E.g. ImageOutput("foo", image_type="Image { width: 100, height: 100, channels: 3 }") narrows the type to "an RGB image of size 100x100".
Since output types often depend on inputs, you can reference inputs using special variables. E.g. Input0 is the type of the input with ID 0.
Let's take a look at the inputs and outputs of the Create Border node. This node adds a solid color border to an image. It has 2 inputs: the image and the width of the border.
inputs=[
ImageInput(),
NumberInput("Width", unit="px"),
],
outputs=[
ImageOutput(
image_type="""
Image {
width: Input0.width + 2 * Input1,
height: Input0.height + 2 * Input1,
channels: Input0.channels,
}
""",
)
],Documentation
Inputs and outputs both allow optional documentation to be added to add more information about it to the Node Documentation modal. You can add documentation to an input or output using the .with_docs() method, like so:
inputs=[
ImageInput().with_docs(
"An example image input.",
"Each string passed in will be its own paragraph in the resulting docs page.",
"And, it supports _markdown_ *formatting*.",
),
NumberInput().with_docs(
"Confusing inputs can be given a hint flag, which will show a tooltip icon on the input in the node itself.",
"Use these when you think people would need an explanation about something regardless of if they have looked at the docs modal."
hint=True,
)
],Broadcasts
Broadcasts are a way of sending data from the backend to the frontend. They are used for updating previews (e.g. image previews), enhancing error messages with useful information, and providing the type system with additional information. Output classes (e.g. ImageOutput) are responsible for generating broadcasts.
There are 2 types of broadcasts: data broadcasts and type broadcasts.
Note: Broadcasts are completely optional. Outputs can have both types of broadcast, only one, or none at all.
Note: Sending a broadcast does not mean that the frontend will acknowledge it. In fact, the frontend ignores most type broadcasts and only uses some data broadcasts (e.g. the ones from LargeImageOutput for preview images).
Data broadcasts
Data broadcasts are used to send data from the backend to the frontend. E.g. ImageOutput sends the dimensions of output images for better error messages, and LargeImageOutput sends the dimensions and image data.
Data broadcasts are implemented using the get_broadcast_data method on output classes that inherit from BaseOutput.
Type broadcasts
Type broadcasts are used to send additional type information from the backend to the frontend. While we try our hardest to encode as much information as possible in the type system, there are some things that are impossible to statically evaluate using the type system.
E.g. we cannot infer the dimensions of an image from a file path alone. So ImageOutput sends an Image type with the dimensions of the image.
Type broadcasts are implemented using the get_broadcast_type method on output classes that inherit from BaseOutput.
Packages
A package is a collection of nodes. ChaiNNer comes with a set of standard nodes (chaiNNer_standard), but you can also create your own packages.
Unfortunately, packages are still a bit of a work in progress. We plan to improve the package system in the future. Right now, they are just fancy directories that contain nodes.
Categories
Nodes are categorized. Categories are defined per package and follow a strict hierarchy. You can see this hierarchy in the node selector sidebar of chaiNNer.
At the top of the hierarchy are categories (e.g. Image Filter). Each category can have any number of subcategories, called node groups. Node groups then contain the actual nodes.
This gives us a clear path: package -> category -> node group -> node. E.g. the path of the Gaussian Blur node is chaiNNer_standard -> Image Filter -> Blur -> Gaussian Blur.
The anatomy of a node
This section will explain how nodes are implemented in chaiNNer. We will use the Opacity node as an example. This node takes an image and an opacity value as input, and outputs the image with the opacity applied. It basically makes the image more transparent.
Here's the full code of the node:
import numpy as np
import navi
from nodes.impl.pil_utils import convert_to_BGRA
from nodes.properties.inputs import ImageInput, SliderInput
from nodes.properties.outputs import ImageOutput
from nodes.utils.utils import get_h_w_c
from .. import adjustments_group
@adjustments_group.register(
schema_id="chainner:image:opacity",
name="Opacity",
description="Adjusts the opacity of an image. The higher the opacity value, the more opaque the image is.",
icon="MdOutlineOpacity",
inputs=[
ImageInput(),
SliderInput(
"Opacity",
maximum=100,
default=100,
precision=1,
controls_step=1,
unit="%",
),
],
outputs=[
ImageOutput(size_as=0, channels=4)
],
)
def opacity_node(img: np.ndarray, opacity: float) -> np.ndarray:
"""Apply opacity adjustment to alpha channel"""
# Convert inputs
c = get_h_w_c(img)[2]
if opacity == 100 and c == 4:
return img
imgout = convert_to_BGRA(img, c)
opacity /= 100
imgout[:, :, 3] *= opacity
return imgoutWe can see the code is roughly split into 2 parts: @adjustments_group.register(...) and def opacity_node.
The first part defines the node's metadata. Nodes are registered for a specific node group (see the section on categories). In this case, the node is registered for the adjustments_group node group. Name, description, and icon should be self-explanatory. The schema_id is a unique identifier for the node. Input and outputs define rich metadata about the arguments and return value of the node. E.g. The SliderInput contains a lot more information than opacity: float. See the section on inputs and outputs for more information.
The second part is the actual node implementation. The implementation is rather straightforward. The node takes an image and an opacity value as input. Aside from handling a special case, it just multiplies the alpha channel of the image with the opacity value. Importantly, the input image is not modified. Instead, the image is copied (convert_to_BGRA guarantees a copy) and the copy is modified and returned. See the nodes section as to why copying is necessary.
It should be noted that metadata is used to verify the correctness of the implementation. E.g. the type hints of img and opacity are checked against the input metadata. So you can rely upon the fact that opacity is a number between 0 and 100. It's similar for the return type annotation. E.g. in this example, ImageOutput will validate that the returned image has 4 channels (as declared by channels=4) and will throw an error otherwise.
04 Navi (`docs/04--Navi.md`)
Navi
ChaiNNer has its own type system called Navi. The type system has 2 main purposes:
Static validation
It checks that the connections between nodes make sense. E.g. connecting a string output to an image input will cause a type error. This is done before running the chain, live inside the graph editor.While this might seem very simple, because of the expressiveness of Navi, we can do some very interesting things. For example, we check number ranges, image dimension, model architectures, and even file types.
Since we can statically determine the validity of connections, the type system is also used to suggest relevant nodes to the user. This takes the form of graying out invalid handles and showing only connectable nodes in the context node selector.
Real time feedback
Since the type system is evaluated and update in real time, users get instant feedback. The frontend uses types to show the value or information about the value a node will output. E.g. number and text outputs will show the value, while image outputs will show the dimensions of the image.This is especially useful for new users who are not familiar with chaiNNer's nodes. They don't have to guess the size of an image upscaled with a 4x model, but the type system calculates it for them. They don't have to guess what Text Append does, because they can see its output and play around with it in real time.
Introduction
Before we start, while Navi can be somewhat complex, it should also be easy to pick up. Navi intentionally uses TypeScript/Python-like syntax, and has similar concepts. While everything works on types, the operations should feel familiar.
For example, here's a function that pads a string to a certain width:
def padRight(s: string, width: uint): string {
let len = string::len(s);
let fillLen = width - len;
match fillLen {
1.. => string::concat(s, string::repeat(" ", fillLen)),
_ => s,
}
}This code should be understandable to most programmers. A few things to note:
- Braces! Navi uses braces for blocks, just like TypeScript. And just like TypeScript, Navi is white-space insensitive.
- Semicolons. Each
letstatement ends with a semicolon. This is required. - Function are defined using
def. They require type annotations for all arguments. - There is no
return. The last expression in a function is the return value. Here, it's thematchexpression. - Navi supports simple pattern matching using
match.ifis also supported, butmatchis more powerful.- Number ranges are defined using the
..syntax. E.g.1..10is the set of all numbers between 1 and 10, including 1 and 10, and1..is all numbers greater than or equal to 1. match(andif) are expressions and can be used anywhere. E.g.let x = match foo { 1..10 => 1, _ => 0 };is valid.
- Number ranges are defined using the
- Variable are declared using
let. This is similar toletin TypeScript, but the variables are immutable. - Navi does not support
.name()call syntax (right now). String function have to be called by their full name.
But remember, Navi is a type system, so padRight will operate on types. Examples:
let a = padRight("foo", 4);
// a is "foo "
// Multiple values
// (`|` is the type union operator)
let b = padRight("foo" | "food", 5);
// b is "foo " | "food "
let c = padRight("foo", 1 | 2 | 3 | 4 | 5);
// c is "foo" | "foo " | "foo "Navi
Navi is a set-based type system. This means that every type is a set of values. For example, the type number is the set of all numbers, int is the set of all integers, string is the set of all strings, and so on.
Important types
number- the set of all numbers. This includes integers, floats, and the infinities, and NaN.int- the set of all integers. This does not include the infinities, and NaN.uint- the set of all unsigned integers. It's allints โฅ 0.string- the set of all strings.bool-true | false.null- this type is used by chaiNNer to present optional inputs. If an input isnull, no value is given and the python function will be givenNone.any- the set of all values. This is more of a placeholder type or theoretical construct. It's rarely used in practice.never- the empty set. Type is typically used to present on error.0,1,2.5,-10,inf,-inf,nan- literal numeric types. These are used to represent specific numbers. E.g.0is the set containing only the number 0.0..100,0..inf,-10..10- numeric range types. These are used to represent a range of numbers. E.g.0..100is the set of all numbers (including non-integers) between 0 and 100, including 0 and 100.int(0..100),int(0..inf),int(-10..10)- integer range types. These are used to represent a range of integers. E.g.int(0..100)is the set of all integers between 0 and 100, including 0 and 100. Note that integer ranges are equivalent to casting numeric ranges toint. E.g.int(0..100)is equivalent toint & 0..100."","foo","bar"- literal string types. These are used to represent specific strings. E.g.""is the set containing only the empty string.
Type operations
Navi has a set of operations that can be used to create new types from existing types. These operations are:
&- Intersection
This operation creates a new type that is the intersection of the two types. E.g.-100..100 & 0..200is equivalent to0..100,0..10 & 20..30is equivalent tonever,any & Tis equivalent toT, andnever & Tis equivalent tonever.Note: intersection can also be thought of as asserting that a value is of a certain type. E.g.
int & 0..100is the set of all integers between 0 and 100, including 0 and 100. This is equivalent to casting0..100toint.|- Union
This operation creates a new type that is the union of the two types. E.g.-100..100 | 0..200is equivalent to-100..200,1 | 2 | 3 | 4is equivalent toint(1..4),any | Tis equivalent toany, andnever | Tis equivalent toT.
Structure types
In the set-theoretic sense, structure types are the cartesian product of their fields, but it's easier to think of them as C-like structs. Structure types allow us to define new types from existing types.
For example, this is how chaiNNer's Image type is defined:
struct Image {
width: uint,
height: uint,
channels: int(1..inf),
}As we can see, Image has 3 fields: width and height are unsigned integers, and channels is an integer between 1 and infinity (= unsigned integer starting at 1).
It should be noted that the above code is the type definition of Image. To get a type, we need to instantiate it. This is done by providing values for the fields. For example, Image { width: uint, height: 100, channels: 3 } is the type of all images with a height of 100 and 3 channels.
Fields that are equivalent to their definitions can be omitted. For example, Image { height: 100, channels: 3 } is equivalent to Image { width: uint, height: 100, channels: 3 }. Taking this to the extreme, we can omit all fields to get the type of all images: Image {}. Since this is quite common for structure types, we can omit the braces and just write Image.
Set operations apply to structure types as well. For example, Image { width: 100 } & Image { height: 100 } is equivalent to Image { width: 100, height: 100 }, and Image { width: 100 } | Image { width: 200 } is equivalent to Image { width: 100 | 200 }.
Constants
It is possible to define structure types with no fields. These are called unit types or constants. They are a type that represent only a single value: themselves.
The best examples for constants are true and false. Here's how they are defined:
struct true;
struct false;Note: struct Name; is equivalent to struct Name {}.
true and false are unique types and are not assignable to each other, they are disjoint (true & false is equivalent to never).
As you might have guessed, true and false are used to define the bool type. The bool type is equivalent to true | false:
let bool = true | false;Type aliases
It's possible to give types names. This is done with the let keyword.
let Nat0 = int(0..inf);
let Nat1 = int(1..inf);
let keywords = "enum" | "let" | "struct";Type aliases are used to both shorten long type names. Since everything is a type in Navi, type aliases can also be thought of as immutable variables (similar to const in JavaScript).
It's important to note that the order of definition does not matter. This means that the following code is valid:
let b = a | Foo { value: 2 };
let a = Foo { value 1 };
struct Foo { value: int }Match expressions
Match expressions are a core feature of the language and can be thought of as the opposite of type unions. While type unions allow us to combine types, match expressions allow us to split types.
A match expression consists of a match value and a set of match arms. Match arms represent conditions that the match value must satisfy. If the match value satisfies a match arm, the match arm's body is evaluated.
Example:
let x = int(0..10);
let y = match x {
0..5 => "small",
5..10 => "big",
};The value of y will be "small" | "big". Since x intersects with both 0..5 and 5..10, both match arms are evaluated and their results are unioned together.
Exhaustiveness & the default match arm
It is important to note that match arms must be exhaustive. This means that every possible value of the match value must be covered by at least one match arm. If this is not the case, a runtime error will be thrown.
If necessary, a default match arm can be used to cover all remaining values. This is done by using the _ keyword as the match arm's condition.
Example:
let x = /* some arbitrary type */;
let y = match x {
0..5 => "small",
5..10 => "big",
_ => "unknown",
};The order of match arms
Match arms are evaluated in order. This is important because match will remove (set difference) the match arm's pattern from the match value. So later match arms will see the reduced match value.
As an example, let's see how following the value of y changed for different values of x. We define 2 structs (Small and Big) as helpers to make it clear which values of x take which match arm.
struct Small { value: any }
struct Big { value: any }
let y = match x {
0..5 => Small { value: x },
5..10 => Big { value: x },
_ => x,
};Let's try a few values of x:
Let x = 3: y will be Small { value: 3 }. This result should be intuitive. 3 is in the range 0..5, so the first match arm is evaluated. Since the first match arm completely covers the match value, no other match arms are evaluated, and the final value will be Small { value: 3 }.
Let x = 3 | 7: y will be Small { value: 3 } | Big { value: 7 }. This should also be fairly intuitive. 3 only matches the first match arm, and 7 only matches the second match arm. Both match arms are evaluated with 3 and 7 respectively, and their results are unioned together.
This brings us to an important property of match: match A | B { ... } is equivalent to match A { ... } | match B { ... }. This means that match distributes over unions.
Now, let's try x = 5: y will be Small { value: 5 }. This is where things get interesting. 5 matches both match arms, but only the first one will be evaluated. This is because the first match arm completely covers the match value, so the second match arm will never be evaluated. This is why y is Small { value: 5 } and not Small { value: 5 } | Big { value: 5 }.
Finally, let's try x = int(0..20): y will be Small { value: int(0..5) } | Big { value: int(6..10) } | int(11..20). Let's go through the match arms step by step:
- We arrive at the first match arm with a match value of
int(0..20). The intersection ofint(0..20)and0..5isint(0..5), so this is the value used to evaluate the first match arm. The result of this match arm isSmall { value: int(0..5) }. - We then remove
0..5from the match valueint(0..20)to getint(6..20). This will be the match value for the second match arm. - We arrive at the second match arm with a match value of
int(6..20). The intersection ofint(6..20)and5..10isint(6..10), so this is the value used to evaluate the second match arm. The result of this match arm isBig { value: int(6..10) }. - We then remove
5..10from the match valueint(6..20)to getint(11..20). This will be the match value for the third match arm. - We arrive at the third match arm with a match value of
int(11..20). Since the third match arm is the default match arm, it will be evaluated with the match valueint(11..20). The result of this match arm isint(11..20). - We union the results of all match arms together to get
Small { value: int(0..5) } | Big { value: int(6..10) } | int(11..20).
Bindings
In the above example, we observed that match automatically narrows down the variable x inside each match arm. Navi is unable to do this automatically for more complex match values. E.g. struct fields, type expressions, function calls, etc. cannot be narrowed down automatically.
Example:
let x = Image { width: 100 | 200 };
let y = match x.width {
100 => "small",
_ => x.width, // x.width will still be 100 | 200
};
// y = "small" | 100 | 200To work around this, we can use bindings. Bindings are used to directly assign the current match value of the match arm to a variable. This variable can then be used inside the match arm. This is done by using the as keyword.
Example:
let x = Image { width: 100 | 200 };
let y = match x.width {
100 => "small",
_ as width => width, // width will still be 200
};
// y = "small" | 200`if`
Navi also supports if expression on bool values. This is simple syntactic sugar for match.
Example:
let x: bool = someFunction();
let y = if x {
"true"
} else {
"false"
};is equivalent to:
let x: bool = someFunction();
let y = match x {
true => "true",
false => "false",
};Note: else is required.
else if is also supported. Just like in other languages, if { ... } else if { ... } else { ... } is equivalent to if { ... } else { if { ... } else { ... } }.
Functions
Functions can be defined using the def keyword and called using a C-like call syntax. Functions are very similar to functions in other programming languages, but they have one important difference: All arguments are types.
Example:
def unionInt(a: int, b: int): int {
a | b
}
let x = unionInt(1, 2 | 3);
// x = 1 | 2 | 3There are 2 syntaxes for functions: expression syntax and scope syntax.
- Expression syntax:
def unionInt(a: int, b: int): int = a | b; - Scope syntax:
def unionInt(a: int, b: int): int { a | b }
The only difference is that scope syntax allows you to use multiple statements inside the function body. E.g. let statements, if statements, etc.
Example:
def unionInt(a: int, b: int): int {
let foo = a | b;
foo
}Note: return is not supported. Functions always return the result of the last expression in the function body.
Note: The return type annotation is optional. However, it is recommended to always use it. It makes the code easier to read and allows Navi to detect errors.
Predefined functions
Navi has some predefined functions that are a standard library of sorts. This includes functions for string operations (length, slice, concatenation), number operations (addition, multiplication, rounding, etc.), and more.
All full list of all predefined functions can be found here. Note that some of these functions are intrinsics. This means that they are not actually implemented using Navi, but are instead implemented in JavaScript/TypeScript.
Syntax sugar
Since arithmetic operations are commonly used in chaiNNer's Navi types, they get special syntax. You can use +, -, *, /, >, >=, <, <=, ==, and !=. They will behave as expected. E.g. 1 + 2 == 3.
Under the hood, they are syntactic sugar for predefined functions. E.g. a + b is equivalent to number::add(a, b).
Equality
Since there is syntax for == and !=, it is important to understand how equality works in Navi.
The result of equality is a bool type. Since types are sets of values, we define equality of 2 types $A$ and $B$ as such: ${ a = b : a \in A, b \in B }$ where $a = b$ return true or false depending on whether $a$ and $b$ are the same value. Type equality is the set of all pair-wise comparisons of the values in $A$ and $B$.
This means that equality has the following properties:
A == BisB == A.(A | B) == Cis(A == C) | (B == C).(A & B) == Cis(A == C) & (B == C).A == neverisnever.A == anyisbool(true | false).
Let's look at a few examples to see this in action:
1 == 1istrue.1 == 2isfalse.1 | 2 == 1istrue | falsewhich isbool.1 & 2 == 1istrue & falsewhich isnever.
Lastly, A != B is defined as not A == B.
Note: When comparing values, nan is equal to itself. So nan == nan is true.
Unsoundness
It is very important to note that Navi is unsound. This means that there are false statements that are accepted as true by Navi. But every true statement can be proven by Navi. This tradeoff is forced upon us by the very nature of mathematical logic.
However, this is not a problem in practice, quite the opposite. It means that we 100% know that chains that do not pass the type system are invalid. But chains that pass the type system are not necessarily valid. This is a very important distinction. We can use Navi to detect errors, but we cannot use it to prove correctness.
05 CLI (`docs/05--CLI.md`)
CLI
CLI mode lets you use ChaiNNer without a GUI. This is useful for running ChaiNNer on a server or in a Docker container.
IMPORTANT: CLI mode is experimental and may change in the future. It is not recommended to use it in production. All of its features are subject to change.
IMPORTANT: CLI mode currently has a bug that prevents you from seeing the console outputs of chainner.exe. This is a known issue and will be fixed in the future.
Introduction
In some cases, you may want to run ChaiNNer without the GUI. E.g. to programmatically run the same chain with different inputs. As such, CLI mode is a way for advanced users with programming experience to work around the limitations of GUI chaiNNer.
Conceptually, CLI mode simply opens a .chn chain file and runs it. It's the same as if you opened the chain in the GUI and pressed the "Run" button. Of course, CLI mode has a way to set the inputs of nodes within the chain before running it.
Usage
The basic command of CLI mode is this:
chainner.exe run "path/to/your-chain.chn"This will run the chain as is, without changing any inputs. The exit code of the program will be 0 if the chain ran successfully, and non-zero if it failed.
If you want to change the inputs of nodes within the chain, you can specify a file with input overrides like so:
chainner.exe run "path/to/your-chain.chn" --override "path/to/your-input-overrides.json"Input overrides
Input overrides are a JSON file that specifies the inputs of nodes within the chain. The format is as follows:
{
"inputs": {
"<input override id>": string | number | null
}
}You can specify as many input overrides as you want.
To get the input override ID of a specific input, open the chain in the GUI and right-click the input. Then click "Copy Input Override ID". This will copy the input override ID to your clipboard.
Supported inputs
The following inputs support overrides:
- Text input
- Number input
- File input
- Directory input
Dropdown, checkbox, and generic inputs do not support overrides.
Override values
Overrides can be either a string, a number, or null.
The value of the override depends on the type of the input. E.g. use a number for a number input, and a string for a text input. Using strings to override number inputs will result in an error.
null is used to reset optional inputs. It will not reset inputs to their default value, but rather remove the input value. This is only useful for a few optional text inputs in chaiNNer.
06 Troubleshooting (`docs/06--Troubleshooting.md`)
Troubleshooting
After installing PyTorch, the nodes still do not show up
This issue is still a bit of a mystery to us, but you can try a couple of things.
Ensure you have at least 3GB of free RAM before starting chaiNNer.
Uninstall PyTorch, restart chaiNNer, and reinstall it.
PyTorch is failing to install from the dependency manager
- Make sure you have nothing blocking pip requests
Windows
- Run this command in a terminal:
%appdata%/chaiNNer/python/python/python.exe -m pip install torch==1.10.2+cu113 --extra-index-url https://download.pytorch.org/whl/cu113
Linux
- I'm not 100% sure what the command would be on linux, but it would essentially be the same thing except with the path to whatever the equivalent of appdata is. so
linuxappdata.../chaiNNer/python/python/python3.9 -m pip install torch==1.10.2+cu113 --extra-index-url https://download.pytorch.org/whl/cu113
Has Nvidia GPU, PyTorch using CPU
Check the dependency manager and ensure it installed the +cu113 version. If it did not, and it says CUDA supported at the top, then uninstall and reinstall PyTorch.
Check to make sure you have the
nvidia-smicommand available to you. Open a terminal (command prompt, powershell, windows terminal) and typenvidia-smi. If it errors, reinstall your drivers and restart your computer.If chaiNNer or a terminal still isn't able to use
nvidia-smi, try adding your System32 folder to your PATH environment variable. Make sure to restart your computer after.
Has AMD/Intel GPU, PyTorch using CPU
- PyTorch can only use Nvidia GPUs. In this case, you would want to try using NCNN as the processing framework instead. You can convert most supported PyTorch models to NCNN through chaiNNer (just be sure to also install ONNX as it is needed in the conversion process).
Checked logs and see `CUDA_INITIALIZE` errors
Ensure your drivers are up to date. Outdated drivers might not support the CUDA version that PyTorch and ONNX rely on.
Ensure your GPU isn't too old. Your GPU needs to be able to support CUDA 11.3. If it only supports CUDA 10 for example, you'll need to manually install an older PyTorch version to your system Python and set chaiNNer to use system Python. However, if you do this we cannot guarantee everything will work properly.
vkQueueSubmit error with NCNN
If you are using an auto-tiling mode, try setting a manual tile size. The lower the number the more likely it will work.
If you are on Windows, you can try using this fix suggested by one of the NCNN devs.
"ChaiNNer was unable to install its integrated Python environment" message or simply fails to start up
Ensure you have access to the internet and that you did not restrict chaiNNer from accessing the internet. ChaiNNer needs internet access in order to download its Python environment and the required dependencies.
If you do have internet access, try going to your
%appdata%/chaiNNer(windows),.config/chaiNNer(linux), orApplication Support/chaiNNerfolder, and deleting the Python folder that is there. This will force chaiNNer to download Python again if the previous download was corrupt.If this all does not work, you can force chaiNNer to use system Python. Right now this is not configurable via the UI without being able to start up first, so you need to go to your data folder mentioned above, go to
/settings, and either find or create ause-system-pythontext file (with no extension) that just contains the wordtrue. In order for this to work, you need to have Python installed to your system.
Integrated Python downloaded, but failed to start up after installing dependencies
- If you are on an older macOS version, such as any version below 10.15, this is unavoidable and you currently cannot use chaiNNer. This is due to a dependency (opencv) that we simply cannot work around at this time.
07 FAQ (`docs/07--FAQ.md`)
Frequently Asked Questions
Can you add [feature request]?
- Maybe! But most likely it will be quite a while before we're able to implement any suggestions. We have a large backlog of major things we want to get done before we start working on other major things. However, if it's a small request, we may be able to implement it sooner.
How can I batch upscale?
- Any of the iterators.
Can I link multiple iterators together to do paired [whatever]?
- No. This will be possible once we do our iterator rewrite. For now though, this isn't possible.
Can you add [random upscaling network] support?
- Maybe. We implement various upscaling architectures in a different way from most other applicatons. Most other apps just make a wrapper around the official CLI version of whatever network it is. We actually integrate the architecture into our code, and do complex parsing to determine the parameters of the models. This results in a really seamless and straightforward user experience, with a downside of it taking a long time to implement each network.
What does the name mean?
- chaiNNer is a play on the fact that you can "chain" different tasks together, with the NN in the name being a common abbreviation for Neural Networks. This is following the brilliant naming scheme of victorca25's machine learning tools (traiNNer, iNNfer, augmeNNt) which he granted me permission to use for this as well.
Why not just use Cupscale/IEU/CLI?
All of these tools are viable options, but as anyone who has used them before knows they can be limited in what they can do. Many features like chaining or interpolating models are hardcoded and provide little flexibility. Certain features that would be useful, like being able to use a separate model on the alpha layer of an image for example, just do not exist in Cupscale. Inversely, you can pretty much do whatever you want with chaiNNer provided there are nodes implemented. Whatever weird feature you want implemented, you can implement yourself by connecting nodes however you want. Cupscale also does not have other image processing abilities like chaiNNer does, such as adjusting contrast.
Cupscale and IEU are also seemingly no longer maintained at the moment, while chaiNNer is being actively worked on still.
Wouldn't this make it more difficult to do things?
- In a way, yes. Similarly to how programming your own script to do this stuff is more difficult, chaiNNer will also be a bit more difficult than simply dragging and dropping an image and messing with some sliders and pressing an upscale button. However, this gives you a lot more flexibility in what you can do. The added complexity is really just connecting some dots together to do what you want. That doesn't sound that bad, right?
Home (`docs/Home.md`)
Welcome to the ChaiNNer Wiki !
This wiki serves as a comprehensive guide to ChaiNNer, a visual programming language and development environment for chaining image processing tasks together. Below are the key sections of the documentation:
Table of Contents
[[01--Contributing]] - Overview of how to contribute to the ChaiNNer project, with setup instructions and project structure.
[[02--Data-Representation]] - Details on how ChaiNNer handles various data types such as images, text, and numbers, along with conventions for node implementations.
[[03--Nodes]] - Information about nodes in ChaiNNer, covering their structure, rules, and methods for implementation.
[[04--Navi]] - Overview of the Navi type system used for static validation and real-time feedback in ChaiNNer.
[[05--CLI]] - Instructions for operating ChaiNNer in command-line interface mode, allowing for chain execution without a GUI.
[[06--Troubleshooting]] - Solutions to common issues encountered while using ChaiNNer, addressing installation and runtime problems.
[[07--FAQ]] - Answers to frequently asked questions about ChaiNNer, including feature requests and usage scenarios.
Additional Resources
For more information, check out the individual sections linked above. If you have any questions or need further assistance, feel free to reach out to the community in discord or open an issue on the GitHub repository.
Thank you for visiting the ChaiNNer wiki!
--- METRICS ---
- Files Extracted: 11
- Estimated Token Budget: ~18274 tokens
- Canonical Reference: https://codewiki.google/github.com/chaiNNer-org/chaiNNer