diff --git a/.gitignore b/.gitignore
index b6e4761..cf082fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,8 @@
+backend/app/models/
+
+# VS Code
+.vscode/
+
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
diff --git a/backend/app/artify.py b/backend/app/artify.py
new file mode 100644
index 0000000..a52f738
--- /dev/null
+++ b/backend/app/artify.py
@@ -0,0 +1,40 @@
+import numpy as np
+import tensorflow as tf
+from PIL import Image
+
+def wrap_frozen_graph(graph_def, inputs, outputs, print_graph=False):
+ def _imports_graph_def():
+ tf.compat.v1.import_graph_def(graph_def, name="")
+
+ wrapped_import = tf.compat.v1.wrap_function(_imports_graph_def, [])
+ import_graph = wrapped_import.graph
+
+ layers = [op.name for op in import_graph.get_operations()]
+ if print_graph == True:
+ for layer in layers:
+ print(layer)
+ return wrapped_import.prune(
+ tf.nest.map_structure(import_graph.as_graph_element, inputs),
+ tf.nest.map_structure(import_graph.as_graph_element, outputs))
+
+def load_artist_graph(artist, model_path):
+ print(f"Loading model for {artist}.")
+ try:
+ with tf.io.gfile.GFile(model_path / artist / "frozen_model.pb", "rb") as f:
+ graph_def = tf.compat.v1.GraphDef()
+ loaded = graph_def.ParseFromString(f.read())
+ frozen_func = wrap_frozen_graph(graph_def, ["placeholder/photo:0"], ["decoder/sub:0"])
+ except FileNotFoundError:
+ def frozen_func(x):
+ return x
+ return frozen_func
+
+def artify(image, model):
+ image = image.convert("RGB")
+ np_image = np.asarray(image)
+ input_image = (
+ tf.expand_dims(tf.constant(np_image, dtype=tf.float32), 0) / 127.5 - 1.0
+ )
+ artified = (model(input_image)[0] + 1) * 127.5
+ artified = Image.fromarray(tf.squeeze(artified, 0).numpy().astype(np.uint8))
+ return artified
\ No newline at end of file
diff --git a/backend/app/config.py b/backend/app/config.py
index 275ae52..305d3e6 100644
--- a/backend/app/config.py
+++ b/backend/app/config.py
@@ -3,6 +3,22 @@ class Config:
["jpg", "png", "jpeg", "tiff", "bmp", "gif", "ppm", "pgm", "tif", "svg"]
)
FONT_FOLDER = "fonts"
+ MODEL_FOLDER = "models"
+ ARTISTS = [
+ "cezanne",
+ "monet",
+ "el-greco",
+ "gauguin",
+ "kandinsky",
+ "kirchner",
+ "morisot",
+ "munch",
+ "peploe",
+ "picasso",
+ "pollock",
+ "roerich",
+ "van-gogh",
+ ]
class ProdConfig(Config):
diff --git a/backend/app/run.py b/backend/app/run.py
index 8355bbf..c0d13d7 100644
--- a/backend/app/run.py
+++ b/backend/app/run.py
@@ -7,9 +7,12 @@
from flask_talisman import Talisman
from PIL import Image
from pathlib import Path
-from asciify import ASCIIfy
from io import BytesIO
import base64
+import tensorflow as tf
+
+import asciify
+import artify
app = Flask(__name__)
app.config.from_object("config.DevConfig")
@@ -17,6 +20,13 @@
Talisman(app)
api = Api(app)
+model_selection = dict(
+ [
+ (artist, artify.load_artist_graph(artist, Path(app.config["MODEL_FOLDER"])))
+ for artist in app.config["ARTISTS"]
+ ]
+)
+
def allowed_file(filename):
"""
@@ -29,6 +39,13 @@ def allowed_file(filename):
)
+def pil_to_base64_str(img):
+ data = BytesIO()
+ img.save(data, "JPEG")
+ data64 = base64.b64encode(data.getvalue())
+ return data64
+
+
class Home(Resource):
"""
The REST-Api response for the main directory.
@@ -62,7 +79,7 @@ def get(self):
}
-class ASCIIfy_Image(Resource):
+class ASCIIfyImage(Resource):
"""
This class will handle the request on sending an image to the server.
"""
@@ -75,11 +92,10 @@ def post(self):
"image", type=werkzeug.datastructures.FileStorage, location="files"
)
parser.add_argument("debug", required=False, type=int)
+ parser.add_argument("resize", required=False, type=int, default=-1)
parser.add_argument("fontsize", required=True, type=int)
parser.add_argument("font", required=True, type=str)
- parser.add_argument("resize", required=True, type=int)
args = parser.parse_args()
- print(args)
if args["debug"] is not None and args["debug"] == 1:
print(args)
@@ -89,32 +105,100 @@ def post(self):
image = Image.open(file)
# asciify it:
- asciified = ASCIIfy(
+ asciified = asciify.ASCIIfy(
np.array(image),
args["resize"],
str(Path(app.config["FONT_FOLDER"], f"{args['font']}.ttf")),
args["fontsize"],
1,
)
+ data64 = pil_to_base64_str(asciified)
+
+ return Response(
+ response=data64.decode("utf-8"), status=200, mimetype="image/jpeg"
+ )
+
+
+class Styles(Resource):
+ """
+ returns a list of fonts
+ """
+
+ def get(self):
+ """
+ Return the list of available fonts.
+ """
+ return {"styles": [artist.title() for artist in app.config["ARTISTS"]]}
+
+
+class ArtifyImage(Resource):
+ def post(self):
+ parser = reqparse.RequestParser()
+
+ # we expect an image to be send, if not send message to the client
+ parser.add_argument(
+ "image", type=werkzeug.datastructures.FileStorage, location="files"
+ )
+ parser.add_argument(
+ "style", required=True, type=str, choices=app.config["ARTISTS"]
+ )
+ args = parser.parse_args()
+
+ file = request.files["image"]
+ image = Image.open(file)
+ artified = artify.artify(image, model_selection[args["style"]])
+ data64 = pil_to_base64_str(artified)
+
+ return Response(
+ response=data64.decode("utf-8"), status=200, mimetype="image/jpeg"
+ )
+
+
+class BeautifyImage(Resource):
+ def post(self):
+ parser = reqparse.RequestParser()
+
+ # we expect an image to be send, if not send message to the client
+ parser.add_argument(
+ "image", type=werkzeug.datastructures.FileStorage, location="files"
+ )
+ parser.add_argument("debug", required=False, type=int)
+ parser.add_argument("resize", required=False, type=int, default=-1)
+ parser.add_argument("fontsize", required=True, type=int)
+ parser.add_argument("font", required=False, type=str)
+ parser.add_argument(
+ "style", required=False, type=str, choices=app.config["ARTISTS"]
+ )
+ args = parser.parse_args()
+
+ file = request.files["image"]
+ image = Image.open(file)
+
+ if args["style"] is not None:
+ image = artify.artify(image, model_selection[args["style"]])
- data = BytesIO()
- asciified.save(data, "JPEG")
- data64 = base64.b64encode(data.getvalue())
+ if args["font"] is not None:
+ image = asciify.ASCIIfy(
+ np.array(image),
+ args["resize"],
+ str(Path(app.config["FONT_FOLDER"], f"{args['font']}.ttf")),
+ args["fontsize"],
+ 1,
+ )
- #_, cv_image = cv2.imencode(
- # ".jpeg", np.array(asciified)[..., ::-1]
- #)
+ data64 = pil_to_base64_str(image)
- # headers = {
- # 'Content-Type': "image/jpeg", # This is important
- # }
- # why?
- return Response(response=data64.decode('utf-8'), status=200, mimetype="image/jpeg")
+ return Response(
+ response=data64.decode("utf-8"), status=200, mimetype="image/jpeg"
+ )
api.add_resource(Home, "/api/home")
api.add_resource(Fonts, "/api/fonts")
-api.add_resource(ASCIIfy_Image, "/api/asciify")
+api.add_resource(ASCIIfyImage, "/api/asciify")
+api.add_resource(Styles, "/api/styles")
+api.add_resource(ArtifyImage, "/api/artify")
+api.add_resource(BeautifyImage, "/api/beautify")
# app.run(host, port): Start flask server by specifying host and port
if __name__ == "__main__":
diff --git a/backend/requirements.txt b/backend/requirements.txt
index eadd0ea..7918d33 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -1,8 +1,9 @@
opencv-python~=4.5
-numpy~=1.20
+numpy~=1.19
tqdm~=4.60
Pillow~=8.2
flask~=1.1
flask-cors~=3.0
flask-restful~=0.3.8
-flask-talisman~=0.7.0
\ No newline at end of file
+flask-talisman~=0.7.0
+tensorflow~=2.5
\ No newline at end of file
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 123fad7..7973fd5 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11499,6 +11499,11 @@
"@vue/shared": "3.0.11"
}
},
+ "vue-class-component": {
+ "version": "7.2.6",
+ "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz",
+ "integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w=="
+ },
"vue-cli-plugin-style-resources-loader": {
"version": "0.1.5",
"resolved": "https://registry.npm.taobao.org/vue-cli-plugin-style-resources-loader/download/vue-cli-plugin-style-resources-loader-0.1.5.tgz",
@@ -11681,6 +11686,14 @@
"resolved": "https://registry.npmjs.org/vue-next-select/-/vue-next-select-2.1.1.tgz",
"integrity": "sha512-cVu4VN2A3YSP2EZZ9MJSVYAgo/NPuzxZ7GJWEhT0wArJIKl9a7RwLWg2uCwnLcvdqWoMvIFsY0RnH1oTe0sVXA=="
},
+ "vue-property-decorator": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-8.5.1.tgz",
+ "integrity": "sha512-O6OUN2OMsYTGPvgFtXeBU3jPnX5ffQ9V4I1WfxFQ6dqz6cOUbR3Usou7kgFpfiXDvV7dJQSFcJ5yUPgOtPPm1Q==",
+ "requires": {
+ "vue-class-component": "^7.1.0"
+ }
+ },
"vue-round-slider": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-round-slider/-/vue-round-slider-1.0.1.tgz",
@@ -11689,6 +11702,15 @@
"round-slider": "^1.6.1"
}
},
+ "vue-slider-component": {
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.11.tgz",
+ "integrity": "sha512-2YyJW6TFnYk5FUvqQLvZcCJ+hthBXB819qNHtwnEUyDbOcTXV0n3Ou1ZphOi5FX9phlQIiC2NvjLuRAVmNq+Zw==",
+ "requires": {
+ "core-js": "^3.6.5",
+ "vue-property-decorator": "^8.0.0"
+ }
+ },
"vue-style-loader": {
"version": "4.1.3",
"resolved": "https://registry.npm.taobao.org/vue-style-loader/download/vue-style-loader-4.1.3.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index e6d6c9f..83c6f19 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -22,6 +22,7 @@
"vue": "^3.0.0",
"vue-next-select": "^2.1.1",
"vue-round-slider": "^1.0.1",
+ "vue-slider-component": "^3.2.11",
"vue-upload-drop-images": "^1.0.4",
"vue3-slider": "^1.6.2",
"vuetify": "^3.0.0-alpha.0"
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index f93f825..909e259 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,7 +1,7 @@
ASCIIfy
+ ArtSCIIfy