From 6f17a4e36d9ec53c5c3d7da3e40fabf0c4da8a2d Mon Sep 17 00:00:00 2001 From: Amit Moryossef Date: Tue, 2 Jul 2024 17:23:54 +0200 Subject: [PATCH] feat(server): add utility server implementation --- README.md | 46 ++++++++++++++++++++++++++-- examples/spell_and_mouth.py | 10 ++++++ signwriting/fingerspelling/server.py | 19 ++++++++++++ signwriting/mouthing/server.py | 15 +++++++++ signwriting/server.py | 16 ++++++++++ signwriting/visualizer/server.py | 45 +++++++++++++++++++++++++++ signwriting/visualizer/visualize.py | 1 + 7 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 examples/spell_and_mouth.py create mode 100644 signwriting/fingerspelling/server.py create mode 100644 signwriting/mouthing/server.py create mode 100644 signwriting/server.py create mode 100644 signwriting/visualizer/server.py diff --git a/README.md b/README.md index 73bb2ad..30b7008 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,9 @@ signwriting_to_image(fsw) ![AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S20544510x500S10019476x475](signwriting/visualizer/test_assets/AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S20544510x500S10019476x475.png) +To use the visualizer with the server, you can hit: +https://sign.mt/api/signwriting/visualizer?fsw=M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475 + ### `signwriting.utils` This module includes general utilities that were not covered in the other modules. @@ -95,14 +98,51 @@ This module includes general utilities that were not covered in the other module This is useful for example for fingerspelling words out of individual character signs. ```python -from signwriting.utils.join_signs import join_signs +from signwriting.utils.join_signs import join_signs_vertical char_a = 'M507x507S1f720487x492' char_b = 'M507x507S14720493x485' -result_sign = join_signs(char_a, char_b) -# M500x500S1f720487x493S14720493x508 +result_sign = join_signs_vertical(char_a, char_b) +# M510x518S1f720490x481S14720496x496 +``` + + +### `signwriting.fingerspelling` + +This module is used to generate spelling data from a list of characters. + +```python +from signwriting.fingerspelling.fingerspelling import spell + +word = "Hello" # any string of characters +language = "en-us-ase-asl" # long language code, as defined in the fingerspelling README +spell(word, language) +# M515x563S11502477x437S14a20492x457S1dc20484x477S1dc20484x512S17620492x547 +``` + +To use the fingerspelling with the server, you can hit: +https://sign.mt/api/signwriting/fingerspelling?text=hello&signed_language=ase + +### `signwriting.mouthing` + +This module is used to generate SpeechWriting from spoken words. + +```python +from signwriting.mouthing.mouthing import mouth + +word = "Hello" # any string of characters, preferably valid words +language = "eng-Latn" # supported languages under "Language Support" at https://pypi.org/project/epitran/ +mouth(word, language) +# M557x518S34700443x482S35c00469x482S34400495x482S34d00521x482 ``` +Note: Installing English support for `epitran` requires extra steps, +see "Install flite" at [mouthing/README.md](signwriting/mouthing/README.md). + +To use the mouthing with the server, you can hit: +https://sign.mt/api/signwriting/mouthing?text=hello&spoken_language=eng-Latn + + ## Cite ```bibtex diff --git a/examples/spell_and_mouth.py b/examples/spell_and_mouth.py new file mode 100644 index 0000000..1a44604 --- /dev/null +++ b/examples/spell_and_mouth.py @@ -0,0 +1,10 @@ +from signwriting.fingerspelling.fingerspelling import spell +from signwriting.mouthing.mouthing import mouth +from signwriting.utils.join_signs import join_signs_vertical + +text = "Hello SignWriting List" +for word in text.split(" "): + mouthing = mouth(word, language="eng-Latn") + spelling = spell(word, language="en-us-ase-asl", vertical=False) + sign = join_signs_vertical(mouthing, spelling, spacing=5) + print(sign) diff --git a/signwriting/fingerspelling/server.py b/signwriting/fingerspelling/server.py new file mode 100644 index 0000000..e385ec7 --- /dev/null +++ b/signwriting/fingerspelling/server.py @@ -0,0 +1,19 @@ +from flask_restful import Resource, reqparse + +from signwriting.fingerspelling.fingerspelling import spell, get_chars_by + +parser = reqparse.RequestParser() +parser.add_argument('text', type=str, required=True, help='Text to fingerspell') +parser.add_argument('signed_language', type=str, required=True, help='Signed language ISO-3 code') + + +class Fingerspelling(Resource): + def get(self): + args = parser.parse_args() + + try: + chars = get_chars_by(value=args["signed_language"], category="SIGNED") + except ValueError as e: + return {"message": str(e)}, 400 + + return {"fsw": spell(args["text"], chars=chars)} diff --git a/signwriting/mouthing/server.py b/signwriting/mouthing/server.py new file mode 100644 index 0000000..7b779d6 --- /dev/null +++ b/signwriting/mouthing/server.py @@ -0,0 +1,15 @@ +from flask_restful import Resource, reqparse + +from signwriting.mouthing.mouthing import mouth + +parser = reqparse.RequestParser() +parser.add_argument('text', type=str, required=True, help='Text to mouth') +parser.add_argument('spoken_language', type=str, required=True, + help='Spoken Language code. See "Language Support" at https://pypi.org/project/epitran/') + + +class Mouthing(Resource): + def get(self): + args = parser.parse_args() + + return {"fsw": mouth(args["text"], language=args["spoken_language"])} diff --git a/signwriting/server.py b/signwriting/server.py new file mode 100644 index 0000000..8d1b789 --- /dev/null +++ b/signwriting/server.py @@ -0,0 +1,16 @@ +from flask import Flask +from flask_restful import Api + +from signwriting.fingerspelling.server import Fingerspelling +from signwriting.mouthing.server import Mouthing +from signwriting.visualizer.server import Visualizer + +app = Flask(__name__) +api = Api(app) + +api.add_resource(Visualizer, '/visualizer') +api.add_resource(Fingerspelling, '/fingerspelling') +api.add_resource(Mouthing, '/mouthing') + +if __name__ == "__main__": + app.run() diff --git a/signwriting/visualizer/server.py b/signwriting/visualizer/server.py new file mode 100644 index 0000000..717135c --- /dev/null +++ b/signwriting/visualizer/server.py @@ -0,0 +1,45 @@ +from io import BytesIO + +from flask import send_file +from flask_restful import Resource, reqparse + +from signwriting.formats.swu_to_fsw import swu2fsw +from signwriting.visualizer.visualize import RGBA, signwriting_to_image + + +def hex_color_to_rgba(hex_color: str) -> RGBA: + r = int(hex_color[0:2], 16) + g = int(hex_color[2:4], 16) + b = int(hex_color[4:6], 16) + a = int(hex_color[6:8], 16) if len(hex_color) > 6 else 255 + return r, g, b, a + + +def send_pil_image(pil_img): + img_io = BytesIO() + pil_img.save(img_io, 'PNG') + img_io.seek(0) + return send_file(img_io, mimetype='image/png') + + +parser = reqparse.RequestParser() +parser.add_argument('fsw', type=str, required=False, help='FSW String (Must be provided if swu is not provided)') +parser.add_argument('swu', type=str, required=False, help='SWU String (Must be provided if fsw is not provided)') +parser.add_argument('line', type=str, default='000000ff', help='Line color in hex format') +parser.add_argument('fill', type=str, default='ffffffff', help='Fill color in hex format') + + +class Visualizer(Resource): + def get(self): + args = parser.parse_args() + fsw = args.get("fsw") + swu = args.get("swu") + if fsw is None and swu is None: + return {"message": "No fsw or swu provided"}, 400 + + line_color = hex_color_to_rgba(args["line"]) + fill_color = hex_color_to_rgba(args["fill"]) + + sw_string = fsw if fsw is not None else swu2fsw(swu) + image = signwriting_to_image(fsw=sw_string, line_color=line_color, fill_color=fill_color) + return send_pil_image(image) diff --git a/signwriting/visualizer/visualize.py b/signwriting/visualizer/visualize.py index 0c926a2..64e12f8 100644 --- a/signwriting/visualizer/visualize.py +++ b/signwriting/visualizer/visualize.py @@ -17,6 +17,7 @@ def get_font(font_name: str) -> ImageFont.FreeTypeFont: return ImageFont.truetype(str(font_path), 30) +@lru_cache(maxsize=None) def get_symbol_size(symbol: str): font = get_font('SuttonSignWritingLine') line_id = symbol_line(key2id(symbol))