Spaces:
Sleeping
Sleeping
Merge remote-tracking branch 'origin/main'
Browse files- .gitignore +4 -0
- app/main.py +54 -14
- app/tabs/examples_tab.py +0 -89
- app/tabs/submit.py +151 -34
- app/tabs/templating.py +6 -20
- app/tabs/visualizer.py +100 -0
- app/utils/__init__.py +0 -0
- app/utils/md_helper.py +0 -14
- app/utils/yaml_helper.py +0 -91
- pyproject.toml +6 -4
- uv.lock +4 -16
.gitignore
CHANGED
@@ -2,6 +2,10 @@ venv/
|
|
2 |
.vscode/
|
3 |
.cache/
|
4 |
outputs/
|
|
|
|
|
|
|
|
|
5 |
|
6 |
# Byte-compiled / optimized / DLL files
|
7 |
*/__pycache__
|
|
|
2 |
.vscode/
|
3 |
.cache/
|
4 |
outputs/
|
5 |
+
alto-outputs/
|
6 |
+
page-outputs/
|
7 |
+
text-outputs/
|
8 |
+
tmp/
|
9 |
|
10 |
# Byte-compiled / optimized / DLL files
|
11 |
*/__pycache__
|
app/main.py
CHANGED
@@ -1,20 +1,39 @@
|
|
1 |
import gradio as gr
|
2 |
-
|
3 |
from app.gradio_config import css, theme
|
4 |
-
from app.tabs.submit import
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
6 |
from app.tabs.templating import (
|
7 |
templating_block,
|
8 |
TEMPLATE_IMAGE_FOLDER,
|
9 |
TEMPLATE_YAML_FOLDER,
|
10 |
template_output_yaml_code,
|
11 |
)
|
12 |
-
|
|
|
13 |
|
14 |
gr.set_static_paths(paths=[TEMPLATE_IMAGE_FOLDER])
|
15 |
gr.set_static_paths(paths=[TEMPLATE_YAML_FOLDER])
|
16 |
|
17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
with gr.Blocks(title="HTRflow", theme=theme, css=css) as demo:
|
19 |
with gr.Row():
|
20 |
with gr.Column(scale=1):
|
@@ -31,8 +50,8 @@ with gr.Blocks(title="HTRflow", theme=theme, css=css) as demo:
|
|
31 |
with gr.Tab(label="Submit Job") as tab_submit:
|
32 |
submit.render()
|
33 |
|
34 |
-
with gr.Tab(label="
|
35 |
-
|
36 |
|
37 |
@demo.load(
|
38 |
inputs=[template_output_yaml_code],
|
@@ -41,20 +60,41 @@ with gr.Blocks(title="HTRflow", theme=theme, css=css) as demo:
|
|
41 |
def inital_yaml_code(template_output_yaml_code):
|
42 |
return template_output_yaml_code
|
43 |
|
44 |
-
|
|
|
|
|
|
|
|
|
45 |
"""Synchronize the YAML state if there is a mismatch."""
|
46 |
return input_value if input_value != state_value else gr.skip()
|
47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
tab_submit.select(
|
49 |
inputs=[template_output_yaml_code, custom_template_yaml],
|
50 |
outputs=[custom_template_yaml],
|
51 |
-
fn=
|
52 |
)
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
)
|
59 |
|
60 |
|
@@ -63,7 +103,7 @@ demo.queue()
|
|
63 |
if __name__ == "__main__":
|
64 |
demo.launch(
|
65 |
server_name="0.0.0.0",
|
66 |
-
server_port=
|
67 |
enable_monitoring=False,
|
68 |
-
show_error=True,
|
69 |
)
|
|
|
1 |
import gradio as gr
|
2 |
+
import os
|
3 |
from app.gradio_config import css, theme
|
4 |
+
from app.tabs.submit import (
|
5 |
+
submit,
|
6 |
+
custom_template_yaml,
|
7 |
+
collection_submit_state,
|
8 |
+
batch_image_gallery,
|
9 |
+
)
|
10 |
+
from app.tabs.visualizer import visualizer, collection_viz_state, viz_image_gallery
|
11 |
from app.tabs.templating import (
|
12 |
templating_block,
|
13 |
TEMPLATE_IMAGE_FOLDER,
|
14 |
TEMPLATE_YAML_FOLDER,
|
15 |
template_output_yaml_code,
|
16 |
)
|
17 |
+
|
18 |
+
from htrflow.models.huggingface.trocr import TrOCR
|
19 |
|
20 |
gr.set_static_paths(paths=[TEMPLATE_IMAGE_FOLDER])
|
21 |
gr.set_static_paths(paths=[TEMPLATE_YAML_FOLDER])
|
22 |
|
23 |
|
24 |
+
def load_markdown(language, section, content_dir="app/content"):
|
25 |
+
"""Load markdown content from files."""
|
26 |
+
if language is None:
|
27 |
+
file_path = os.path.join(content_dir, f"{section}.md")
|
28 |
+
else:
|
29 |
+
file_path = os.path.join(content_dir, language, f"{section}.md")
|
30 |
+
|
31 |
+
if os.path.exists(file_path):
|
32 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
33 |
+
return f.read()
|
34 |
+
return f"## Content missing for {file_path} in {language}"
|
35 |
+
|
36 |
+
|
37 |
with gr.Blocks(title="HTRflow", theme=theme, css=css) as demo:
|
38 |
with gr.Row():
|
39 |
with gr.Column(scale=1):
|
|
|
50 |
with gr.Tab(label="Submit Job") as tab_submit:
|
51 |
submit.render()
|
52 |
|
53 |
+
with gr.Tab(label="Visualize Result") as tab_visualizer:
|
54 |
+
visualizer.render()
|
55 |
|
56 |
@demo.load(
|
57 |
inputs=[template_output_yaml_code],
|
|
|
60 |
def inital_yaml_code(template_output_yaml_code):
|
61 |
return template_output_yaml_code
|
62 |
|
63 |
+
@demo.load()
|
64 |
+
def inital_trocr_load():
|
65 |
+
return TrOCR("Riksarkivet/trocr-base-handwritten-hist-swe-2")
|
66 |
+
|
67 |
+
def sync_gradio_objects(input_value, state_value):
|
68 |
"""Synchronize the YAML state if there is a mismatch."""
|
69 |
return input_value if input_value != state_value else gr.skip()
|
70 |
|
71 |
+
def sync_gradio_object_state(input_value, state_value):
|
72 |
+
"""Synchronize the Collection."""
|
73 |
+
state_value = input_value
|
74 |
+
return state_value if state_value is not None else gr.skip()
|
75 |
+
|
76 |
+
tab_templating.select(
|
77 |
+
inputs=[custom_template_yaml, template_output_yaml_code],
|
78 |
+
outputs=[template_output_yaml_code],
|
79 |
+
fn=sync_gradio_objects,
|
80 |
+
)
|
81 |
+
|
82 |
tab_submit.select(
|
83 |
inputs=[template_output_yaml_code, custom_template_yaml],
|
84 |
outputs=[custom_template_yaml],
|
85 |
+
fn=sync_gradio_objects,
|
86 |
)
|
87 |
|
88 |
+
# tab_visualizer.select(
|
89 |
+
# inputs=[batch_image_gallery, viz_image_gallery],
|
90 |
+
# outputs=[viz_image_gallery],
|
91 |
+
# fn=sync_gradio_objects,
|
92 |
+
# )
|
93 |
+
|
94 |
+
tab_visualizer.select(
|
95 |
+
inputs=[collection_submit_state, collection_viz_state],
|
96 |
+
outputs=[collection_viz_state],
|
97 |
+
fn=sync_gradio_object_state,
|
98 |
)
|
99 |
|
100 |
|
|
|
103 |
if __name__ == "__main__":
|
104 |
demo.launch(
|
105 |
server_name="0.0.0.0",
|
106 |
+
server_port=7864,
|
107 |
enable_monitoring=False,
|
108 |
+
# show_error=True,
|
109 |
)
|
app/tabs/examples_tab.py
DELETED
@@ -1,89 +0,0 @@
|
|
1 |
-
import gradio as gr
|
2 |
-
from gradio_modal import Modal
|
3 |
-
import pandas as pd
|
4 |
-
|
5 |
-
|
6 |
-
output_image_placehholder = gr.Image(
|
7 |
-
label="Output image",
|
8 |
-
height=400,
|
9 |
-
show_share_button=True,
|
10 |
-
)
|
11 |
-
markdown_selected_option = gr.Markdown(value=" ", container=True)
|
12 |
-
|
13 |
-
|
14 |
-
def htr_image_placehholder(txt, method, image):
|
15 |
-
needs_yaml_to_forward_tohtrflow_ = """steps:
|
16 |
-
"""
|
17 |
-
print(method)
|
18 |
-
|
19 |
-
return txt, method, image
|
20 |
-
|
21 |
-
|
22 |
-
with gr.Blocks() as examples:
|
23 |
-
with gr.Row(variant="panel"):
|
24 |
-
with gr.Column():
|
25 |
-
example_text_input_placeholder = gr.Markdown(visible=False, container=False)
|
26 |
-
example_method_input_placeholder = gr.Markdown(
|
27 |
-
visible=False, container=False
|
28 |
-
)
|
29 |
-
example_text_output_placeholder = gr.Markdown(
|
30 |
-
visible=False, container=False
|
31 |
-
)
|
32 |
-
|
33 |
-
user_image_input = gr.Image(visible=False, container=False)
|
34 |
-
|
35 |
-
# gr.Examples( # TODO: add so example has a alto xml file on hf datasets
|
36 |
-
# fn=htr_image_placehholder,
|
37 |
-
# examples=images_for_demo.examples_list,
|
38 |
-
# inputs=[
|
39 |
-
# example_text_input_placeholder,
|
40 |
-
# example_method_input_placeholder,
|
41 |
-
# user_image_input,
|
42 |
-
# ],
|
43 |
-
# outputs=[example_text_output_placeholder, markdown_selected_option, output_image_placehholder],
|
44 |
-
# cache_examples=True,
|
45 |
-
# cache_mode="eager",
|
46 |
-
# label="Example images",
|
47 |
-
# examples_per_page=6,
|
48 |
-
# )
|
49 |
-
|
50 |
-
with gr.Column():
|
51 |
-
with gr.Tabs():
|
52 |
-
with gr.Tab("Viewer"):
|
53 |
-
with gr.Group():
|
54 |
-
with gr.Row():
|
55 |
-
output_image_placehholder.render()
|
56 |
-
with gr.Row():
|
57 |
-
markdown_selected_option.render()
|
58 |
-
show_output_modal = gr.Button(
|
59 |
-
"Show output", variant="secondary", scale=0
|
60 |
-
)
|
61 |
-
with gr.Row():
|
62 |
-
output_dataframe_pipeline = gr.Textbox(
|
63 |
-
label="Click text", info="click on image bla bla.."
|
64 |
-
)
|
65 |
-
with gr.Tab("YAML, DF, Graph?") as htrflow_output_table_tab:
|
66 |
-
with gr.Group():
|
67 |
-
with gr.Row():
|
68 |
-
output_dataframe_pipeline = gr.Image(
|
69 |
-
label="Output image", interactive=False, height="100"
|
70 |
-
)
|
71 |
-
with gr.Row():
|
72 |
-
output_dataframe_pipeline = gr.Dataframe(
|
73 |
-
label="Output image", col_count=2
|
74 |
-
)
|
75 |
-
|
76 |
-
with Modal(visible=False) as output_modal:
|
77 |
-
output_files_pipeline = gr.Files(
|
78 |
-
label="Output files", height=100, visible=True, scale=0
|
79 |
-
)
|
80 |
-
output_yaml_code = gr.Code(
|
81 |
-
language="yaml",
|
82 |
-
label="yaml",
|
83 |
-
interactive=True,
|
84 |
-
visible=True,
|
85 |
-
scale=0,
|
86 |
-
)
|
87 |
-
# TODO: yaml should parse from datasets.. and show the yaml code
|
88 |
-
|
89 |
-
show_output_modal.click(lambda: Modal(visible=True), None, output_modal)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/tabs/submit.py
CHANGED
@@ -1,14 +1,152 @@
|
|
|
|
1 |
import time
|
|
|
2 |
import gradio as gr
|
3 |
from htrflow.pipeline.pipeline import Pipeline
|
|
|
|
|
|
|
|
|
4 |
from htrflow.pipeline.steps import auto_import
|
5 |
import yaml
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
|
11 |
with gr.Blocks() as submit:
|
|
|
|
|
12 |
with gr.Column(variant="panel"):
|
13 |
with gr.Group():
|
14 |
with gr.Row():
|
@@ -20,6 +158,7 @@ with gr.Blocks() as submit:
|
|
20 |
height=400,
|
21 |
object_fit="cover",
|
22 |
columns=5,
|
|
|
23 |
)
|
24 |
|
25 |
with gr.Column(scale=1):
|
@@ -30,7 +169,9 @@ with gr.Blocks() as submit:
|
|
30 |
interactive=True,
|
31 |
)
|
32 |
with gr.Row():
|
33 |
-
run_button = gr.Button("Submit", variant="primary", scale=0, min_width=
|
|
|
|
|
34 |
|
35 |
@batch_image_gallery.upload(
|
36 |
inputs=batch_image_gallery,
|
@@ -42,38 +183,14 @@ with gr.Blocks() as submit:
|
|
42 |
return gr.update(value=None)
|
43 |
return images
|
44 |
|
45 |
-
def my_function(custom_template_yaml, batch_image_gallery, progress=gr.Progress()):
|
46 |
-
config = yaml.safe_load(custom_template_yaml)
|
47 |
-
|
48 |
-
image, _ = batch_image_gallery[0]
|
49 |
-
|
50 |
-
pipe = Pipeline.from_config(config)
|
51 |
-
print(batch_image_gallery)
|
52 |
-
collections = auto_import(image)
|
53 |
-
|
54 |
-
label = "HTRflow demo"
|
55 |
-
|
56 |
-
for collection in collections:
|
57 |
-
if "labels" in config:
|
58 |
-
collection.set_label_format(**config["labels"])
|
59 |
-
if label:
|
60 |
-
collection.label = label
|
61 |
-
collection = pipe.run(collection)
|
62 |
-
|
63 |
-
# progress(0, desc="Starting...")
|
64 |
-
# time.sleep(1)
|
65 |
-
# for i in progress.tqdm(range(100)):
|
66 |
-
# if i == 20:
|
67 |
-
# gr.Info("hej morgan")
|
68 |
-
# if i == 50:
|
69 |
-
# gr.Info("hej morgan2")
|
70 |
-
# time.sleep(0.1)
|
71 |
-
# gr.Info("hej morgan nu är jag klar")
|
72 |
-
print(collection)
|
73 |
-
return gr.skip()
|
74 |
-
|
75 |
run_button.click(
|
76 |
-
|
|
|
|
|
|
|
77 |
inputs=[custom_template_yaml, batch_image_gallery],
|
78 |
-
outputs=
|
|
|
|
|
|
|
79 |
)
|
|
|
1 |
+
import glob
|
2 |
import time
|
3 |
+
import uuid
|
4 |
import gradio as gr
|
5 |
from htrflow.pipeline.pipeline import Pipeline
|
6 |
+
from htrflow.pipeline.steps import init_step
|
7 |
+
import os
|
8 |
+
from htrflow.volume.volume import Collection
|
9 |
+
|
10 |
from htrflow.pipeline.steps import auto_import
|
11 |
import yaml
|
12 |
|
13 |
+
MAX_IMAGES = int(os.environ.get("MAX_IMAGES", 5)) # env: Maximum allowed images
|
14 |
+
|
15 |
+
|
16 |
+
class PipelineWithProgress(Pipeline):
|
17 |
+
@classmethod
|
18 |
+
def from_config(cls, config: dict[str, str]):
|
19 |
+
"""Init pipeline from config, ensuring the correct subclass is instantiated."""
|
20 |
+
return cls([init_step(step["step"], step.get("settings", {})) for step in config["steps"]])
|
21 |
+
|
22 |
+
def run(self, collection, start=0, progress=None):
|
23 |
+
"""
|
24 |
+
Run pipeline on collection with Gradio progress support.
|
25 |
+
If progress is provided, it updates the Gradio progress bar during execution.
|
26 |
+
"""
|
27 |
+
total_steps = len(self.steps[start:])
|
28 |
+
for i, step in enumerate(self.steps[start:]):
|
29 |
+
step_name = f"{step} (step {start + i + 1} / {total_steps})"
|
30 |
+
|
31 |
+
try:
|
32 |
+
progress((i + 1) / total_steps, desc=f"Running {step_name}")
|
33 |
+
collection = step.run(collection)
|
34 |
+
|
35 |
+
except Exception:
|
36 |
+
if self.pickle_path:
|
37 |
+
gr.Error(
|
38 |
+
f"HTRflow: Pipeline failed on step {step_name}. A backup collection is saved at {self.pickle_path}"
|
39 |
+
)
|
40 |
+
else:
|
41 |
+
gr.Error(
|
42 |
+
f"HTRflow: Pipeline failed on step {step_name}",
|
43 |
+
)
|
44 |
+
raise
|
45 |
+
return collection
|
46 |
+
|
47 |
+
|
48 |
+
def rewrite_export_dests(config):
|
49 |
+
"""
|
50 |
+
Rewrite the 'dest' in all 'Export' steps to include 'tmp' and a UUID.
|
51 |
+
Returns:
|
52 |
+
- A new config object with the updated 'dest' values.
|
53 |
+
- A list of all updated 'dest' paths.
|
54 |
+
"""
|
55 |
+
new_config = {"steps": []}
|
56 |
+
updated_paths = []
|
57 |
+
|
58 |
+
unique_id = str(uuid.uuid4())
|
59 |
+
|
60 |
+
for step in config.get("steps", []):
|
61 |
+
new_step = step.copy()
|
62 |
+
if new_step.get("step") == "Export":
|
63 |
+
settings = new_step.get("settings", {})
|
64 |
+
if "dest" in settings:
|
65 |
+
new_dest = os.path.join("tmp", unique_id, settings["dest"])
|
66 |
+
settings["dest"] = new_dest
|
67 |
+
updated_paths.append(new_dest)
|
68 |
+
new_config["steps"].append(new_step)
|
69 |
+
|
70 |
+
return new_config, updated_paths
|
71 |
+
|
72 |
+
|
73 |
+
def run_htrflow(custom_template_yaml, batch_image_gallery, progress=gr.Progress()):
|
74 |
+
"""
|
75 |
+
Executes the HTRflow pipeline based on the provided YAML configuration and batch images.
|
76 |
+
Args:
|
77 |
+
custom_template_yaml (str): YAML string specifying the HTRflow pipeline configuration.
|
78 |
+
batch_image_gallery (list): List of uploaded images to process in the pipeline.
|
79 |
+
Returns:
|
80 |
+
tuple: A collection of processed items, list of exported file paths, and a Gradio update object.
|
81 |
+
"""
|
82 |
+
|
83 |
+
if custom_template_yaml is None or len(custom_template_yaml) < 1:
|
84 |
+
gr.Warning("HTRflow: Please insert a HTRflow-yaml template")
|
85 |
+
try:
|
86 |
+
config = yaml.safe_load(custom_template_yaml)
|
87 |
+
except Exception as e:
|
88 |
+
gr.Warning(f"HTRflow: Error loading YAML configuration: {e}")
|
89 |
+
return gr.skip()
|
90 |
+
|
91 |
+
temp_config, tmp_output_paths = rewrite_export_dests(config)
|
92 |
+
|
93 |
+
progress(0, desc="HTRflow: Starting")
|
94 |
+
time.sleep(0.3)
|
95 |
|
96 |
+
if batch_image_gallery is None:
|
97 |
+
gr.Warning("HTRflow: You must upload atleast 1 image or more")
|
98 |
+
|
99 |
+
images = [temp_img[0] for temp_img in batch_image_gallery]
|
100 |
+
|
101 |
+
pipe = PipelineWithProgress.from_config(temp_config)
|
102 |
+
collections = auto_import(images)
|
103 |
+
|
104 |
+
gr.Info(f"HTRflow: processing {len(images)} {'image' if len(images) == 1 else 'images'}.")
|
105 |
+
progress(0.1, desc="HTRflow: Processing")
|
106 |
+
|
107 |
+
for collection in collections:
|
108 |
+
if "labels" in temp_config:
|
109 |
+
collection.set_label_format(**temp_config["labels"])
|
110 |
+
|
111 |
+
collection.label = "HTRflow_demo_output"
|
112 |
+
collection: Collection = pipe.run(collection, progress=progress)
|
113 |
+
|
114 |
+
exported_files = tracking_exported_files(tmp_output_paths)
|
115 |
+
|
116 |
+
time.sleep(0.5)
|
117 |
+
progress(1, desc="HTRflow: Finish")
|
118 |
+
gr.Info("HTRflow: Finish")
|
119 |
+
|
120 |
+
yield collection, exported_files, gr.skip()
|
121 |
+
|
122 |
+
|
123 |
+
def tracking_exported_files(tmp_output_paths):
|
124 |
+
"""
|
125 |
+
Look for files with specific extensions in the provided tmp_output_paths,
|
126 |
+
including subdirectories. Eliminates duplicate files.
|
127 |
+
|
128 |
+
Args:
|
129 |
+
tmp_output_paths (list): List of temporary output directories to search.
|
130 |
+
|
131 |
+
Returns:
|
132 |
+
list: Unique paths of all matching files found in the directories.
|
133 |
+
"""
|
134 |
+
accepted_extensions = {".txt", ".xml", ".json"}
|
135 |
+
|
136 |
+
exported_files = set()
|
137 |
+
|
138 |
+
for tmp_folder in tmp_output_paths:
|
139 |
+
for ext in accepted_extensions:
|
140 |
+
search_pattern = os.path.join(tmp_folder, "**", f"*{ext}")
|
141 |
+
matching_files = glob.glob(search_pattern, recursive=True)
|
142 |
+
exported_files.update(matching_files)
|
143 |
+
|
144 |
+
return sorted(exported_files)
|
145 |
|
146 |
|
147 |
with gr.Blocks() as submit:
|
148 |
+
collection_submit_state = gr.State()
|
149 |
+
|
150 |
with gr.Column(variant="panel"):
|
151 |
with gr.Group():
|
152 |
with gr.Row():
|
|
|
158 |
height=400,
|
159 |
object_fit="cover",
|
160 |
columns=5,
|
161 |
+
# preview=True,
|
162 |
)
|
163 |
|
164 |
with gr.Column(scale=1):
|
|
|
169 |
interactive=True,
|
170 |
)
|
171 |
with gr.Row():
|
172 |
+
run_button = gr.Button("Submit", variant="primary", scale=0, min_width=200)
|
173 |
+
progess_bar = gr.Textbox(visible=False, show_label=False)
|
174 |
+
collection_output_files = gr.Files(label="Output Files", scale=0, min_width=400, visible=False)
|
175 |
|
176 |
@batch_image_gallery.upload(
|
177 |
inputs=batch_image_gallery,
|
|
|
183 |
return gr.update(value=None)
|
184 |
return images
|
185 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
186 |
run_button.click(
|
187 |
+
lambda: (gr.update(visible=True), gr.update(visible=False)),
|
188 |
+
outputs=[progess_bar, collection_output_files],
|
189 |
+
).then(
|
190 |
+
fn=run_htrflow,
|
191 |
inputs=[custom_template_yaml, batch_image_gallery],
|
192 |
+
outputs=[collection_submit_state, collection_output_files, progess_bar],
|
193 |
+
).then(
|
194 |
+
lambda: (gr.update(visible=False), gr.update(visible=True)),
|
195 |
+
outputs=[progess_bar, collection_output_files],
|
196 |
)
|
app/tabs/templating.py
CHANGED
@@ -6,11 +6,7 @@ import re
|
|
6 |
def get_sorted_files(folder, extensions):
|
7 |
"""Retrieve sorted files by numeric value in their names."""
|
8 |
return sorted(
|
9 |
-
[
|
10 |
-
os.path.join(folder, file)
|
11 |
-
for file in os.listdir(folder)
|
12 |
-
if file.lower().endswith(extensions)
|
13 |
-
],
|
14 |
key=lambda x: (
|
15 |
int(re.search(r"\d+", os.path.basename(x)).group())
|
16 |
if re.search(r"\d+", os.path.basename(x))
|
@@ -49,9 +45,7 @@ TEMPLATE_IMAGE_FOLDER = "app/assets/images"
|
|
49 |
TEMPLATE_YAML_FOLDER = "app/assets/templates"
|
50 |
|
51 |
# File Retrieval
|
52 |
-
image_files = get_sorted_files(
|
53 |
-
TEMPLATE_IMAGE_FOLDER, (".png", ".jpg", ".jpeg", ".webp")
|
54 |
-
)
|
55 |
yaml_files = get_sorted_files(TEMPLATE_YAML_FOLDER, (".yaml",))
|
56 |
|
57 |
# Categorize YAML Files
|
@@ -96,11 +90,8 @@ with gr.Blocks() as templating_block:
|
|
96 |
with gr.Group():
|
97 |
with gr.Row():
|
98 |
with gr.Column(scale=1):
|
99 |
-
template_image = gr.Image(
|
100 |
-
label="Example Templates", value=image_files[0], height=400
|
101 |
-
)
|
102 |
with gr.Column(scale=1):
|
103 |
-
|
104 |
template_output_yaml_code = gr.Code(
|
105 |
language="yaml",
|
106 |
label="Pipeline",
|
@@ -108,7 +99,7 @@ with gr.Blocks() as templating_block:
|
|
108 |
visible=True,
|
109 |
)
|
110 |
docs_link = gr.HTML(
|
111 |
-
value='<p><a href="https://ai-riksarkivet.github.io/htrflow/latest/getting_started/pipeline.html#example-pipelines" target="_blank">📚 Click here 📚</a> for a detailed description on how to customize the configuration</p>',
|
112 |
visible=True,
|
113 |
)
|
114 |
|
@@ -121,7 +112,6 @@ with gr.Blocks() as templating_block:
|
|
121 |
],
|
122 |
)
|
123 |
def on_template_select(dropdown_selection_template):
|
124 |
-
|
125 |
if dropdown_selection_template == "Simple":
|
126 |
yaml_content = get_yaml_content(yaml_files_numbered[0])
|
127 |
return image_files[0], yaml_content, gr.update(visible=False)
|
@@ -132,9 +122,7 @@ with gr.Blocks() as templating_block:
|
|
132 |
yaml_content = get_yaml_content(yaml_files_c_letter[0])
|
133 |
return image_files[2], yaml_content, gr.update(visible=True)
|
134 |
else:
|
135 |
-
return gr.Error(
|
136 |
-
f"{dropdown_selection_template} - is not a valid Template selection"
|
137 |
-
)
|
138 |
|
139 |
@custom_dropdown_selection_template.select(
|
140 |
inputs=custom_dropdown_selection_template,
|
@@ -147,9 +135,7 @@ with gr.Blocks() as templating_block:
|
|
147 |
yaml_content = get_yaml_content(yaml_path)
|
148 |
return yaml_content
|
149 |
else:
|
150 |
-
return gr.Error(
|
151 |
-
f"{custom_template_selection} - is not a valid Custom Template selection"
|
152 |
-
)
|
153 |
|
154 |
@dropdown_selection_template.select(
|
155 |
inputs=dropdown_selection_template,
|
|
|
6 |
def get_sorted_files(folder, extensions):
|
7 |
"""Retrieve sorted files by numeric value in their names."""
|
8 |
return sorted(
|
9 |
+
[os.path.join(folder, file) for file in os.listdir(folder) if file.lower().endswith(extensions)],
|
|
|
|
|
|
|
|
|
10 |
key=lambda x: (
|
11 |
int(re.search(r"\d+", os.path.basename(x)).group())
|
12 |
if re.search(r"\d+", os.path.basename(x))
|
|
|
45 |
TEMPLATE_YAML_FOLDER = "app/assets/templates"
|
46 |
|
47 |
# File Retrieval
|
48 |
+
image_files = get_sorted_files(TEMPLATE_IMAGE_FOLDER, (".png", ".jpg", ".jpeg", ".webp"))
|
|
|
|
|
49 |
yaml_files = get_sorted_files(TEMPLATE_YAML_FOLDER, (".yaml",))
|
50 |
|
51 |
# Categorize YAML Files
|
|
|
90 |
with gr.Group():
|
91 |
with gr.Row():
|
92 |
with gr.Column(scale=1):
|
93 |
+
template_image = gr.Image(label="Example Templates", value=image_files[0], height=400)
|
|
|
|
|
94 |
with gr.Column(scale=1):
|
|
|
95 |
template_output_yaml_code = gr.Code(
|
96 |
language="yaml",
|
97 |
label="Pipeline",
|
|
|
99 |
visible=True,
|
100 |
)
|
101 |
docs_link = gr.HTML(
|
102 |
+
value='<p><a href="https://ai-riksarkivet.github.io/htrflow/latest/getting_started/pipeline.html#example-pipelines" target="_blank">📚 Click here 📚</a> for a detailed description on how to customize the configuration for HTRflow</p>',
|
103 |
visible=True,
|
104 |
)
|
105 |
|
|
|
112 |
],
|
113 |
)
|
114 |
def on_template_select(dropdown_selection_template):
|
|
|
115 |
if dropdown_selection_template == "Simple":
|
116 |
yaml_content = get_yaml_content(yaml_files_numbered[0])
|
117 |
return image_files[0], yaml_content, gr.update(visible=False)
|
|
|
122 |
yaml_content = get_yaml_content(yaml_files_c_letter[0])
|
123 |
return image_files[2], yaml_content, gr.update(visible=True)
|
124 |
else:
|
125 |
+
return gr.Error(f"{dropdown_selection_template} - is not a valid Template selection")
|
|
|
|
|
126 |
|
127 |
@custom_dropdown_selection_template.select(
|
128 |
inputs=custom_dropdown_selection_template,
|
|
|
135 |
yaml_content = get_yaml_content(yaml_path)
|
136 |
return yaml_content
|
137 |
else:
|
138 |
+
return gr.Error(f"{custom_template_selection} - is not a valid Custom Template selection")
|
|
|
|
|
139 |
|
140 |
@dropdown_selection_template.select(
|
141 |
inputs=dropdown_selection_template,
|
app/tabs/visualizer.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
from htrflow.volume.volume import Collection
|
5 |
+
from htrflow.utils.draw import draw_polygons
|
6 |
+
from htrflow.utils import imgproc
|
7 |
+
|
8 |
+
from htrflow.results import Segment
|
9 |
+
|
10 |
+
|
11 |
+
with gr.Blocks() as visualizer:
|
12 |
+
with gr.Column(variant="panel"):
|
13 |
+
with gr.Row():
|
14 |
+
collection_viz_state = gr.State()
|
15 |
+
result_collection_viz_state = gr.State()
|
16 |
+
with gr.Column():
|
17 |
+
viz_image_gallery = gr.Gallery(
|
18 |
+
file_types=["image"],
|
19 |
+
label="Visualized Images from HTRflow",
|
20 |
+
interactive=False,
|
21 |
+
height=400,
|
22 |
+
object_fit="cover",
|
23 |
+
columns=5,
|
24 |
+
preview=True,
|
25 |
+
)
|
26 |
+
|
27 |
+
visualize_button = gr.Button(
|
28 |
+
"Visualize", scale=0, min_width=200, variant="secondary"
|
29 |
+
)
|
30 |
+
|
31 |
+
with gr.Column():
|
32 |
+
# image_visualizer_annotation = gr.Image(
|
33 |
+
# interactive=False,
|
34 |
+
# )
|
35 |
+
|
36 |
+
line2 = gr.Gallery(
|
37 |
+
interactive=False,
|
38 |
+
)
|
39 |
+
textlines = gr.Dataframe()
|
40 |
+
|
41 |
+
# @viz_image_gallery.select(outputs=image_visualizer_annotation)
|
42 |
+
# def return_image_from_gallery(evt: gr.SelectData):
|
43 |
+
# return evt.value["image"]["path"]
|
44 |
+
|
45 |
+
@visualize_button.click(
|
46 |
+
outputs=[result_collection_viz_state, viz_image_gallery, line2, textlines]
|
47 |
+
)
|
48 |
+
def testie_load_pickle():
|
49 |
+
col = Collection.from_pickle(".cache/HTRflow_demo_output.pickle")
|
50 |
+
|
51 |
+
results = []
|
52 |
+
for page_idx, page_node in enumerate(col):
|
53 |
+
page_image = page_node.image.copy()
|
54 |
+
|
55 |
+
lines = list(page_node.traverse(lambda node: node.is_line()))
|
56 |
+
|
57 |
+
recog_conf_values = {
|
58 |
+
i: list(zip(tr.texts, tr.scores)) if (tr := ln.text_result) else []
|
59 |
+
for i, ln in enumerate(lines)
|
60 |
+
}
|
61 |
+
|
62 |
+
recog_df = pd.DataFrame(
|
63 |
+
[
|
64 |
+
{"Transcription": text, "Confidence Score": f"{score:.4f}"}
|
65 |
+
for values in recog_conf_values.values()
|
66 |
+
for text, score in values
|
67 |
+
]
|
68 |
+
)
|
69 |
+
|
70 |
+
line_polygons = []
|
71 |
+
line_crops = []
|
72 |
+
for ln in lines:
|
73 |
+
seg: Segment = ln.data.get("segment")
|
74 |
+
if not seg:
|
75 |
+
continue
|
76 |
+
|
77 |
+
cropped_line_img = imgproc.crop(page_image, seg.bbox)
|
78 |
+
cropped_line_img = np.clip(cropped_line_img, 0, 255).astype(np.uint8)
|
79 |
+
line_crops.append(cropped_line_img)
|
80 |
+
|
81 |
+
if seg.polygon is not None:
|
82 |
+
line_polygons.append(seg.polygon)
|
83 |
+
|
84 |
+
annotated_image = draw_polygons(page_image, line_polygons)
|
85 |
+
annotated_page_node = np.clip(annotated_image, 0, 255).astype(np.uint8)
|
86 |
+
results.append(
|
87 |
+
{
|
88 |
+
"page_image": page_node,
|
89 |
+
"annotated_page_node": annotated_page_node,
|
90 |
+
"line_crops": line_crops,
|
91 |
+
"recog_conf_values": recog_df,
|
92 |
+
}
|
93 |
+
)
|
94 |
+
|
95 |
+
return (
|
96 |
+
results,
|
97 |
+
[results[0]["annotated_page_node"]],
|
98 |
+
results[0]["line_crops"],
|
99 |
+
results[0]["recog_conf_values"],
|
100 |
+
)
|
app/utils/__init__.py
DELETED
File without changes
|
app/utils/md_helper.py
DELETED
@@ -1,14 +0,0 @@
|
|
1 |
-
import os
|
2 |
-
|
3 |
-
|
4 |
-
def load_markdown(language, section, content_dir="app/content"):
|
5 |
-
"""Load markdown content from files."""
|
6 |
-
if language is None:
|
7 |
-
file_path = os.path.join(content_dir, f"{section}.md")
|
8 |
-
else:
|
9 |
-
file_path = os.path.join(content_dir, language, f"{section}.md")
|
10 |
-
|
11 |
-
if os.path.exists(file_path):
|
12 |
-
with open(file_path, "r", encoding="utf-8") as f:
|
13 |
-
return f.read()
|
14 |
-
return f"## Content missing for {file_path} in {language}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/utils/yaml_helper.py
DELETED
@@ -1,91 +0,0 @@
|
|
1 |
-
from jinja2 import Environment, FileSystemLoader
|
2 |
-
|
3 |
-
|
4 |
-
def get_yaml_button_fn(
|
5 |
-
method,
|
6 |
-
output_formats,
|
7 |
-
reading_order,
|
8 |
-
simple_segment_model=None,
|
9 |
-
simple_htr_model=None,
|
10 |
-
simple_htr_model_type=None,
|
11 |
-
simple_segment_model_type=None,
|
12 |
-
nested_segment_model_1=None,
|
13 |
-
nested_segment_model_2=None,
|
14 |
-
nested_htr_model=None,
|
15 |
-
nested_segment_model_1_type=None,
|
16 |
-
nested_segment_model_2_type=None,
|
17 |
-
nested_htr_model_type=None,
|
18 |
-
):
|
19 |
-
env = Environment(loader=FileSystemLoader("app/templates"))
|
20 |
-
|
21 |
-
if output_formats is None:
|
22 |
-
output_formats = ["txt"]
|
23 |
-
|
24 |
-
template_name = "steps_template.yaml.j2"
|
25 |
-
try:
|
26 |
-
if method == "Simple layout":
|
27 |
-
steps = [
|
28 |
-
{
|
29 |
-
"step": "Segmentation",
|
30 |
-
"model": simple_segment_model_type,
|
31 |
-
"model_settings": {"model": simple_segment_model},
|
32 |
-
},
|
33 |
-
{
|
34 |
-
"step": "TextRecognition",
|
35 |
-
"model": simple_htr_model_type,
|
36 |
-
"model_settings": {"model": simple_htr_model},
|
37 |
-
},
|
38 |
-
]
|
39 |
-
elif method == "Nested segmentation":
|
40 |
-
steps = [
|
41 |
-
{
|
42 |
-
"step": "Segmentation",
|
43 |
-
"model": nested_segment_model_1_type,
|
44 |
-
"model_settings": {"model": nested_segment_model_1},
|
45 |
-
},
|
46 |
-
{
|
47 |
-
"step": "Segmentation",
|
48 |
-
"model": nested_segment_model_2_type,
|
49 |
-
"model_settings": {"model": nested_segment_model_2},
|
50 |
-
},
|
51 |
-
{
|
52 |
-
"step": "TextRecognition",
|
53 |
-
"model": nested_htr_model_type,
|
54 |
-
"model_settings": {"model": nested_htr_model},
|
55 |
-
},
|
56 |
-
]
|
57 |
-
else:
|
58 |
-
return "Invalid method or not yet supported."
|
59 |
-
|
60 |
-
steps.append({"step": reading_order})
|
61 |
-
|
62 |
-
# TODO: fix reading order
|
63 |
-
# - step: ReadingOrderMarginalia
|
64 |
-
# settings:
|
65 |
-
# two_page: always
|
66 |
-
|
67 |
-
# TODO: fix labeling format
|
68 |
-
# # Sets label format to regionX_lineY_wordZ
|
69 |
-
# labels:
|
70 |
-
# level_labels:
|
71 |
-
# - region
|
72 |
-
# - line
|
73 |
-
# - word
|
74 |
-
# sep: _
|
75 |
-
# template: "{label}{number}"
|
76 |
-
|
77 |
-
steps.extend(
|
78 |
-
{
|
79 |
-
"step": "Export",
|
80 |
-
"settings": {"format": format, "dest": f"{format}-outputs"},
|
81 |
-
}
|
82 |
-
for format in output_formats
|
83 |
-
)
|
84 |
-
|
85 |
-
template = env.get_template(template_name)
|
86 |
-
|
87 |
-
yaml_value = template.render(steps=steps)
|
88 |
-
return yaml_value
|
89 |
-
|
90 |
-
except Exception as e:
|
91 |
-
return f"Error generating YAML: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pyproject.toml
CHANGED
@@ -17,13 +17,12 @@ classifiers = [
|
|
17 |
requires-python = ">=3.10,<3.13"
|
18 |
|
19 |
dependencies = [
|
20 |
-
"htrflow==0.
|
21 |
"gradio>=5.11.0",
|
22 |
"datasets>=3.2.0",
|
23 |
"pandas>=2.2.3",
|
24 |
-
"
|
25 |
-
"
|
26 |
-
"fastapi[standard]>=0.115.6",
|
27 |
]
|
28 |
|
29 |
[project.urls]
|
@@ -46,6 +45,9 @@ openmmlab = [
|
|
46 |
"mmdet==3.0.0",
|
47 |
"mmocr==1.0.0"
|
48 |
]
|
|
|
|
|
|
|
49 |
|
50 |
|
51 |
[build-system]
|
|
|
17 |
requires-python = ">=3.10,<3.13"
|
18 |
|
19 |
dependencies = [
|
20 |
+
"htrflow==0.2.0",
|
21 |
"gradio>=5.11.0",
|
22 |
"datasets>=3.2.0",
|
23 |
"pandas>=2.2.3",
|
24 |
+
"tqdm>=4.67.1",
|
25 |
+
"pillow>=11.1.0",
|
|
|
26 |
]
|
27 |
|
28 |
[project.urls]
|
|
|
45 |
"mmdet==3.0.0",
|
46 |
"mmocr==1.0.0"
|
47 |
]
|
48 |
+
teklia = [
|
49 |
+
"pylaia==1.1.2"
|
50 |
+
]
|
51 |
|
52 |
|
53 |
[build-system]
|
uv.lock
CHANGED
@@ -851,18 +851,6 @@ wheels = [
|
|
851 |
{ url = "https://files.pythonhosted.org/packages/a6/3d/e05202dd42581c2a1e93c730d10a0ef45bc40921332c9aa0d6645bbf0e2b/gradio_client-1.5.4-py3-none-any.whl", hash = "sha256:ad38c9a6f7fc590e822627f5bf5685321a7822b8f1a88b76d00a0621a43162d6", size = 321364 },
|
852 |
]
|
853 |
|
854 |
-
[[package]]
|
855 |
-
name = "gradio-modal"
|
856 |
-
version = "0.0.4"
|
857 |
-
source = { registry = "https://pypi.org/simple" }
|
858 |
-
dependencies = [
|
859 |
-
{ name = "gradio" },
|
860 |
-
]
|
861 |
-
sdist = { url = "https://files.pythonhosted.org/packages/e2/fd/3b383f9ee8d60625e9e26871ba4adcacbedeab132041b94290758e02e543/gradio_modal-0.0.4.tar.gz", hash = "sha256:717ae699072a171648cfa1b84bc153be84e92d04e9ad58c1bc59af68ef332726", size = 1180812 }
|
862 |
-
wheels = [
|
863 |
-
{ url = "https://files.pythonhosted.org/packages/05/3d/76f454de84ae1dccbf2b7023e933afb8dde5fdd89e9476786726ef770737/gradio_modal-0.0.4-py3-none-any.whl", hash = "sha256:d96e817d2e934d9e1b835b06474f45fd349b5ccea499d1536bfb4bd38f62dedb", size = 1106241 },
|
864 |
-
]
|
865 |
-
|
866 |
[[package]]
|
867 |
name = "h11"
|
868 |
version = "0.14.0"
|
@@ -905,10 +893,10 @@ dependencies = [
|
|
905 |
{ name = "datasets" },
|
906 |
{ name = "fastapi", extra = ["standard"] },
|
907 |
{ name = "gradio" },
|
908 |
-
{ name = "gradio-modal" },
|
909 |
{ name = "htrflow" },
|
910 |
-
{ name = "jinja2" },
|
911 |
{ name = "pandas" },
|
|
|
|
|
912 |
]
|
913 |
|
914 |
[package.optional-dependencies]
|
@@ -933,15 +921,15 @@ requires-dist = [
|
|
933 |
{ name = "datasets", specifier = ">=3.2.0" },
|
934 |
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.6" },
|
935 |
{ name = "gradio", specifier = ">=5.11.0" },
|
936 |
-
{ name = "gradio-modal", specifier = ">=0.0.4" },
|
937 |
{ name = "htrflow", specifier = "==0.1.3" },
|
938 |
-
{ name = "jinja2", specifier = ">=3.1.4" },
|
939 |
{ name = "mmcv", marker = "extra == 'openmmlab'", specifier = "==2.0.1" },
|
940 |
{ name = "mmdet", marker = "extra == 'openmmlab'", specifier = "==3.0.0" },
|
941 |
{ name = "mmengine", marker = "extra == 'openmmlab'", specifier = "==0.7.4" },
|
942 |
{ name = "mmocr", marker = "extra == 'openmmlab'", specifier = "==1.0.0" },
|
943 |
{ name = "openmim", marker = "extra == 'openmmlab'", specifier = "==0.3.9" },
|
944 |
{ name = "pandas", specifier = ">=2.2.3" },
|
|
|
|
|
945 |
]
|
946 |
|
947 |
[package.metadata.requires-dev]
|
|
|
851 |
{ url = "https://files.pythonhosted.org/packages/a6/3d/e05202dd42581c2a1e93c730d10a0ef45bc40921332c9aa0d6645bbf0e2b/gradio_client-1.5.4-py3-none-any.whl", hash = "sha256:ad38c9a6f7fc590e822627f5bf5685321a7822b8f1a88b76d00a0621a43162d6", size = 321364 },
|
852 |
]
|
853 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
854 |
[[package]]
|
855 |
name = "h11"
|
856 |
version = "0.14.0"
|
|
|
893 |
{ name = "datasets" },
|
894 |
{ name = "fastapi", extra = ["standard"] },
|
895 |
{ name = "gradio" },
|
|
|
896 |
{ name = "htrflow" },
|
|
|
897 |
{ name = "pandas" },
|
898 |
+
{ name = "pillow" },
|
899 |
+
{ name = "tqdm" },
|
900 |
]
|
901 |
|
902 |
[package.optional-dependencies]
|
|
|
921 |
{ name = "datasets", specifier = ">=3.2.0" },
|
922 |
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.6" },
|
923 |
{ name = "gradio", specifier = ">=5.11.0" },
|
|
|
924 |
{ name = "htrflow", specifier = "==0.1.3" },
|
|
|
925 |
{ name = "mmcv", marker = "extra == 'openmmlab'", specifier = "==2.0.1" },
|
926 |
{ name = "mmdet", marker = "extra == 'openmmlab'", specifier = "==3.0.0" },
|
927 |
{ name = "mmengine", marker = "extra == 'openmmlab'", specifier = "==0.7.4" },
|
928 |
{ name = "mmocr", marker = "extra == 'openmmlab'", specifier = "==1.0.0" },
|
929 |
{ name = "openmim", marker = "extra == 'openmmlab'", specifier = "==0.3.9" },
|
930 |
{ name = "pandas", specifier = ">=2.2.3" },
|
931 |
+
{ name = "pillow", specifier = ">=11.1.0" },
|
932 |
+
{ name = "tqdm", specifier = ">=4.67.1" },
|
933 |
]
|
934 |
|
935 |
[package.metadata.requires-dev]
|