Skip to content

Commit

Permalink
Initial Alpha version completed (#4)
Browse files Browse the repository at this point in the history
Hotfix for release action
  • Loading branch information
joeyballentine committed Jan 17, 2022
1 parent affe78e commit ef64682
Show file tree
Hide file tree
Showing 205 changed files with 20,902 additions and 1,694 deletions.
File renamed without changes.
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ jobs:
with:
node-version: 14
- name: install dependencies
run: cd frontend && npm install
run: npm install
- name: build
run: cd frontend && npm run make
run: npm run make
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ on:
- 'v*'

# Triggers on creation of a release
release:
types:
- created
# release:
# types:
# - created

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand All @@ -32,8 +32,8 @@ jobs:
with:
node-version: 14
- name: install dependencies
run: cd frontend && npm install
run: npm install
- name: publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd frontend && npm run publish
run: npm run publish
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ typings/
.webpack/

# Electron-Forge
out/
out/
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# chaiNNer

![GitHub Latest Release](https://img.shields.io/github/v/release/joeyballentine/chaiNNer) ![GitHub Total Downloads](https://img.shields.io/github/downloads/joeyballentine/chaiNNer/total) ![License](https://img.shields.io/github/license/joeyballentine/chaiNNer) ![Discord](https://img.shields.io/discord/930865462852591648?label=Discord&logo=Discord&logoColor=white)

<p align="center">
<img src="src/public/chaiNNer screenshot.png" width="600" />
</p>

A flowchart/node-based image processing GUI aimed at making chaining image processing tasks (especially those done by neural networks) easy, intuitive, and customizable.

No existing GUI gives you the level of customization of your image processing workflow that chaiNNer does. 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.
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](https://discord.gg/pzvAKPKyHM)

## Installation

Expand All @@ -15,3 +23,59 @@ The only dependency you need to have installed already is Python 3.7-3.9. All ot
## GPU Support

Currently, chaiNNer's neural network support (via PyTorch) only supports Nvidia GPUs. There is currently no plan to support pre-compiled `.exe`s for NCNN processing. PyTorch also does not support GPU processing on MacOS.

## Planned Features

**Embedded Python**

> I'm currently figuring out the best way to add this in. There are standalone python binaries for every platform that I plan on supporting. I am still just trying to figure out whether it should be downloaded and installed to on first run, or if all that should be done in the build action and bundled with the installer.
**NCNN**

> Once the python api for NCNN supports GPU, I will be adding the ability to convert from PyTorch to TorchScript to ONNX to NCNN. It'll be a bit convoluted but it'll allow AMD support I think
**PIL & Wand**

> I do plan on adding support for PIL and Wand for image processing.
**Batch Processing**

> I am waiting to add this until the node-graph library I use supports nested flows (which is coming relatively soon). The way I will be doing this will be similar to how for loops work, in that you will have iterator panels that will iterate over some sort of loaded array of items (i.e. folder input or frames of a video)
**Undo History, Copy & Paste**

> For now I am having difficulty adding these in. I plan on revisiting this later after I am forced to refactor my implementation due to the node-graph library I use releasing breaking changes soon.
**Drag and Drop Images**

> This is planned, ideally for both dragging into the file selection box and onto the window to make a new image read node
**Presets**

> Some things that are common tasks should have presets you can drag in, that are basically just multiple nodes packaged together
**More SR Networks, More Image Processing Libraries**

> What the title says
**Live Updating**

> This is something that will be a bit complex to do, but basically I'd like to have a mode where it constantly is running and refreshing on any node change, and displays previews of each node
## FAQ

**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 it can do, as many features like chaining or interpolating models are hardcoded in 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, just do not exist in Cupscale, for example. 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.
**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 and 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?
**What platforms are supported?**

> Windows, Linux, and MacOS are all supported by chaiNNer. However, MacOS currently lacks GPU support for pytorch, so I highly recommend using another OS if you need that functionality.
12 changes: 0 additions & 12 deletions backend/build.sh

This file was deleted.

79 changes: 41 additions & 38 deletions backend/nodes/node_factory.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
from typing import Callable, Dict

from .node_base import NodeBase

from sanic.log import logger


# Implementation based on https://medium.com/@geoffreykoh/implementing-the-factory-pattern-via-dynamic-registry-and-python-decorators-479fc1537bbe
class NodeFactory:
"""The factory class for creating nodes"""

registry = {}
""" Internal registry for available nodes """

@classmethod
def create_node(cls, category: str, name: str) -> NodeBase:
"""Factory command to create the node"""

node_class = cls.registry[category][name]
node = node_class()
logger.info(f"Created {category}, {name} node")
return node

@classmethod
def register(cls, category: str, name: str) -> Callable:
def inner_wrapper(wrapped_class: NodeBase) -> Callable:
if category not in cls.registry:
cls.registry[category] = {}
if name in cls.registry[category]:
logger.warning(f"Node {name} already exists. Will replace it")
cls.registry[category][name] = wrapped_class
return wrapped_class

return inner_wrapper

@classmethod
def get_registry(cls) -> Dict:
return cls.registry
import sys
from typing import Callable, Dict

sys.path.append("..")

from sanic_server.sanic.log import logger

from .node_base import NodeBase


# Implementation based on https://medium.com/@geoffreykoh/implementing-the-factory-pattern-via-dynamic-registry-and-python-decorators-479fc1537bbe
class NodeFactory:
"""The factory class for creating nodes"""

registry = {}
""" Internal registry for available nodes """

@classmethod
def create_node(cls, category: str, name: str) -> NodeBase:
"""Factory command to create the node"""

node_class = cls.registry[category][name]
node = node_class()
logger.info(f"Created {category}, {name} node")
return node

@classmethod
def register(cls, category: str, name: str) -> Callable:
def inner_wrapper(wrapped_class: NodeBase) -> Callable:
if category not in cls.registry:
cls.registry[category] = {}
if name in cls.registry[category]:
logger.warning(f"Node {name} already exists. Will replace it")
cls.registry[category][name] = wrapped_class
return wrapped_class

return inner_wrapper

@classmethod
def get_registry(cls) -> Dict:
return cls.registry
6 changes: 5 additions & 1 deletion backend/nodes/numpy_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
Nodes that provide functionality for numpy array manipulation
"""

import sys
from typing import List

import cv2
import numpy as np
from sanic.log import logger

sys.path.append("..")

from sanic_server.sanic.log import logger

from .node_base import NodeBase
from .node_factory import NodeFactory
Expand Down
6 changes: 5 additions & 1 deletion backend/nodes/opencv_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
"""

import os
import sys

import cv2
import numpy as np
from sanic.log import logger

sys.path.append("..")

from sanic_server.sanic.log import logger

from .node_base import NodeBase
from .node_factory import NodeFactory
Expand Down
129 changes: 67 additions & 62 deletions backend/nodes/properties/inputs/file_inputs.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,67 @@
from typing import Dict, List

from .generic_inputs import DropDownInput


def FileInput(
input_type: str, label: str, accepts: List[str], filetypes: List[str]
) -> Dict:
""" Input for submitting a local file """
return {
"type": f"file::{input_type}",
"label": label,
"accepts": None,
"filetypes": filetypes,
}


def ImageFileInput() -> Dict:
""" Input for submitting a local image file """
return FileInput(
"image", "Image File", None, ["png", "jpg", "jpeg", "gif", "tiff", "webp"]
)


def PthFileInput() -> Dict:
""" Input for submitting a local .pth file """
return FileInput("pth", "Pretrained Model", None, ["pth"])


def DirectoryInput() -> Dict:
""" Input for submitting a local directory """
return FileInput("directory", "Directory", None, ["directory"])


def ImageExtensionDropdown() -> Dict:
""" Input for selecting file type from dropdown """
return DropDownInput(
"image-extensions",
"Image Extension",
[
{
"option": "PNG",
"value": "png",
},
{
"option": "JPG",
"value": "jpg",
},
{
"option": "GIF",
"value": "gif",
},
{
"option": "TIFF",
"value": "tiff",
},
{
"option": "WEBP",
"value": "webp",
},
],
)
from typing import Dict, List

from .generic_inputs import DropDownInput


def FileInput(
input_type: str, label: str, accepts: List[str], filetypes: List[str]
) -> Dict:
""" Input for submitting a local file """
return {
"type": f"file::{input_type}",
"label": label,
"accepts": None,
"filetypes": filetypes,
}


def ImageFileInput() -> Dict:
""" Input for submitting a local image file """
return FileInput(
"image", "Image File", None, ["png", "jpg", "jpeg", "gif", "tiff", "webp"]
)


def PthFileInput() -> Dict:
""" Input for submitting a local .pth file """
return FileInput("pth", "Pretrained Model", None, ["pth"])


def TorchFileInput() -> Dict:
""" Input for submitting a local .pth or .pt file """
return FileInput("pth", "Pretrained Model", None, ["pth", "pt"])


def DirectoryInput() -> Dict:
""" Input for submitting a local directory """
return FileInput("directory", "Directory", None, ["directory"])


def ImageExtensionDropdown() -> Dict:
""" Input for selecting file type from dropdown """
return DropDownInput(
"image-extensions",
"Image Extension",
[
{
"option": "PNG",
"value": "png",
},
{
"option": "JPG",
"value": "jpg",
},
{
"option": "GIF",
"value": "gif",
},
{
"option": "TIFF",
"value": "tiff",
},
{
"option": "WEBP",
"value": "webp",
},
],
)
8 changes: 8 additions & 0 deletions backend/nodes/properties/inputs/pytorch_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ def ModelInput() -> Any:
"type": "pytorch::model",
"label": "Loaded Model",
}


def TorchScriptInput() -> Any:
""" Input a JIT traced model """
return {
"type": "pytorch::torchscript",
"label": "Traced Model",
}
Loading

0 comments on commit ef64682

Please sign in to comment.