Spaces:
Sleeping
Sleeping
from interactive_pipe import interactive_pipeline, interactive, Image | |
from typing import Tuple | |
import numpy as np | |
# Helper functions | |
# ---------------- | |
def flip_image(img, flip=True, mirror=True): | |
img = img[::-1] if flip else img | |
img = img[:, ::-1] if mirror else img | |
return img | |
def get_crop(img, pos_x, pos_y, crop_size=0.1): | |
c_size = int(crop_size * img.shape[0]) | |
crop_x = (int(pos_x * img.shape[1]), int((pos_x) * img.shape[1]) + c_size) | |
crop_y = (int(pos_y * img.shape[0]), int((pos_y) * img.shape[0]) + c_size) | |
return crop_x, crop_y | |
# Processing blocks | |
# ----------------- | |
def generate_feedback_ribbon() -> Tuple[np.ndarray, np.ndarray]: | |
"""Generate green and red ribbons for feedback""" | |
flat_array = np.ones((800, 12, 3)) | |
colors = [[0., 1., 0.], [1., 0., 0.]] | |
ribbons = [flat_array*np.array(col)[None, None, :] for col in colors] | |
return ribbons[0], ribbons[1] | |
DIFFICULY = {"easy": 0.18, "medium": 0.1, "hard": 0.05} | |
DIFFICULY_LEVELS = list(DIFFICULY.keys()) | |
def generate_random_puzzle( | |
seed: int = 45, | |
difficulty: str = DIFFICULY_LEVELS[0], | |
context: dict = {} | |
): | |
"""Generate random puzzle configuration and store in context. | |
Configuration = 2D position and flip/mirror. | |
Freeze seed for reproducibility. | |
""" | |
np.random.seed(seed) | |
pos_x, pos_y = np.random.uniform(0.2, 0.8, 2) | |
context["puzzle_pos"] = (pos_x, pos_y) | |
context["puzzle_flip_mirror"] = np.random.choice([True, False], 2) | |
context["puzzle_piece_size"] = DIFFICULY.get(difficulty, 0.18) | |
def create_puzzle( | |
img: np.ndarray, | |
intensity: float = 0.4, | |
context: dict = {} | |
) -> Tuple[np.ndarray, np.ndarray]: | |
"""Extract puzzle piece from image. Make a dark hole where the """ | |
out = img.copy() | |
x_gt, y_gt = context["puzzle_pos"] | |
flip_gt, mirror_gt = context["puzzle_flip_mirror"] | |
cs_x, cs_y = get_crop( | |
img, x_gt, y_gt, crop_size=context["puzzle_piece_size"]) | |
crop = img[cs_y[0]:cs_y[1], cs_x[0]:cs_x[1], ...] | |
out[cs_y[0]:cs_y[1], cs_x[0]:cs_x[1]] = intensity*crop | |
crop = flip_image(crop, flip=flip_gt, mirror=mirror_gt) | |
return out, crop | |
def flip_mirror_piece( | |
piece: np.ndarray, | |
flip: bool = False, | |
mirror: bool = False, | |
context: dict = {} | |
) -> np.ndarray: | |
"""Flip and/or mirror the puzzle piece.""" | |
context["user_flip_mirror"] = (flip, mirror) | |
return flip_image(piece.copy(), flip=flip, mirror=mirror) | |
def place_puzzle( | |
puzzle: np.ndarray, | |
piece: np.ndarray, | |
pos_x: float = 0.5, | |
pos_y: float = 0.5, | |
context: dict = {} | |
) -> np.ndarray: | |
"""Place the puzzle piece at the user-defined position.""" | |
out = puzzle.copy() | |
context["user_pos"] = (pos_x, pos_y) | |
cp_x, cp_y = get_crop( | |
img, pos_x, pos_y, crop_size=context["puzzle_piece_size"]) | |
out[cp_y[0]:cp_y[1], cp_x[0]:cp_x[1]] = piece | |
return out | |
TOLERANCES = {"low": 0.01, "medium": 0.02, "high": 0.05} | |
TOLERANCE_LEVELS = list(TOLERANCES.keys()) | |
def check_puzzle(tolerance: str = "low", context: dict = {}) -> None: | |
"""Check if the user placed the puzzle piece correctly.""" | |
x_gt, y_gt = context["puzzle_pos"] | |
flip_gt, mirror_gt = context["puzzle_flip_mirror"] | |
x, y = context["user_pos"] | |
flip, mirror = context["user_flip_mirror"] | |
check_pos = np.allclose([x_gt, y_gt], [x, y], | |
atol=TOLERANCES.get(tolerance, 0.01)) | |
check_flip_mirror = (flip_gt == flip) and (mirror_gt == mirror) | |
success = check_pos and check_flip_mirror | |
context["success"] = success | |
def display_feedback( | |
puzzle: np.ndarray, | |
ok_ribbon: np.ndarray, | |
nok_ribbon: np.ndarray, | |
context: dict = {} | |
) -> np.ndarray: | |
success = context["success"] | |
ribbon = ok_ribbon if success else nok_ribbon | |
out = np.hstack([puzzle, ribbon[:puzzle.shape[0], ...]]) | |
return out | |
# pipeline definition | |
# ------------------- | |
def captcha_pipe(inp): | |
ok_ribbon, nok_ribbon = generate_feedback_ribbon() | |
generate_random_puzzle() | |
puzzle, puzzle_piece = create_puzzle(inp) | |
puzzle_piece = flip_mirror_piece(puzzle_piece) | |
puzzle = place_puzzle(puzzle, puzzle_piece) | |
check_puzzle() | |
puzzle = display_feedback(puzzle, ok_ribbon, nok_ribbon) | |
return puzzle | |
if __name__ == "__main__": | |
import argparse | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-b", "--backend", default="gradio", | |
choices=["gradio", "qt"], type=str) | |
args = parser.parse_args() | |
markdown_description = "# Code to build this app on gradio \n" | |
markdown_description += "```python\n"+open(__file__, 'r').read()+"```" | |
img = Image.load_image("sample.jpg") | |
captcha_pipe_interactive = interactive_pipeline( | |
gui=args.backend, | |
cache=True, | |
markdown_description=markdown_description | |
)(captcha_pipe) | |
captcha_pipe_interactive(img) | |