Spaces:
Runtime error
Runtime error
v1
Browse files- .gitattributes +1 -0
- app.py +593 -0
- image/map.png +3 -0
- image/music_sheet.png +3 -0
- image/trade_directories.png +3 -0
- requirements.txt +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
app.py
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
import cv2
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import numpy as np
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from pylena.scribo import line_detector
|
| 9 |
+
from pylena.scribo import VSegment, LSuperposition
|
| 10 |
+
from pylena.scribo import e_segdet_preprocess, e_segdet_process_extraction, e_segdet_process_tracking, e_segdet_process_traversal_mode
|
| 11 |
+
import time
|
| 12 |
+
|
| 13 |
+
from typing import List, Tuple, Dict
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# Define all the default values
|
| 17 |
+
default_min_len = 10
|
| 18 |
+
default_preprocess = "NONE"
|
| 19 |
+
default_tracker = "KALMAN"
|
| 20 |
+
default_traversal_mode = "HORIZONTAL_VERTICAL"
|
| 21 |
+
default_extraction_type = "BINARY"
|
| 22 |
+
default_negate_image = False
|
| 23 |
+
default_dyn = 0.6
|
| 24 |
+
default_size_mask = 11
|
| 25 |
+
default_double_exponential_alpha = 0.6
|
| 26 |
+
default_simple_moving_average_memory = 30.0
|
| 27 |
+
default_exponential_moving_average_memory = 16.0
|
| 28 |
+
default_one_euro_beta = 0.007
|
| 29 |
+
default_one_euro_mincutoff = 1.0
|
| 30 |
+
default_one_euro_dcutoff = 1.0
|
| 31 |
+
default_bucket_size = 32
|
| 32 |
+
default_nb_values_to_keep = 30
|
| 33 |
+
default_discontinuity_relative = 0
|
| 34 |
+
default_discontinuity_absolute = 0
|
| 35 |
+
default_minimum_for_fusion = 15
|
| 36 |
+
default_default_sigma_position = 2
|
| 37 |
+
default_default_sigma_thickness = 2
|
| 38 |
+
default_default_sigma_luminosity = 57
|
| 39 |
+
default_min_nb_values_sigma = 10
|
| 40 |
+
default_sigma_pos_min = 1.0
|
| 41 |
+
default_sigma_thickness_min = 0.64
|
| 42 |
+
default_sigma_luminosity_min = 13.0
|
| 43 |
+
default_gradient_threshold = 30
|
| 44 |
+
default_llumi = 225
|
| 45 |
+
default_blumi = 225
|
| 46 |
+
default_ratio_lum = 1.0
|
| 47 |
+
default_max_thickness = 100
|
| 48 |
+
default_threshold_intersection = 0.8
|
| 49 |
+
default_remove_duplicates = True
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def get_json_extract(full_json: dict) -> dict:
|
| 53 |
+
"""Extract 5 samples from a json dictionnary
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
full_json (dict): The full json dictionnary
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
dict: A sub sample of the full json dictionnary containing the first 5 samples.
|
| 60 |
+
"""
|
| 61 |
+
extract_json = {}
|
| 62 |
+
|
| 63 |
+
count = 5
|
| 64 |
+
for key, value in full_json.items():
|
| 65 |
+
extract_json[key] = value
|
| 66 |
+
|
| 67 |
+
count -= 1
|
| 68 |
+
if count == 0:
|
| 69 |
+
break
|
| 70 |
+
|
| 71 |
+
return extract_json
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def save_json(data: dict, path: Path) -> None:
|
| 75 |
+
"""Save a json dictionnary to a file
|
| 76 |
+
|
| 77 |
+
Args:
|
| 78 |
+
data (dict): The json dictionnary to save
|
| 79 |
+
path (Path): The path to the file
|
| 80 |
+
"""
|
| 81 |
+
with open(path, "w") as f:
|
| 82 |
+
json.dump(data, f)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def get_new_white(height: int, width: int) -> np.ndarray:
|
| 86 |
+
"""Create a new white image
|
| 87 |
+
|
| 88 |
+
Args:
|
| 89 |
+
height (int): The height of the image
|
| 90 |
+
width (int): The width of the image
|
| 91 |
+
|
| 92 |
+
Returns:
|
| 93 |
+
np.ndarray: The new white image
|
| 94 |
+
"""
|
| 95 |
+
img = np.ones((height, width, 3), dtype=np.uint8) * 255
|
| 96 |
+
return img
|
| 97 |
+
|
| 98 |
+
# fmt: off
|
| 99 |
+
|
| 100 |
+
def generate_vector_output(img_rgb_input: np.ndarray, lines: List[VSegment], lines_colors: Dict[int, np.ndarray]):
|
| 101 |
+
"""Generate the vector output using the VSegment list
|
| 102 |
+
|
| 103 |
+
Args:
|
| 104 |
+
img_rgb_input (np.ndarray): Input image with 3 channels
|
| 105 |
+
lines (List[VSegment]): The identified lines in the image
|
| 106 |
+
lines_colors (Dict[int, np.ndarray]): Dictionary containing the color for each line according to their label
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
Tuple[np.ndarray, np.ndarray, Path, dict]: The vector output
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
def draw_lines(img: np.ndarray, lines: List[VSegment]) -> np.ndarray:
|
| 113 |
+
"""Draw the lines as vector on the image
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
img (np.ndarray): The image to draw on
|
| 117 |
+
lines (List[VSegment]): The lines to draw
|
| 118 |
+
|
| 119 |
+
Returns:
|
| 120 |
+
np.ndarray: The image with the lines drawn on it
|
| 121 |
+
"""
|
| 122 |
+
for line in lines:
|
| 123 |
+
cv2.line(img, (line.x0, line.y0), (line.x1, line.y1), lines_colors[line.label].tolist(), 2)
|
| 124 |
+
return img
|
| 125 |
+
|
| 126 |
+
def get_vector_json(lines: List[VSegment]) -> dict:
|
| 127 |
+
"""Generate the json dictionnary containing the vector output
|
| 128 |
+
|
| 129 |
+
Args:
|
| 130 |
+
lines (List[VSegment]): The lines to draw
|
| 131 |
+
|
| 132 |
+
Returns:
|
| 133 |
+
dict: The json dictionnary containing the vector output
|
| 134 |
+
"""
|
| 135 |
+
ret = {}
|
| 136 |
+
for line in lines:
|
| 137 |
+
ret[str(line.label)] = {"x0": line.x0, "y0": line.y0, "x1": line.x1, "y1": line.y1}
|
| 138 |
+
return ret
|
| 139 |
+
|
| 140 |
+
img_empty = get_new_white(img_rgb_input.shape[0], img_rgb_input.shape[1])
|
| 141 |
+
|
| 142 |
+
out_vector_over_img = draw_lines(img_rgb_input.copy(), lines)
|
| 143 |
+
out_vector_label_img = draw_lines(img_empty, lines)
|
| 144 |
+
|
| 145 |
+
out_vector_file = Path("vector_output_full.json")
|
| 146 |
+
out_vector_file_full = get_vector_json(lines)
|
| 147 |
+
save_json(out_vector_file_full, out_vector_file)
|
| 148 |
+
|
| 149 |
+
out_vector_file_extract = get_json_extract(out_vector_file_full)
|
| 150 |
+
|
| 151 |
+
return out_vector_over_img, out_vector_label_img, out_vector_file, out_vector_file_extract,
|
| 152 |
+
|
| 153 |
+
def generate_pixel_output(img_rgb_input: np.ndarray, img_label: np.ndarray, superpositions: List[LSuperposition], lines_colors: Dict[int, np.ndarray]):
|
| 154 |
+
"""Generate the pixel output using the LSuperposition list and the img_label
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
img_rgb_input (np.ndarray): Input image with 3 channels
|
| 158 |
+
img_label (np.ndarray): The labelized image
|
| 159 |
+
superpositions (List[LSuperposition]): The identified superpositions in the image
|
| 160 |
+
lines_colors (Dict[int, np.ndarray]): Dictionary containing the color for each line according to their label
|
| 161 |
+
|
| 162 |
+
Returns:
|
| 163 |
+
Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, Path, Path, dict]: The pixel output
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def draw_pixels(img: np.ndarray, img_label: np.ndarray, lines_colors: Dict[int, np.ndarray]) -> np.ndarray:
|
| 167 |
+
"""Draw the pixels as vector on the image
|
| 168 |
+
|
| 169 |
+
Args:
|
| 170 |
+
img (np.ndarray): The image to draw on
|
| 171 |
+
img_label (np.ndarray): The labelized image
|
| 172 |
+
lines_colors (Dict[int, np.ndarray]): Dictionary containing the color for each line according to their label
|
| 173 |
+
|
| 174 |
+
Returns:
|
| 175 |
+
np.ndarray: The image with the pixels drawn on it
|
| 176 |
+
"""
|
| 177 |
+
for x in range(img.shape[0]):
|
| 178 |
+
for y in range(img.shape[1]):
|
| 179 |
+
if img_label[x, y] != 0 and img_label[x, y] != 1:
|
| 180 |
+
img[x, y, :] = lines_colors[img_label[x, y]]
|
| 181 |
+
return img
|
| 182 |
+
|
| 183 |
+
def draw_superposition(img: np.ndarray, superpositions: List[LSuperposition], lines_colors: Dict[int, np.ndarray]) -> np.ndarray:
|
| 184 |
+
"""Draw the superpositions as vector on the image
|
| 185 |
+
|
| 186 |
+
Args:
|
| 187 |
+
img (np.ndarray): The image to draw on
|
| 188 |
+
superpositions (List[LSuperposition]): The superpositions to draw
|
| 189 |
+
lines_colors (Dict[int, np.ndarray]): Dictionary containing the color for each line according to their label
|
| 190 |
+
|
| 191 |
+
Returns:
|
| 192 |
+
np.ndarray: The image with the superpositions drawn on it
|
| 193 |
+
"""
|
| 194 |
+
for superposition in superpositions:
|
| 195 |
+
img[superposition.y, superposition.x, :] = lines_colors[1]
|
| 196 |
+
return img
|
| 197 |
+
|
| 198 |
+
def get_superposition_json(superpositions: List[LSuperposition]) -> dict:
|
| 199 |
+
"""Generate the json dictionnary containing the superposition output
|
| 200 |
+
|
| 201 |
+
Args:
|
| 202 |
+
superpositions (List[LSuperposition]): The superpositions
|
| 203 |
+
|
| 204 |
+
Returns:
|
| 205 |
+
dict: The json dictionnary containing the superposition output
|
| 206 |
+
"""
|
| 207 |
+
ret = {}
|
| 208 |
+
for superposition in superpositions:
|
| 209 |
+
key = f"{superposition.x}_{superposition.y}"
|
| 210 |
+
if not key in ret:
|
| 211 |
+
ret[key] = []
|
| 212 |
+
|
| 213 |
+
ret[key].append(superposition.label)
|
| 214 |
+
return ret
|
| 215 |
+
|
| 216 |
+
def draw_full(img: np.ndarray, img_label: np.ndarray, superpositions: List[LSuperposition], lines_colors: Dict[int, np.ndarray]):
|
| 217 |
+
"""Draw the full output (pixels and superpositions) on the image
|
| 218 |
+
|
| 219 |
+
Args:
|
| 220 |
+
img (np.ndarray): The image to draw on
|
| 221 |
+
img_label (np.ndarray): The labelized image
|
| 222 |
+
superpositions (List[LSuperposition]): The superpositions
|
| 223 |
+
lines_colors (Dict[int, np.ndarray]): Dictionary containing the color for each line according to their label
|
| 224 |
+
|
| 225 |
+
Returns:
|
| 226 |
+
np.ndarray: The image with the full output drawn on it
|
| 227 |
+
"""
|
| 228 |
+
img = draw_pixels(img, img_label, lines_colors)
|
| 229 |
+
img = draw_superposition(img, superpositions, lines_colors)
|
| 230 |
+
return img
|
| 231 |
+
|
| 232 |
+
out_pixel_full_over_img = draw_full(img_rgb_input.copy(), img_label, superpositions, lines_colors)
|
| 233 |
+
out_pixel_line_over_img = draw_pixels(img_rgb_input.copy(), img_label, lines_colors)
|
| 234 |
+
out_pixel_superposition_over_img = draw_superposition(img_rgb_input.copy(), superpositions, lines_colors)
|
| 235 |
+
|
| 236 |
+
img_empty = get_new_white(img_rgb_input.shape[0], img_rgb_input.shape[1])
|
| 237 |
+
out_pixel_full_img = draw_full(img_empty.copy(), img_label, superpositions, lines_colors)
|
| 238 |
+
out_pixel_line_img = draw_pixels(img_empty.copy(), img_label, lines_colors)
|
| 239 |
+
out_pixel_superposition_img = draw_superposition(img_empty.copy(), superpositions, lines_colors)
|
| 240 |
+
|
| 241 |
+
out_pixel_file_label = Path("pixel_output_label.npy")
|
| 242 |
+
img_label.dump(out_pixel_file_label)
|
| 243 |
+
out_pixel_file_superposition = Path("pixel_output_superposition.json")
|
| 244 |
+
out_pixel_file_superposition_full = get_superposition_json(superpositions)
|
| 245 |
+
save_json(out_pixel_file_superposition_full, out_pixel_file_superposition)
|
| 246 |
+
out_pixel_file_superposition_extract = get_json_extract(out_pixel_file_superposition_full)
|
| 247 |
+
|
| 248 |
+
return out_pixel_full_over_img, out_pixel_line_over_img, out_pixel_superposition_over_img, out_pixel_full_img, out_pixel_line_img, out_pixel_superposition_img, out_pixel_file_label, out_pixel_file_superposition, out_pixel_file_superposition_extract
|
| 249 |
+
|
| 250 |
+
def generate_output(img_input: np.ndarray, img_label: np.ndarray, superpositions: List[LSuperposition], lines: List[VSegment]):
|
| 251 |
+
"""Generate the output using the LSuperposition list and the img_label
|
| 252 |
+
|
| 253 |
+
Args:
|
| 254 |
+
img_input (np.ndarray): Input image with 1 channel
|
| 255 |
+
img_label (np.ndarray): The labelized image
|
| 256 |
+
superpositions (List[LSuperposition]): The identified superpositions in the image
|
| 257 |
+
lines (List[VSegment]): The identified lines in the image
|
| 258 |
+
|
| 259 |
+
Returns:
|
| 260 |
+
Tuple[np.ndarray, np.ndarray, Path, dict, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, Path, Path, dict]: The complete output for gradio application
|
| 261 |
+
"""
|
| 262 |
+
def get_rgb_input_img(greyscale_input_img: np.ndarray) -> np.ndarray:
|
| 263 |
+
"""Convert a greyscale image to a rgb image
|
| 264 |
+
|
| 265 |
+
Args:
|
| 266 |
+
greyscale_input_img (np.ndarray): The greyscale / 1 channel image
|
| 267 |
+
|
| 268 |
+
Returns:
|
| 269 |
+
np.ndarray: The 3 channels version of the input image
|
| 270 |
+
"""
|
| 271 |
+
rgb_input_img: np.ndarray = np.zeros((greyscale_input_img.shape[0], greyscale_input_img.shape[1], 3), dtype=np.uint8)
|
| 272 |
+
rgb_input_img[:, :, 0] = greyscale_input_img
|
| 273 |
+
rgb_input_img[:, :, 1] = greyscale_input_img
|
| 274 |
+
rgb_input_img[:, :, 2] = greyscale_input_img
|
| 275 |
+
|
| 276 |
+
return rgb_input_img
|
| 277 |
+
|
| 278 |
+
def generate_line_colors(lines: List[VSegment]) -> Dict[int, np.ndarray]:
|
| 279 |
+
"""Generate a color for each line
|
| 280 |
+
|
| 281 |
+
Args:
|
| 282 |
+
lines (List[VSegment]): The lines
|
| 283 |
+
|
| 284 |
+
Returns:
|
| 285 |
+
Dict[int, np.ndarray]: A dictionary containing the color for each line according to their label
|
| 286 |
+
"""
|
| 287 |
+
np.random.seed(0)
|
| 288 |
+
color = np.random.randint(low=0, high=255, size=(len(lines), 3))
|
| 289 |
+
|
| 290 |
+
ret = {}
|
| 291 |
+
ret[0] = np.array([0, 0, 0])
|
| 292 |
+
ret[1] = np.array([255, 0, 0])
|
| 293 |
+
for i, line in enumerate(lines):
|
| 294 |
+
ret[line.label] = color[i, :].astype(np.uint8)
|
| 295 |
+
return ret
|
| 296 |
+
|
| 297 |
+
rgb_input_img: np.ndarray = get_rgb_input_img(img_input)
|
| 298 |
+
lines_colors: Dict[int, np.ndarray] = generate_line_colors(lines)
|
| 299 |
+
|
| 300 |
+
out_vector: Tuple[np.ndarray, np.ndarray, Path, dict]
|
| 301 |
+
out_vector = generate_vector_output(rgb_input_img, lines, lines_colors)
|
| 302 |
+
|
| 303 |
+
out_pixel: Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, Path, Path, dict]
|
| 304 |
+
out_pixel = generate_pixel_output(rgb_input_img, img_label, superpositions, lines_colors)
|
| 305 |
+
|
| 306 |
+
return *out_vector, *out_pixel
|
| 307 |
+
|
| 308 |
+
def app_function(
|
| 309 |
+
greyscale_input_img,
|
| 310 |
+
min_len,
|
| 311 |
+
preprocess,
|
| 312 |
+
tracker,
|
| 313 |
+
traversal_mode,
|
| 314 |
+
extraction_type,
|
| 315 |
+
negate_image,
|
| 316 |
+
dyn,
|
| 317 |
+
size_mask,
|
| 318 |
+
double_exponential_alpha,
|
| 319 |
+
simple_moving_average_memory,
|
| 320 |
+
exponential_moving_average_memory,
|
| 321 |
+
one_euro_beta,
|
| 322 |
+
one_euro_mincutoff,
|
| 323 |
+
one_euro_dcutoff,
|
| 324 |
+
bucket_size,
|
| 325 |
+
nb_values_to_keep,
|
| 326 |
+
discontinuity_relative,
|
| 327 |
+
discontinuity_absolute,
|
| 328 |
+
minimum_for_fusion,
|
| 329 |
+
default_sigma_position,
|
| 330 |
+
default_sigma_thickness,
|
| 331 |
+
default_sigma_luminosity,
|
| 332 |
+
min_nb_values_sigma,
|
| 333 |
+
sigma_pos_min,
|
| 334 |
+
sigma_thickness_min,
|
| 335 |
+
sigma_luminosity_min,
|
| 336 |
+
gradient_threshold,
|
| 337 |
+
llumi,
|
| 338 |
+
blumi,
|
| 339 |
+
ratio_lum,
|
| 340 |
+
max_thickness,
|
| 341 |
+
threshold_intersection,
|
| 342 |
+
remove_duplicates):
|
| 343 |
+
|
| 344 |
+
img_label: np.ndarray
|
| 345 |
+
superpositions: List[LSuperposition]
|
| 346 |
+
lines: List[VSegment]
|
| 347 |
+
|
| 348 |
+
def get_enum_value(enum, value):
|
| 349 |
+
return enum.__members__[value]
|
| 350 |
+
|
| 351 |
+
t0 = time.time()
|
| 352 |
+
img_label, superpositions, lines = line_detector(
|
| 353 |
+
greyscale_input_img, "full",
|
| 354 |
+
min_len=int(min_len),
|
| 355 |
+
preprocess=get_enum_value(e_segdet_preprocess, preprocess),
|
| 356 |
+
tracker=get_enum_value(e_segdet_process_tracking, tracker),
|
| 357 |
+
traversal_mode=get_enum_value(e_segdet_process_traversal_mode, traversal_mode),
|
| 358 |
+
extraction_type=get_enum_value(e_segdet_process_extraction, extraction_type),
|
| 359 |
+
negate_image=bool(negate_image),
|
| 360 |
+
dyn=float(dyn),
|
| 361 |
+
size_mask=int(size_mask),
|
| 362 |
+
double_exponential_alpha=float(double_exponential_alpha),
|
| 363 |
+
simple_moving_average_memory=int(simple_moving_average_memory),
|
| 364 |
+
exponential_moving_average_memory=int(exponential_moving_average_memory),
|
| 365 |
+
one_euro_beta=float(one_euro_beta),
|
| 366 |
+
one_euro_mincutoff=float(one_euro_mincutoff),
|
| 367 |
+
one_euro_dcutoff=float(one_euro_dcutoff),
|
| 368 |
+
bucket_size=int(bucket_size),
|
| 369 |
+
nb_values_to_keep=int(nb_values_to_keep),
|
| 370 |
+
discontinuity_relative=int(discontinuity_relative),
|
| 371 |
+
discontinuity_absolute=int(discontinuity_absolute),
|
| 372 |
+
minimum_for_fusion=int(minimum_for_fusion),
|
| 373 |
+
default_sigma_position=int(default_sigma_position),
|
| 374 |
+
default_sigma_thickness=int(default_sigma_thickness),
|
| 375 |
+
default_sigma_luminosity=int(default_sigma_luminosity),
|
| 376 |
+
min_nb_values_sigma=int(min_nb_values_sigma),
|
| 377 |
+
sigma_pos_min=float(sigma_pos_min),
|
| 378 |
+
sigma_thickness_min=float(sigma_thickness_min),
|
| 379 |
+
sigma_luminosity_min=float(sigma_luminosity_min),
|
| 380 |
+
gradient_threshold=int(gradient_threshold),
|
| 381 |
+
llumi=int(llumi),
|
| 382 |
+
blumi=int(blumi),
|
| 383 |
+
ratio_lum=float(ratio_lum),
|
| 384 |
+
max_thickness=int(max_thickness),
|
| 385 |
+
threshold_intersection=float(threshold_intersection),
|
| 386 |
+
remove_duplicates=bool(remove_duplicates)
|
| 387 |
+
)
|
| 388 |
+
t1 = time.time()
|
| 389 |
+
|
| 390 |
+
duration = t1 - t0
|
| 391 |
+
|
| 392 |
+
outputs = generate_output(greyscale_input_img, img_label, superpositions, lines)
|
| 393 |
+
|
| 394 |
+
return duration, *outputs
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
with gr.Blocks() as app:
|
| 399 |
+
gr.Markdown("""
|
| 400 |
+
# Pylena line detection demonstration
|
| 401 |
+
|
| 402 |
+
This is a demonstration of the line detector described in the article *Linear Object Detection in Document Images using Multiple Object Tracking*
|
| 403 |
+
accepted at ICDAR 2023. The article is available at: https://arxiv.org/abs/2305.16968.
|
| 404 |
+
|
| 405 |
+
## How to use this demonstration ?
|
| 406 |
+
|
| 407 |
+
You can either upload your own (greyscale/8bit image) image or use one of the examples, then change the parameters and click on the run button.
|
| 408 |
+
|
| 409 |
+
The complete documentation is available at: http://olena.pages.lre.epita.fr/pylena/
|
| 410 |
+
""")
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
with gr.Row():
|
| 414 |
+
with gr.Column():
|
| 415 |
+
gr.Markdown("## Input")
|
| 416 |
+
|
| 417 |
+
img_input = gr.Image(type="numpy", image_mode="L", label="Greyscale input image")
|
| 418 |
+
|
| 419 |
+
with gr.Tab("Parameters"):
|
| 420 |
+
with gr.Tab("Tracking"):
|
| 421 |
+
min_len = gr.Number(label="min_len", value=default_min_len)
|
| 422 |
+
tracker = gr.Radio(label="tracker", choices=["KALMAN", "ONE_EURO", "DOUBLE_EXPONENTIAL", "LAST_INTEGRATION", "SIMPLE_MOVING_AVERAGE", "EXPONENTIAL_MOVING_AVERAGE"], value=default_tracker)
|
| 423 |
+
traversal_mode = gr.Radio(label="traversal_mode", choices=["HORIZONTAL_VERTICAL", "HORIZONTAL", "VERTICAL"], value=default_traversal_mode)
|
| 424 |
+
|
| 425 |
+
with gr.Tab("Observation extraction"):
|
| 426 |
+
blumi = gr.Number(label="blumi", value=default_blumi)
|
| 427 |
+
llumi = gr.Number(label="llumi", value=default_llumi)
|
| 428 |
+
max_thickness = gr.Number(label="max_thickness", value=default_max_thickness)
|
| 429 |
+
|
| 430 |
+
with gr.Tab("Discontinuity"):
|
| 431 |
+
discontinuity_relative = gr.Number(label="discontinuity_relative", value=default_discontinuity_relative)
|
| 432 |
+
discontinuity_absolute = gr.Number(label="discontinuity_absolute", value=default_discontinuity_absolute)
|
| 433 |
+
|
| 434 |
+
with gr.Tab("Advanced parameters"):
|
| 435 |
+
with gr.Tab("Preprocessing"):
|
| 436 |
+
preprocess = gr.Radio(label="preprocess", choices=["NONE", "Black top hat"], value=default_preprocess)
|
| 437 |
+
negate_image = gr.Checkbox(label="negate_image", value=default_negate_image)
|
| 438 |
+
dyn = gr.Number(label="dyn", value=default_dyn)
|
| 439 |
+
size_mask = gr.Number(label="size_mask", value=default_size_mask)
|
| 440 |
+
|
| 441 |
+
with gr.Tab("Tracker specific parameters"):
|
| 442 |
+
double_exponential_alpha = gr.Number(label="double_exponential_alpha", value=default_double_exponential_alpha)
|
| 443 |
+
simple_moving_average_memory = gr.Number(label="simple_moving_average_memory", value=default_simple_moving_average_memory)
|
| 444 |
+
exponential_moving_average_memory = gr.Number(label="exponential_moving_average_memory", value=default_exponential_moving_average_memory)
|
| 445 |
+
one_euro_beta = gr.Number(label="one_euro_beta", value=default_one_euro_beta)
|
| 446 |
+
one_euro_mincutoff = gr.Number(label="one_euro_mincutoff", value=default_one_euro_mincutoff)
|
| 447 |
+
one_euro_dcutoff = gr.Number(label="one_euro_dcutoff", value=default_one_euro_dcutoff)
|
| 448 |
+
|
| 449 |
+
with gr.Tab("Tracker parameters"):
|
| 450 |
+
nb_values_to_keep = gr.Number(label="nb_values_to_keep", value=default_nb_values_to_keep)
|
| 451 |
+
minimum_for_fusion = gr.Number(label="minimum_for_fusion", value=default_minimum_for_fusion)
|
| 452 |
+
|
| 453 |
+
with gr.Tab("Observation extraction"):
|
| 454 |
+
extraction_type = gr.Radio(label="extraction_type", choices=["BINARY", "GRADIENT"], value="BINARY")
|
| 455 |
+
gradient_threshold = gr.Number(label="gradient_threshold", value=default_gradient_threshold)
|
| 456 |
+
|
| 457 |
+
with gr.Tab("Observation matching"):
|
| 458 |
+
default_sigma_position = gr.Number(label="default_sigma_position", value=default_default_sigma_position)
|
| 459 |
+
default_sigma_thickness = gr.Number(label="default_sigma_thickness", value=default_default_sigma_thickness)
|
| 460 |
+
default_sigma_luminosity = gr.Number(label="default_sigma_luminosity", value=default_default_sigma_luminosity)
|
| 461 |
+
min_nb_values_sigma = gr.Number(label="min_nb_values_sigma", value=default_min_nb_values_sigma)
|
| 462 |
+
sigma_pos_min = gr.Number(label="sigma_pos_min", value=default_sigma_pos_min)
|
| 463 |
+
sigma_thickness_min = gr.Number(label="sigma_thickness_min", value=default_sigma_thickness_min)
|
| 464 |
+
sigma_luminosity_min = gr.Number(label="sigma_luminosity_min", value=default_sigma_luminosity_min)
|
| 465 |
+
|
| 466 |
+
with gr.Tab("Extraction"):
|
| 467 |
+
ratio_lum = gr.Number(label="ratio_lum", value=default_ratio_lum)
|
| 468 |
+
|
| 469 |
+
with gr.Tab("Post Processing"):
|
| 470 |
+
threshold_intersection = gr.Number(label="threshold_intersection", value=default_threshold_intersection)
|
| 471 |
+
remove_duplicates = gr.Checkbox(label="remove_duplicates", value=default_remove_duplicates)
|
| 472 |
+
|
| 473 |
+
with gr.Tab("Optimisation"):
|
| 474 |
+
bucket_size = gr.Number(label="bucket_size", value=default_bucket_size)
|
| 475 |
+
|
| 476 |
+
with gr.Column():
|
| 477 |
+
gr.Markdown("## Output")
|
| 478 |
+
|
| 479 |
+
out_duration = gr.Number(label="Line detection duration (in seconds)", value=-1, interactive=False)
|
| 480 |
+
|
| 481 |
+
with gr.Tab("Output Vector"):
|
| 482 |
+
with gr.Tab("Over input"):
|
| 483 |
+
out_vector_over_img = gr.Image(type="numpy", image_mode="RGB", interactive=False)
|
| 484 |
+
with gr.Tab("Line only"):
|
| 485 |
+
out_vector_label_img = gr.Image(type="numpy", image_mode="RGB", interactive=False)
|
| 486 |
+
with gr.Tab("File"):
|
| 487 |
+
out_vector_file = gr.File(label="Vector output full", interactive=False)
|
| 488 |
+
out_vector_file_extract = gr.Json(label="Vector sample")
|
| 489 |
+
|
| 490 |
+
with gr.Tab("Output Pixel"):
|
| 491 |
+
with gr.Tab("Line and Superposition over input"):
|
| 492 |
+
out_pixel_full_over_img = gr.Image(type="numpy", image_mode="RGB", interactive=False)
|
| 493 |
+
with gr.Tab("Line over input"):
|
| 494 |
+
out_pixel_line_over_img = gr.Image(type="numpy", image_mode="RGB", interactive=False)
|
| 495 |
+
with gr.Tab("Superposition over input"):
|
| 496 |
+
out_pixel_superposition_over_img = gr.Image(type="numpy", image_mode="RGB", interactive=False)
|
| 497 |
+
with gr.Tab("Line and Superposition"):
|
| 498 |
+
out_pixel_full_img = gr.Image(type="numpy", image_mode="RGB", interactive=False)
|
| 499 |
+
with gr.Tab("Line only"):
|
| 500 |
+
out_pixel_line_img = gr.Image(type="numpy", image_mode="RGB", interactive=False)
|
| 501 |
+
with gr.Tab("Superposition only"):
|
| 502 |
+
out_pixel_superposition_img = gr.Image(type="numpy", image_mode="RGB", label="Labelized image")
|
| 503 |
+
with gr.Tab("File"):
|
| 504 |
+
out_pixel_file_label = gr.File(label="Pixel output full", interactive=False)
|
| 505 |
+
out_pixel_file_superposition = gr.File(label="Pixel output full", interactive=False)
|
| 506 |
+
out_pixel_file_superposition_extract = gr.Json(label="Superposition sample")
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
run_button = gr.Button("Run")
|
| 510 |
+
run_button.click(
|
| 511 |
+
app_function,
|
| 512 |
+
inputs=[
|
| 513 |
+
img_input,
|
| 514 |
+
min_len,
|
| 515 |
+
preprocess,
|
| 516 |
+
tracker,
|
| 517 |
+
traversal_mode,
|
| 518 |
+
extraction_type,
|
| 519 |
+
negate_image,
|
| 520 |
+
dyn,
|
| 521 |
+
size_mask,
|
| 522 |
+
double_exponential_alpha,
|
| 523 |
+
simple_moving_average_memory,
|
| 524 |
+
exponential_moving_average_memory,
|
| 525 |
+
one_euro_beta,
|
| 526 |
+
one_euro_mincutoff,
|
| 527 |
+
one_euro_dcutoff,
|
| 528 |
+
bucket_size,
|
| 529 |
+
nb_values_to_keep,
|
| 530 |
+
discontinuity_relative,
|
| 531 |
+
discontinuity_absolute,
|
| 532 |
+
minimum_for_fusion,
|
| 533 |
+
default_sigma_position,
|
| 534 |
+
default_sigma_thickness,
|
| 535 |
+
default_sigma_luminosity,
|
| 536 |
+
min_nb_values_sigma,
|
| 537 |
+
sigma_pos_min,
|
| 538 |
+
sigma_thickness_min,
|
| 539 |
+
sigma_luminosity_min,
|
| 540 |
+
gradient_threshold,
|
| 541 |
+
llumi,
|
| 542 |
+
blumi,
|
| 543 |
+
ratio_lum,
|
| 544 |
+
max_thickness,
|
| 545 |
+
threshold_intersection,
|
| 546 |
+
remove_duplicates
|
| 547 |
+
],
|
| 548 |
+
outputs=[
|
| 549 |
+
out_duration,
|
| 550 |
+
|
| 551 |
+
out_vector_over_img, out_vector_label_img,
|
| 552 |
+
out_vector_file, out_vector_file_extract,
|
| 553 |
+
|
| 554 |
+
out_pixel_full_over_img, out_pixel_line_over_img, out_pixel_superposition_over_img,
|
| 555 |
+
out_pixel_full_img, out_pixel_line_img, out_pixel_superposition_img,
|
| 556 |
+
out_pixel_file_label,
|
| 557 |
+
out_pixel_file_superposition, out_pixel_file_superposition_extract
|
| 558 |
+
])
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
gr.Markdown("""
|
| 562 |
+
## Examples
|
| 563 |
+
|
| 564 |
+
Be aware that parameters are not reset when you change example.
|
| 565 |
+
""")
|
| 566 |
+
|
| 567 |
+
current_dir = os.path.dirname(__file__)
|
| 568 |
+
with gr.Tab("trade_directory"):
|
| 569 |
+
gr.Examples(
|
| 570 |
+
examples=[[os.path.join(current_dir, "image", "trade_directories.png"), 200, 200, 200]],
|
| 571 |
+
inputs=[img_input, blumi, llumi, min_len]
|
| 572 |
+
)
|
| 573 |
+
with gr.Tab("music_sheet"):
|
| 574 |
+
gr.Examples(
|
| 575 |
+
|
| 576 |
+
examples=[[os.path.join(current_dir, "image", "music_sheet.png"), 30, 5, 20, "HORIZONTAL"]],
|
| 577 |
+
inputs=[img_input, discontinuity_relative, max_thickness, min_len, traversal_mode]
|
| 578 |
+
)
|
| 579 |
+
with gr.Tab("map"):
|
| 580 |
+
gr.Examples(
|
| 581 |
+
examples=[[os.path.join(current_dir, "image", "map.png"), 4, 180, 180, 20, 6]],
|
| 582 |
+
inputs=[img_input, discontinuity_relative, blumi, llumi, min_len, max_thickness]
|
| 583 |
+
)
|
| 584 |
+
|
| 585 |
+
gr.Markdown("""
|
| 586 |
+
## A question ?
|
| 587 |
+
|
| 588 |
+
If you have any question, please contact us at: <philippe.bernet@epita.fr>
|
| 589 |
+
""")
|
| 590 |
+
|
| 591 |
+
# fmt: on
|
| 592 |
+
|
| 593 |
+
app.launch()
|
image/map.png
ADDED
|
Git LFS Details
|
image/music_sheet.png
ADDED
|
Git LFS Details
|
image/trade_directories.png
ADDED
|
Git LFS Details
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy
|
| 2 |
+
opencv-python
|
| 3 |
+
pylena
|