carpelan commited on
Commit
60d8ae5
·
1 Parent(s): c6fd70c

Made some inital changes for sprint 2

Browse files
.github/README.md CHANGED
@@ -1,95 +1,195 @@
1
- # WORK IN PROGRESS
2
 
3
- > :warning: **Dont use yet !**
4
 
5
- # htrflow_app: A demo app for htrflow
6
 
7
- We're thrilled to introduce [htrflow](https://huggingface.co/spaces/Riksarkivet/htr_demo), our demonstration platform that brings to life the process of transcribing Swedish handwritten documents from the 17th to the 19th century.
8
 
9
  <p align="center">
10
- <img src="https://github.com/Borg93/htr_gradio_file_placeholder/blob/main/htrflow_background_dalle3.png?raw=true" alt="HTRFLOW Image" width=40%>
11
  </p>
12
 
13
- htrflow_app is designed to provide users with a step-by-step visualization of the HTR-process, and offer non-expert users an inside look into the workings of an AI-transcription pipeline.
14
- At the moment htrflow_app is mainly a demo-application. It’s not intended for production, but instead to showcase the immense possibilities that HTR-technology is opening up for cultural heritage institutions around the world.
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- All code is open-source, all our models are on [Hugging Face](https://huggingface.co/collections/Riksarkivet/models-for-handwritten-text-recognition-652692c6871f915e766de688) and are free to use, and all data will be made available for download and use on [Hugging Face](https://huggingface.co/datasets/Riksarkivet/placeholder_htr) as well.
17
 
18
- **Note** that the backend (src) for the app will be rewritten and packaged to be more optimized under the project name [htrflow_core](https://github.com/Swedish-National-Archives-AI-lab/htrflow_core).
19
 
20
- ## Run app
21
 
22
- Use virtual env.
23
 
24
- ```
25
- python3 -m venv .venv
26
- source .venv/bin/activate
27
- ```
28
 
29
- Install libraries with Makefile:
30
 
31
- ```
32
- make install
33
- ```
34
 
35
- With pip:
36
 
37
- ```
38
- pip install -r requirements.txt
39
- ```
 
 
40
 
41
- Run app with:
42
 
43
- ```
44
- gradio app.py
45
- ```
46
 
47
- ## Run with Docker
 
 
48
 
49
- There are two options:
50
 
51
- ### Run with Docker locally
52
 
53
- Build container:
 
54
 
55
- ```
56
- docker build --tag htrflow/htrflow-app .
57
- ```
58
 
59
- Run container:
60
 
61
- ```
62
- docker run -it -d --name htrflow-app -p 7000:7860 htrflow/htrflow-app:latest
63
- ```
 
 
 
 
 
 
 
 
 
64
 
65
- ### Run with Docker with HF
 
 
 
 
 
66
 
67
- You can also just run it from Hugging Face:
 
 
 
 
 
 
 
 
 
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  docker run -it -p 7860:7860 --platform=linux/amd64 --gpus all \
71
- -e registry.hf.space/riksarkivet-htr-demo:latest
72
  ```
73
 
74
  ---
75
 
76
- ## Instructions for documentation
77
 
78
- - Naming convention of folder is based on tab
79
- - Naming convention of file is based on subtabs
80
- - If subtab uses columns and rows
81
- - Use suffix such as col1, row1 or tab1, to indicate differences in postion of text.
82
 
83
- see image below:
 
 
 
 
 
84
 
85
- <p align="center">
86
- <img src="https://github.com/Borg93/htr_gradio_file_placeholder/blob/main/layout_structure.png?raw=true" alt="Badge 1">
87
- </p>
88
-
89
- ## Assets and file sharing with app
90
 
91
- This repo acts as asset manager for the app:
92
 
93
- - [Github Repo](https://github.com/Borg93/htr_gradio_file_placeholder)
94
 
95
- **Note**: this repo is an work in progress
 
1
+ # HTRflow_app
2
 
3
+ [HTRflow_app](https://huggingface.co/spaces/Riksarkivet/htr_demo), our interactive demo application that visualizes the entire Handwritten Text Recognition (HTR) process. With this demo, users can explore, step by step, how AI transforms historical manuscripts into digital text.
4
 
5
+ Please note that this is a demo application—not intended for production use—but it highlights the immense potential of HTR technology for cultural heritage institutions worldwide.
6
 
 
7
 
8
  <p align="center">
9
+ <img src="https://ai-riksarkivet.github.io/htrflow/latest/assets/background_htrflow_2.png" alt="HTRflow App Demo" width="80%">
10
  </p>
11
 
12
+ ---
13
+ <!-- https://ecotrust-canada.github.io/markdown-toc/ -->
14
+ - [HTRflow_app](#htrflow-app)
15
+ * [Overview](#overview)
16
+ * [Guide](#guide)
17
+ * [How to use app..](#how-to-use-app)
18
+ * [Getting Started](#getting-started)
19
+ + [Prerequisites](#prerequisites)
20
+ + [Installation](#installation)
21
+ + [Running the Application Locally](#running-the-application-locally)
22
+ * [Running with Docker](#running-with-docker)
23
+ + [Locally with Docker](#locally-with-docker)
24
+ + [On Hugging Face Spaces](#on-hugging-face-spaces)
25
+ * [Contributing](#contributing)
26
+ * [License](#license)
27
 
 
28
 
29
+ ---
30
 
31
+ ## Guide
32
 
33
+ The demo consist of 3 tabs: Upload, Results and Export. You navigate through the app by first uploading 1 or many images in
34
 
35
+ Upload:
 
 
 
36
 
37
+ Result:
38
 
39
+ Export:
 
 
40
 
41
+ ## Pipeline Configuration
42
 
43
+ HTRflow powers the application's engine with a structured pipeline design pattern. This pattern uses declarative YAML schemas as blueprints to define step-by-step processing instructions. For detailed documentation, visit the [HTRflow Pipeline Guide](https://ai-riksarkivet.github.io/htrflow/latest/getting_started/pipeline.html#yaml).
44
+
45
+ <p align="center">
46
+ <img src="../app/assets/images/3_worker.png" alt="HTRflow Worker Pipeline" width="20%">
47
+ </p>
48
 
49
+ ### Understanding YAML Pipeline Templates
50
 
51
+ The following series of images demonstrates how YAML pipeline templates function. Each template is designed for specific document types - the example below shows a template optimized for single-column running text, such as letters, notes, and individual pages.
 
 
52
 
53
+ <p align="center">
54
+ <img src="../app/assets/images/how_to_1.png" alt="YAML Template Structure" width="70%">
55
+ </p>
56
 
57
+ ### Pipeline Steps
58
 
59
+ Each pipeline consists of sequential steps executed from top to bottom. In this example, we focus on two primary steps:
60
 
61
+ 1. **Segmentation**: Identifies and extracts text lines from the image
62
+ 2. **Text Recognition**: Performs Handwritten Text Recognition (HTR) on the segmented lines
63
 
64
+ <p align="center">
65
+ <img src="../app/assets/images/how_to_2.png" alt="Pipeline Steps Overview" width="50%">
66
+ </p>
67
 
68
+ ### Model Integration
69
 
70
+ Models specified in the pipeline can be downloaded directly from the [Huggingface model hub](https://huggingface.co/models?library=htrflow). For a comprehensive list of supported models, refer to the [HTRflow Models Documentation](https://ai-riksarkivet.github.io/htrflow/latest/getting_started/models.html#models).
71
+
72
+ > **Note**: For English text recognition, you'll need to specify an appropriate model ID, such as the [Microsoft TrOCR base handwritten model](https://huggingface.co/microsoft/trocr-base-handwritten).
73
+
74
+ <p align="center">
75
+ <img src="../app/assets/images/how_to_3.png" alt="Model Configuration" width="50%">
76
+ </p>
77
+
78
+ ### Processing Workflow
79
+
80
+ #### Text Line Detection
81
+ The following image illustrates the text line segmentation process:
82
 
83
+ <p align="center">
84
+ <img src="../app/assets/images/how_to_4.png" alt="Text Line Detection Process" width="90%">
85
+ </p>
86
+
87
+ #### Text Recognition
88
+ After segmentation, the detected text lines are processed by the HTR component:
89
 
90
+ <p align="center">
91
+ <img src="../app/assets/images/how_to_5.png" alt="Text Recognition Process" width="80%">
92
+ </p>
93
+
94
+ #### Reading Order Determination
95
+ The final pipeline step determines the reading order of the text. In this example, it applies a simple top-down ordering transformation:
96
+
97
+ <p align="center">
98
+ <img src="../app/assets/images/how_to_6.png" alt="Reading Order Determination" width="85%">
99
+ </p>
100
 
101
+
102
+
103
+
104
+
105
+ ## Development
106
+
107
+ ### Prerequisites
108
+
109
+ - **Python:** Version 3.7 or higher
110
+ - **pip:** Python package installer
111
+ - **(Optional) Docker:** For containerized deployment
112
+ - **(Optional) Nvidia GPU:** For faster predictions..
113
+
114
+
115
+ ### Installation
116
+
117
+ 1. **Clone the Repository:**
118
+
119
+ ```bash
120
+ git clone https://github.com/your_username/htrflow_app.git
121
+ cd htrflow_app
122
+ ```
123
+
124
+ 2. **Set Up a Virtual Environment:**
125
+
126
+ ```bash
127
+ python3 -m venv .venv
128
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
129
+ ```
130
+
131
+ 3. **Install Dependencies:**
132
+
133
+ Since we are no longer using a Makefile, install the required packages with:
134
+
135
+ ```bash
136
+ pip install -r requirements.txt
137
+ ```
138
+
139
+ ### Running the Application Locally
140
+
141
+ Launch the Gradio demo by running:
142
+
143
+ ```bash
144
+ gradio app/main.py
145
  ```
146
+
147
+ Then open your web browser and navigate to `http://localhost:7860` (or the address displayed in your terminal) to interact with the demo.
148
+
149
+ ---
150
+
151
+ ## Running with Docker
152
+
153
+ ### Locally with Docker
154
+
155
+ 1. **Build the Docker Image:**
156
+
157
+ ```bash
158
+ docker build --tag htrflow/htrflow-app .
159
+ ```
160
+
161
+ 2. **Run the Docker Container:**
162
+
163
+ ```bash
164
+ docker run -it -d --name htrflow-app -p 7000:7860 htrflow/htrflow-app:latest
165
+ ```
166
+
167
+ Now, visit `http://localhost:7000` in your browser.
168
+
169
+ ### On Hugging Face Spaces
170
+
171
+ Alternatively, you can run HTRflow_app directly on Hugging Face with:
172
+
173
+ ```bash
174
  docker run -it -p 7860:7860 --platform=linux/amd64 --gpus all \
175
+ -e registry.hf.space/riksarkivet-htr-demo:latest
176
  ```
177
 
178
  ---
179
 
 
180
 
181
+ ## Contributing
 
 
 
182
 
183
+ We welcome community contributions! If you’d like to contribute:
184
+ 1. Fork the repository.
185
+ 2. Create a feature branch (`git checkout -b feature/YourFeature`).
186
+ 3. Commit your changes (`git commit -m 'Add some feature'`).
187
+ 4. Push to your branch (`git push origin feature/YourFeature`).
188
+ 5. Open a pull request.
189
 
190
+ ---
 
 
 
 
191
 
192
+ ## License
193
 
194
+ This project is open source. See the [LICENSE](./LICENSE) file for details.
195
 
 
Makefile DELETED
@@ -1,48 +0,0 @@
1
- .PHONY: install
2
-
3
- venv:
4
- python -m venv venv
5
-
6
-
7
- activate:
8
- source ./venv/bin/activate
9
-
10
- install: local_install install_openmmlab
11
-
12
- docker_install: local_install install_openmmlab_with_mim
13
-
14
- local_install:
15
- @echo "Running requirements install"
16
- pip install --upgrade pip
17
- pip install -r requirements.txt
18
-
19
- install_openmmlab_with_mim:
20
- @echo "Running Openmmlab requirements install"
21
- pip install -U openmim
22
- mim install mmengine
23
- mim install mmcv
24
- mim install mmdet
25
- mim install mmocr
26
-
27
- install_openmmlab:
28
- @echo "Running Openmmlab requirements install"
29
- pip install mmengine==0.7.4
30
- pip install mmcv==2.0.1
31
- pip install mmdet==3.0.0
32
- pip install mmocr==1.0.0
33
-
34
- build:
35
- pip install -e .
36
- gradio app.py
37
-
38
- docker_build:
39
- docker build -t htrflow-app -f .docker/Dockerfile .
40
-
41
- # clean_for_actions:
42
- # git lfs prune
43
- # git filter-branch --force --index-filter "git rm --cached --ignore-unmatch helper/text/videos/eating_spaghetti.mp4" --prune-empty --tag-name-filter cat -- --all
44
- # git push --force origin main
45
-
46
- # add_space:
47
- # git remote add demo https://huggingface.co/spaces/Riksarkivet/htr_demo
48
- # git push --force demo main
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/assets/images/how_to_1.png ADDED

Git LFS Details

  • SHA256: ef76982df58855265b8f06831a6a8bd085be06b32587a615446bc8649c8fe722
  • Pointer size: 131 Bytes
  • Size of remote file: 432 kB
app/assets/images/how_to_2.png ADDED

Git LFS Details

  • SHA256: e1949d45f28de938d2b74cdd04cc476c215d26718efc33ab7018c15bc9101348
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
app/assets/images/how_to_3.png ADDED

Git LFS Details

  • SHA256: 6334d825cab1fc9abc80f0ea70ce25913e67ead3712358b6e5e139642b059003
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
app/assets/images/how_to_4.png ADDED

Git LFS Details

  • SHA256: 9100e54608da3cfb1779b02fd5a8891f4146092eba0df58b1155134a31baf74d
  • Pointer size: 131 Bytes
  • Size of remote file: 647 kB
app/assets/images/how_to_5.png ADDED

Git LFS Details

  • SHA256: bd0a3ed8e0334c7fbc384f8b00ed71649187bee16ada90e7a8386b41ad1c7641
  • Pointer size: 131 Bytes
  • Size of remote file: 178 kB
app/assets/images/how_to_6.png ADDED

Git LFS Details

  • SHA256: 0b7b375abee3e2c51b997ba5d342b358946c6be9abcd03bb51c99a00b4c7cd46
  • Pointer size: 131 Bytes
  • Size of remote file: 358 kB
app/assets/templates/c_nested_labels.yaml DELETED
@@ -1,41 +0,0 @@
1
- steps:
2
- - step: Segmentation
3
- settings:
4
- model: yolo
5
- model_settings:
6
- model: Riksarkivet/yolov9-regions-1
7
- generation_settings:
8
- batch_size: 2
9
- - step: Segmentation
10
- settings:
11
- model: yolo
12
- model_settings:
13
- model: Riksarkivet/yolov9-lines-within-regions-1
14
- generation_settings:
15
- batch_size: 2
16
- - step: TextRecognition
17
- settings:
18
- model: WordLevelTrocr
19
- model_settings:
20
- model: Riksarkivet/trocr-base-handwritten-hist-swe-2
21
- generation_settings:
22
- batch_size: 4
23
- num_beams: 1
24
- - step: ReadingOrderMarginalia
25
- settings:
26
- two_page: auto
27
- - step: Export
28
- settings:
29
- dest: outputs/alto
30
- format: alto
31
- - step: Export
32
- settings:
33
- dest: outputs/page
34
- format: page
35
- labels:
36
- level_labels:
37
- - region
38
- - line
39
- - word
40
- sep: _
41
- template: "{label}{number}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/assets/templates/c_nested_reading_order.yaml DELETED
@@ -1,24 +0,0 @@
1
- steps:
2
- - step: Segmentation
3
- settings:
4
- model: yolo
5
- model_settings:
6
- model: Riksarkivet/yolov9-regions-1
7
- - step: Segmentation
8
- settings:
9
- model: yolo
10
- model_settings:
11
- model: Riksarkivet/yolov9-lines-within-regions-1
12
- - step: TextRecognition
13
- settings:
14
- model: TrOCR
15
- model_settings:
16
- model: Riksarkivet/trocr-base-handwritten-hist-swe-2
17
- - step: ReadingOrderMarginalia
18
- settings:
19
- two_page: always
20
- - step: Export
21
- settings:
22
- format: txt
23
- dest: text-outputs
24
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/assets/templates/c_nested_with_filter.yaml DELETED
@@ -1,28 +0,0 @@
1
- steps:
2
- - step: Segmentation
3
- settings:
4
- model: yolo
5
- model_settings:
6
- model: Riksarkivet/yolov9-regions-1
7
- - step: Segmentation
8
- settings:
9
- model: yolo
10
- model_settings:
11
- model: Riksarkivet/yolov9-lines-within-regions-1
12
- - step: TextRecognition
13
- settings:
14
- model: TrOCR
15
- model_settings:
16
- model: Riksarkivet/trocr-base-handwritten-hist-swe-2
17
- - step: OrderLines
18
- - step: Export
19
- settings:
20
- format: txt
21
- dest: raw-outputs
22
- - step: RemoveLowTextConfidenceLines
23
- settings:
24
- threshold: 0.95
25
- - step: Export
26
- settings:
27
- format: txt
28
- dest: cleaned-outputs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/assets/templates/c_simple_gensettings.yaml DELETED
@@ -1,21 +0,0 @@
1
- steps:
2
- - step: Segmentation
3
- settings:
4
- model: yolo
5
- model_settings:
6
- model: Riksarkivet/yolov9-lines-within-regions-1
7
- generation_settings:
8
- batch_size: 2
9
- - step: TextRecognition
10
- settings:
11
- model: TrOCR
12
- model_settings:
13
- model: Riksarkivet/trocr-base-handwritten-hist-swe-2
14
- generation_settings:
15
- batch_size: 4
16
- num_beams: 1
17
- - step: OrderLines
18
- - step: Export
19
- settings:
20
- format: txt
21
- dest: outputs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/assets/templates/c_simple_multi_output.yaml DELETED
@@ -1,24 +0,0 @@
1
- steps:
2
- - step: Segmentation
3
- settings:
4
- model: yolo
5
- model_settings:
6
- model: Riksarkivet/yolov9-lines-within-regions-1
7
- - step: TextRecognition
8
- settings:
9
- model: TrOCR
10
- model_settings:
11
- model: Riksarkivet/trocr-base-handwritten-hist-swe-2
12
- - step: OrderLines
13
- - step: Export
14
- settings:
15
- format: txt
16
- dest: text-outputs
17
- - step: Export
18
- settings:
19
- format: page
20
- dest: page-outputs
21
- - step: Export
22
- settings:
23
- format: alto
24
- dest: alto-outputs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/assets/templates/{2_nested.yaml → nested.yaml} RENAMED
@@ -14,9 +14,4 @@ steps:
14
  model: TrOCR
15
  model_settings:
16
  model: Riksarkivet/trocr-base-handwritten-hist-swe-2
17
- - step: ReadingOrderMarginalia
18
- - step: Export
19
- settings:
20
- format: txt
21
- dest: text-outputs
22
-
 
14
  model: TrOCR
15
  model_settings:
16
  model: Riksarkivet/trocr-base-handwritten-hist-swe-2
17
+ - step: ReadingOrderMarginalia
 
 
 
 
 
app/assets/templates/{1_simple.yaml → simple.yaml} RENAMED
@@ -9,8 +9,4 @@ steps:
9
  model: TrOCR
10
  model_settings:
11
  model: Riksarkivet/trocr-base-handwritten-hist-swe-2
12
- - step: OrderLines
13
- - step: Export
14
- settings:
15
- format: txt
16
- dest: outputs
 
9
  model: TrOCR
10
  model_settings:
11
  model: Riksarkivet/trocr-base-handwritten-hist-swe-2
12
+ - step: OrderLines
 
 
 
 
app/content/how_it_works.md DELETED
@@ -1,44 +0,0 @@
1
- # Nocebant Achilles de vallis meminere fugit
2
-
3
- ## Corpus exta frondes pectora neque
4
-
5
- Lorem markdownum animi, resistere praefertur recenti de vocor data levibus.
6
- Lucifer cupidine pugnandi alter, quies modestos, nec aut quae Cancri diva
7
- Latiis. Morerne est bonis ingentibus luctantemque corpore ad consistuntque
8
- Cereris clausit.
9
-
10
- ## Putat inclita si parte se
11
-
12
- Accipe fit explevit pessima in timebat querellas qui. Peti fuit: summa et
13
- adstantem vulnere artus is utque orbes suis exsangues me saepe! Hominis et Troia
14
- pater contigit, dolor fecit illis in.
15
-
16
- ## Nos celer bracchia curvari hiemsque
17
-
18
- Corpus Alcmene omnia hiemes viros sic nepotum *pater* soporem, tenebat modo
19
- Lethaea adstupet artis et cur. Optatis tendebant posita pudore Hennaeis dicere
20
- visa tanti cornua laevam et faciebat et transfert sanguineam iussos. Aliquid
21
- occupat [sagittis](http://densihabet.org/) tributuram si nihil fugamque
22
- Bienoris.
23
-
24
- > Os docuisse posse tectus, nisi pronas, trabes annos amor porrigitur. Corpora
25
- > retemptantem fulvas. Et letum semianimem exclamat omnia et amisso.
26
-
27
- ## Aquae tibi insanis se quas
28
-
29
- [Hyperione dare](http://www.fama.org/)? Cum certam virique fugacis magnos, sedes
30
- iuverat Canens fera, mox cervus, res dea equorum vocant, vocandus.
31
-
32
- traceroute.mac_active.partition_widget_optical(restoreBare(irqMeme,
33
- reimage_file), -1, network.ethics(socketUdp, tagAddressLaser, 54));
34
- processScanMainframe.uncPipeline(flash_graphics_kilobit(dsl, imapCircuit,
35
- suffix(driveMultitaskingDrive)));
36
- var guidSmishing = 881375;
37
- ipv(vgaPointPeripheral);
38
-
39
- Ureris totidemque mihi sed pendens amantes praesens ambos tua planxerunt
40
- **lumine**, huius bracchia Cepheusque ne invida circum etiam! Exsistere
41
- cornuque, non oblatae quid servat quae tecto potiere exhibuit annos qui vulnera.
42
- Eras sic non passus peragit frequens, quae creati vix. Regno per cortice ignea,
43
- versus non omni missus: cervice sub *foedoque ferali*. Troiana hiemes solidumve
44
- et timori; quod, deus clamor barba; mea aulam furtum saltus suo catenas.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/gradio_config.py CHANGED
@@ -76,4 +76,12 @@ hr.region-divider {
76
  margin: auto;
77
  color: var(--body-text-color);
78
  }
 
 
 
 
 
 
 
 
79
  """
 
76
  margin: auto;
77
  color: var(--body-text-color);
78
  }
79
+
80
+ .button-group-viz {
81
+ margin: auto;
82
+ display: flex;
83
+ justify-content: center;
84
+ gap: 1rem;
85
+ text-align: center;
86
+ }
87
  """
app/main.py CHANGED
@@ -1,20 +1,18 @@
1
- import shutil
2
- import gradio as gr
3
  import os
4
- from app.gradio_config import css, theme
5
- from app.tabs.submit import submit, collection_submit_state
6
- from app.tabs.visualizer import visualizer, collection as collection_viz_state
7
- from gradio_modal import Modal
8
 
 
9
  from htrflow.models.huggingface.trocr import TrOCR
10
 
 
 
 
 
 
 
 
11
  TEMPLATE_YAML_FOLDER = "app/assets/templates"
12
  gr.set_static_paths(paths=[TEMPLATE_YAML_FOLDER])
13
 
14
- # TODO: fix api/ endpoints..
15
- # TODO add colab
16
- # TDOO addd eexmaple for api
17
-
18
 
19
  def load_markdown(language, section, content_dir="app/content"):
20
  """Load markdown content from files."""
@@ -50,29 +48,25 @@ matomo = """
50
  <!-- End Matomo Code -->
51
  """
52
 
 
53
  with gr.Blocks(title="HTRflow", theme=theme, css=css, head=matomo) as demo:
54
  with gr.Row():
55
  with gr.Column(scale=1):
56
- help_button = gr.Button("Help", scale=0)
57
- with Modal(visible=False) as help_modal:
58
- # TODO: tutorial material?
59
- with gr.Tab("How to use App"):
60
- gr.Markdown(load_markdown(None, "how_it_works"))
61
- with gr.Tab("Contact"):
62
- pass
63
-
64
  with gr.Column(scale=2):
65
  gr.Markdown(load_markdown(None, "main_title"))
66
  with gr.Column(scale=1):
67
  gr.Markdown(load_markdown(None, "main_sub_title"))
68
 
69
  with gr.Tabs(elem_classes="top-navbar") as navbar:
70
- with gr.Tab(label="Submit Job") as tab_submit:
71
  submit.render()
72
-
73
  with gr.Tab(label="Result") as tab_visualizer:
74
  visualizer.render()
75
 
 
 
 
76
  @demo.load()
77
  def inital_trocr_load():
78
  TrOCR("Riksarkivet/trocr-base-handwritten-hist-swe-2")
@@ -88,14 +82,13 @@ with gr.Blocks(title="HTRflow", theme=theme, css=css, head=matomo) as demo:
88
  fn=sync_gradio_object_state,
89
  )
90
 
91
- help_button.click(lambda: Modal(visible=True), None, help_modal)
 
 
 
 
92
 
93
  demo.queue()
94
 
95
  if __name__ == "__main__":
96
- demo.launch(
97
- server_name="0.0.0.0",
98
- server_port=7860,
99
- enable_monitoring=True,
100
- # show_error=True,
101
- )
 
 
 
1
  import os
 
 
 
 
2
 
3
+ import gradio as gr
4
  from htrflow.models.huggingface.trocr import TrOCR
5
 
6
+ from app.gradio_config import css, theme
7
+ from app.tabs.export import collection as collection_export_state
8
+ from app.tabs.export import export
9
+ from app.tabs.submit import collection_submit_state, submit
10
+ from app.tabs.visualizer import collection as collection_viz_state
11
+ from app.tabs.visualizer import visualizer
12
+
13
  TEMPLATE_YAML_FOLDER = "app/assets/templates"
14
  gr.set_static_paths(paths=[TEMPLATE_YAML_FOLDER])
15
 
 
 
 
 
16
 
17
  def load_markdown(language, section, content_dir="app/content"):
18
  """Load markdown content from files."""
 
48
  <!-- End Matomo Code -->
49
  """
50
 
51
+
52
  with gr.Blocks(title="HTRflow", theme=theme, css=css, head=matomo) as demo:
53
  with gr.Row():
54
  with gr.Column(scale=1):
55
+ pass
 
 
 
 
 
 
 
56
  with gr.Column(scale=2):
57
  gr.Markdown(load_markdown(None, "main_title"))
58
  with gr.Column(scale=1):
59
  gr.Markdown(load_markdown(None, "main_sub_title"))
60
 
61
  with gr.Tabs(elem_classes="top-navbar") as navbar:
62
+ with gr.Tab(label="Upload") as tab_submit:
63
  submit.render()
 
64
  with gr.Tab(label="Result") as tab_visualizer:
65
  visualizer.render()
66
 
67
+ with gr.Tab(label="Export") as tab_export:
68
+ export.render()
69
+
70
  @demo.load()
71
  def inital_trocr_load():
72
  TrOCR("Riksarkivet/trocr-base-handwritten-hist-swe-2")
 
82
  fn=sync_gradio_object_state,
83
  )
84
 
85
+ tab_export.select(
86
+ inputs=[collection_submit_state, collection_export_state],
87
+ outputs=[collection_export_state],
88
+ fn=sync_gradio_object_state,
89
+ )
90
 
91
  demo.queue()
92
 
93
  if __name__ == "__main__":
94
+ demo.launch(server_name="0.0.0.0", server_port=7860, enable_monitoring=True, show_api=False)
 
 
 
 
 
app/tabs/export.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import yaml
3
+ from htrflow.pipeline.pipeline import Pipeline
4
+ from htrflow.volume.volume import Collection
5
+
6
+
7
+ def run_htrflow(custom_template_yaml, collection, progress=gr.Progress()):
8
+ """
9
+ Executes the HTRflow pipeline based on the provided YAML configuration and batch images.
10
+ Args:
11
+ custom_template_yaml (str): YAML string specifying the HTRflow pipeline configuration.
12
+ batch_image_gallery (list): List of uploaded images to process in the pipeline.
13
+ Returns:
14
+ tuple: A collection of processed items, list of exported file paths, and a Gradio update object.
15
+ """
16
+
17
+ if custom_template_yaml is None or len(custom_template_yaml) < 1:
18
+ gr.Warning("HTRflow: Please insert a HTRflow-yaml template")
19
+ try:
20
+ config = yaml.safe_load(custom_template_yaml)
21
+ except Exception as e:
22
+ gr.Warning(f"HTRflow: Error loading YAML configuration: {e}")
23
+ return gr.skip()
24
+
25
+ pipe = Pipeline.from_config(config)
26
+
27
+ collection: Collection = pipe.run(collection, progress=progress)
28
+
29
+ gr.Info("HTRflow: Export complete!")
30
+
31
+ yield collection, gr.skip()
32
+
33
+
34
+ with gr.Blocks() as export:
35
+ collection = gr.State()
36
+
37
+ gr.Markdown("## Export")
38
+ with gr.Group():
39
+ with gr.Row(equal_height=True):
40
+ with gr.Column(scale=1):
41
+ selected_output = gr.Dropdown(
42
+ label="Export file format",
43
+ info="Select (multiple) what export format you want",
44
+ choices=["txt", "alto", "page", "json"],
45
+ multiselect=True,
46
+ interactive=True,
47
+ )
48
+ name_of_files = gr.Textbox(
49
+ label="File name",
50
+ info="All files will be given the same name with a suffix of the file extension",
51
+ placeholder="my_htr_file",
52
+ )
53
+
54
+ with gr.Column(scale=1):
55
+ download_files = gr.Files(interactive=False)
56
+ with gr.Row():
57
+ export_button = gr.Button("Export", scale=0, min_width=200, variant="primary")
58
+
59
+ @export_button.click(inputs=[], outputs=[])
60
+ def blable():
61
+ pass
62
+
63
+
64
+ # TODO: test pylaia works...
65
+ # TODO: add other pipeliens for other language like english and hebrew model?
66
+ # TODO: add other pipeliens for other language like english and hebrew model?
67
+ # TODO kolla över toast. toast vid export?
app/tabs/submit.py CHANGED
@@ -1,16 +1,14 @@
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
- import logging
9
  from htrflow.volume.volume import Collection
10
 
11
- from htrflow.pipeline.steps import auto_import
12
- import yaml
13
-
14
  logger = logging.getLogger(__name__)
15
 
16
  # Max number of images a user can upload at once
@@ -19,23 +17,23 @@ MAX_IMAGES = int(os.environ.get("MAX_IMAGES", 5))
19
  # Example pipelines
20
  PIPELINES = {
21
  "Running text (Swedish)": {
22
- "file": "app/assets/templates/2_nested.yaml",
23
  "description": "This pipeline works well on documents with multiple text regions.",
24
  "examples": [
25
  "R0003364_00005.jpg",
26
  "30002027_00008.jpg",
27
  "A0070302_00201.jpg",
28
- ]
29
  },
30
  "Letters and snippets (Swedish)": {
31
- "file": "app/assets/templates/1_simple.yaml",
32
  "description": "This pipeline works well on letters and other documents with only one text region.",
33
  "examples": [
34
  "451511_1512_01.jpg",
35
  "A0062408_00006.jpg",
36
  "C0000546_00085_crop.png",
37
  "A0073477_00025.jpg",
38
- ]
39
  },
40
  }
41
 
@@ -46,21 +44,14 @@ GRADIO_CACHE = ".gradio_cache"
46
  EXAMPLES_DIRECTORY = os.path.join(GRADIO_CACHE, "examples")
47
 
48
  if os.environ.get("GRADIO_CACHE_DIR", GRADIO_CACHE) != GRADIO_CACHE:
49
- logger.warning(
50
- "Setting GRADIO_CACHE_DIR to '%s' (overriding a previous value)."
51
- )
52
 
53
 
54
  class PipelineWithProgress(Pipeline):
55
  @classmethod
56
  def from_config(cls, config: dict[str, str]):
57
  """Init pipeline from config, ensuring the correct subclass is instantiated."""
58
- return cls(
59
- [
60
- init_step(step["step"], step.get("settings", {}))
61
- for step in config["steps"]
62
- ]
63
- )
64
 
65
  def run(self, collection, start=0, progress=None):
66
  """
@@ -88,31 +79,6 @@ class PipelineWithProgress(Pipeline):
88
  return collection
89
 
90
 
91
- def rewrite_export_dests(config):
92
- """
93
- Rewrite the 'dest' in all 'Export' steps to include 'tmp' and a UUID.
94
- Returns:
95
- - A new config object with the updated 'dest' values.
96
- - A list of all updated 'dest' paths.
97
- """
98
- new_config = {"steps": []}
99
- updated_paths = []
100
-
101
- unique_id = str(uuid.uuid4())
102
-
103
- for step in config.get("steps", []):
104
- new_step = step.copy()
105
- if new_step.get("step") == "Export":
106
- settings = new_step.get("settings", {})
107
- if "dest" in settings:
108
- new_dest = os.path.join("tmp", unique_id, settings["dest"])
109
- settings["dest"] = new_dest
110
- updated_paths.append(new_dest)
111
- new_config["steps"].append(new_step)
112
-
113
- return new_config, updated_paths
114
-
115
-
116
  def run_htrflow(custom_template_yaml, batch_image_gallery, progress=gr.Progress()):
117
  """
118
  Executes the HTRflow pipeline based on the provided YAML configuration and batch images.
@@ -131,68 +97,32 @@ def run_htrflow(custom_template_yaml, batch_image_gallery, progress=gr.Progress(
131
  gr.Warning(f"HTRflow: Error loading YAML configuration: {e}")
132
  return gr.skip()
133
 
134
- temp_config, tmp_output_paths = rewrite_export_dests(config)
135
-
136
  progress(0, desc="HTRflow: Starting")
137
  time.sleep(0.3)
138
 
139
- print(temp_config)
140
-
141
  if batch_image_gallery is None:
142
  gr.Warning("HTRflow: You must upload atleast 1 image or more")
143
 
144
  images = [temp_img[0] for temp_img in batch_image_gallery]
145
 
146
- pipe = PipelineWithProgress.from_config(temp_config)
147
  collections = auto_import(images)
148
 
149
- gr.Info(
150
- f"HTRflow: processing {len(images)} {'image' if len(images) == 1 else 'images'}."
151
- )
152
  progress(0.1, desc="HTRflow: Processing")
153
 
154
  for collection in collections:
155
- if "labels" in temp_config:
156
- collection.set_label_format(**temp_config["labels"])
157
 
158
  collection.label = "HTRflow_demo_output"
159
  collection: Collection = pipe.run(collection, progress=progress)
160
 
161
- exported_files = tracking_exported_files(tmp_output_paths)
162
-
163
- time.sleep(0.5)
164
  progress(1, desc="HTRflow: Finish")
 
165
  gr.Info("HTRflow: Finish")
166
 
167
- yield collection, exported_files, gr.skip()
168
-
169
-
170
- def tracking_exported_files(tmp_output_paths):
171
- """
172
- Look for files with specific extensions in the provided tmp_output_paths,
173
- including subdirectories. Eliminates duplicate files.
174
-
175
- Args:
176
- tmp_output_paths (list): List of temporary output directories to search.
177
-
178
- Returns:
179
- list: Unique paths of all matching files found in the directories.
180
- """
181
- accepted_extensions = {".txt", ".xml", ".json"}
182
-
183
- exported_files = set()
184
-
185
- print(tmp_output_paths)
186
-
187
- # TODO: fix so that we get the file extension for page and alto...
188
-
189
- for tmp_folder in tmp_output_paths:
190
- for ext in accepted_extensions:
191
- search_pattern = os.path.join(tmp_folder, "**", f"*{ext}")
192
- matching_files = glob.glob(search_pattern, recursive=True)
193
- exported_files.update(matching_files)
194
-
195
- return sorted(exported_files)
196
 
197
 
198
  def get_pipeline_description(pipeline: str) -> str:
@@ -229,6 +159,7 @@ def get_selected_example_image(event: gr.SelectData) -> str:
229
  """
230
  Get path to the selected example image.
231
  """
 
232
  return [event.value["image"]["path"]]
233
 
234
 
@@ -242,55 +173,88 @@ def get_selected_example_pipeline(event: gr.SelectData) -> str | None:
242
 
243
 
244
  with gr.Blocks() as submit:
245
- collection_submit_state = gr.State()
 
 
 
 
 
 
 
246
 
 
247
  with gr.Group():
248
  with gr.Row(equal_height=True):
249
- batch_image_gallery = gr.Gallery(
250
- file_types=["image"],
251
- label="Image to transcribe",
252
- interactive=True,
253
- object_fit="scale-down",
254
- scale=3,
255
- preview=True
256
- )
257
-
258
- examples = gr.Gallery(
259
- all_example_images(),
260
- label="Examples",
261
- interactive=False,
262
- allow_preview=False,
263
- object_fit="scale-down",
264
- min_width=250,
265
- )
 
 
 
 
 
 
 
 
 
 
266
 
267
  with gr.Column(variant="panel", elem_classes="pipeline-panel"):
268
  gr.HTML("Pipeline", elem_classes="pipeline-header", padding=False)
269
 
270
  with gr.Row():
271
- pipeline_dropdown = gr.Dropdown(
272
- PIPELINES, container=False, min_width=240, scale=0, elem_classes="pipeline-dropdown"
273
- )
274
- pipeline_description = gr.HTML(
275
- value=get_pipeline_description, inputs=pipeline_dropdown, elem_classes="pipeline-description", padding=False
276
- )
277
-
278
- with gr.Group():
279
- with gr.Accordion("Edit pipeline", open=False):
280
- custom_template_yaml = gr.Code(
281
- value=get_yaml, inputs=pipeline_dropdown, language="yaml", container=False
282
- )
283
- url = "https://ai-riksarkivet.github.io/htrflow/latest/getting_started/pipeline.html#example-pipelines"
284
- gr.HTML(
285
- f'See the <a href="{url}">documentation</a> for a detailed description on how to customize HTRflow pipelines.',
286
- padding=False,
287
- elem_classes="pipeline-help",
288
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
 
290
  with gr.Row():
291
  run_button = gr.Button("Submit", variant="primary", scale=0, min_width=200)
292
  progess_bar = gr.Textbox(visible=False, show_label=False)
293
- collection_output_files = gr.Files(label="Output Files", scale=0, min_width=400, visible=False)
294
 
295
  @batch_image_gallery.upload(
296
  inputs=batch_image_gallery,
@@ -302,20 +266,26 @@ with gr.Blocks() as submit:
302
  return gr.update(value=None)
303
  return images
304
 
 
 
 
 
 
 
 
305
  run_button.click(
306
- lambda: (gr.update(visible=True), gr.update(visible=False)),
307
- outputs=[progess_bar, collection_output_files],
308
  ).then(
309
  fn=run_htrflow,
310
  inputs=[custom_template_yaml, batch_image_gallery],
311
- outputs=[collection_submit_state, collection_output_files, progess_bar],
312
  ).then(
313
- lambda: (gr.update(visible=False), gr.update(visible=True)),
314
- outputs=[progess_bar, collection_output_files],
315
  )
316
 
317
  examples.select(get_selected_example_image, None, batch_image_gallery)
318
  examples.select(get_selected_example_pipeline, None, pipeline_dropdown)
319
 
320
- # TODO: valudate yaml before submitting...?
321
- # TODO: Add toast gr.Warning: Lose previues run...
 
1
+ import logging
2
+ import os
3
  import time
4
+
5
  import gradio as gr
6
+ import yaml
7
+ from gradio_modal import Modal
8
  from htrflow.pipeline.pipeline import Pipeline
9
+ from htrflow.pipeline.steps import auto_import, init_step
 
 
10
  from htrflow.volume.volume import Collection
11
 
 
 
 
12
  logger = logging.getLogger(__name__)
13
 
14
  # Max number of images a user can upload at once
 
17
  # Example pipelines
18
  PIPELINES = {
19
  "Running text (Swedish)": {
20
+ "file": "app/assets/templates/nested.yaml",
21
  "description": "This pipeline works well on documents with multiple text regions.",
22
  "examples": [
23
  "R0003364_00005.jpg",
24
  "30002027_00008.jpg",
25
  "A0070302_00201.jpg",
26
+ ],
27
  },
28
  "Letters and snippets (Swedish)": {
29
+ "file": "app/assets/templates/simple.yaml",
30
  "description": "This pipeline works well on letters and other documents with only one text region.",
31
  "examples": [
32
  "451511_1512_01.jpg",
33
  "A0062408_00006.jpg",
34
  "C0000546_00085_crop.png",
35
  "A0073477_00025.jpg",
36
+ ],
37
  },
38
  }
39
 
 
44
  EXAMPLES_DIRECTORY = os.path.join(GRADIO_CACHE, "examples")
45
 
46
  if os.environ.get("GRADIO_CACHE_DIR", GRADIO_CACHE) != GRADIO_CACHE:
47
+ logger.warning("Setting GRADIO_CACHE_DIR to '%s' (overriding a previous value).")
 
 
48
 
49
 
50
  class PipelineWithProgress(Pipeline):
51
  @classmethod
52
  def from_config(cls, config: dict[str, str]):
53
  """Init pipeline from config, ensuring the correct subclass is instantiated."""
54
+ return cls([init_step(step["step"], step.get("settings", {})) for step in config["steps"]])
 
 
 
 
 
55
 
56
  def run(self, collection, start=0, progress=None):
57
  """
 
79
  return collection
80
 
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  def run_htrflow(custom_template_yaml, batch_image_gallery, progress=gr.Progress()):
83
  """
84
  Executes the HTRflow pipeline based on the provided YAML configuration and batch images.
 
97
  gr.Warning(f"HTRflow: Error loading YAML configuration: {e}")
98
  return gr.skip()
99
 
 
 
100
  progress(0, desc="HTRflow: Starting")
101
  time.sleep(0.3)
102
 
 
 
103
  if batch_image_gallery is None:
104
  gr.Warning("HTRflow: You must upload atleast 1 image or more")
105
 
106
  images = [temp_img[0] for temp_img in batch_image_gallery]
107
 
108
+ pipe = PipelineWithProgress.from_config(config)
109
  collections = auto_import(images)
110
 
111
+ gr.Info(f"HTRflow: processing {len(images)} {'image' if len(images) == 1 else 'images'}.")
 
 
112
  progress(0.1, desc="HTRflow: Processing")
113
 
114
  for collection in collections:
115
+ if "labels" in config:
116
+ collection.set_label_format(**config["labels"])
117
 
118
  collection.label = "HTRflow_demo_output"
119
  collection: Collection = pipe.run(collection, progress=progress)
120
 
 
 
 
121
  progress(1, desc="HTRflow: Finish")
122
+ time.sleep(1)
123
  gr.Info("HTRflow: Finish")
124
 
125
+ yield collection, gr.skip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
 
128
  def get_pipeline_description(pipeline: str) -> str:
 
159
  """
160
  Get path to the selected example image.
161
  """
162
+ print([event.value["image"]["path"]])
163
  return [event.value["image"]["path"]]
164
 
165
 
 
173
 
174
 
175
  with gr.Blocks() as submit:
176
+ gr.Markdown("# Upload")
177
+ gr.Markdown("Start Here! ")
178
+ gr.Markdown(
179
+ "First you upload upload 1 image or multiple images (max 5 images). You can also use directly the Image ID from the National Archives of Sweden to request an image"
180
+ )
181
+ gr.Markdown(
182
+ "Afterward, choice a template from the examples based on your material. This will configure a certain pipeline that fits your image."
183
+ )
184
 
185
+ collection_submit_state = gr.State()
186
  with gr.Group():
187
  with gr.Row(equal_height=True):
188
+ with gr.Column(scale=5):
189
+ batch_image_gallery = gr.Gallery(
190
+ file_types=["image"],
191
+ label="Image to transcribe",
192
+ interactive=True,
193
+ object_fit="scale-down",
194
+ scale=3,
195
+ preview=True,
196
+ )
197
+
198
+ with gr.Column(scale=2):
199
+ examples = gr.Gallery(
200
+ all_example_images(),
201
+ label="Examples",
202
+ interactive=False,
203
+ allow_preview=False,
204
+ object_fit="scale-down",
205
+ min_width=250,
206
+ )
207
+
208
+ image_iiif_url = gr.Textbox(
209
+ label="Images from the National Archives of Sweden",
210
+ info="e.g <a href='https://sok.riksarkivet.se/bildvisning/R0002231_00005' target='_blank'>R0002231_00005</a> - Press enter to submit",
211
+ placeholder="R0002231_00005",
212
+ )
213
+
214
+ iiif_image_placeholder = gr.Image(visible=False)
215
 
216
  with gr.Column(variant="panel", elem_classes="pipeline-panel"):
217
  gr.HTML("Pipeline", elem_classes="pipeline-header", padding=False)
218
 
219
  with gr.Row():
220
+ with gr.Column(scale=0):
221
+ pipeline_dropdown = gr.Dropdown(
222
+ PIPELINES,
223
+ container=False,
224
+ min_width=240,
225
+ scale=0,
226
+ elem_classes="pipeline-dropdown",
 
 
 
 
 
 
 
 
 
 
227
  )
228
+ with gr.Column():
229
+ with gr.Row():
230
+ pipeline_description = gr.HTML(
231
+ value=get_pipeline_description,
232
+ inputs=pipeline_dropdown,
233
+ elem_classes="pipeline-description",
234
+ padding=False,
235
+ )
236
+ help_button = gr.Button(
237
+ "Edit Pipeline",
238
+ scale=0,
239
+ )
240
+
241
+ with Modal(visible=False) as help_modal:
242
+ custom_template_yaml = gr.Code(
243
+ value=get_yaml,
244
+ inputs=pipeline_dropdown,
245
+ language="yaml",
246
+ container=False,
247
+ )
248
+ url = "https://ai-riksarkivet.github.io/htrflow/latest/getting_started/pipeline.html#example-pipelines"
249
+ gr.HTML(
250
+ f'See the <a href="{url}">documentation</a> for a detailed description on how to customize HTRflow pipelines.',
251
+ padding=False,
252
+ elem_classes="pipeline-help",
253
+ )
254
 
255
  with gr.Row():
256
  run_button = gr.Button("Submit", variant="primary", scale=0, min_width=200)
257
  progess_bar = gr.Textbox(visible=False, show_label=False)
 
258
 
259
  @batch_image_gallery.upload(
260
  inputs=batch_image_gallery,
 
266
  return gr.update(value=None)
267
  return images
268
 
269
+ def return_iiif_url(image_iiif_url):
270
+ return f"https://lbiiif.riksarkivet.se/arkis!{image_iiif_url}/full/max/0/default.jpg"
271
+
272
+ image_iiif_url.submit(fn=return_iiif_url, inputs=image_iiif_url, outputs=iiif_image_placeholder).then(
273
+ fn=lambda x: [x], inputs=iiif_image_placeholder, outputs=batch_image_gallery
274
+ )
275
+
276
  run_button.click(
277
+ lambda: gr.update(visible=True),
278
+ outputs=[progess_bar],
279
  ).then(
280
  fn=run_htrflow,
281
  inputs=[custom_template_yaml, batch_image_gallery],
282
+ outputs=[collection_submit_state, progess_bar],
283
  ).then(
284
+ lambda: gr.update(visible=False),
285
+ outputs=[progess_bar],
286
  )
287
 
288
  examples.select(get_selected_example_image, None, batch_image_gallery)
289
  examples.select(get_selected_example_pipeline, None, pipeline_dropdown)
290
 
291
+ help_button.click(lambda: Modal(visible=True), None, help_modal)
 
app/tabs/visualizer.py CHANGED
@@ -1,18 +1,22 @@
1
  import gradio as gr
2
  from jinja2 import Environment, FileSystemLoader
3
 
4
-
5
  _ENV = Environment(loader=FileSystemLoader("app/assets/jinja-templates"))
6
  _IMAGE_TEMPLATE = _ENV.get_template("image")
7
  _TRANSCRIPTION_TEMPLATE = _ENV.get_template("transcription")
8
 
9
 
10
  def render_image(collection, current_page_index):
11
- return _IMAGE_TEMPLATE.render(page=collection[current_page_index], lines=collection[current_page_index].traverse(lambda node: node.is_line()))
 
 
 
12
 
13
 
14
  def render_transcription(collection, current_page_index):
15
- regions = collection[current_page_index].traverse(lambda node: node.children and all(child.is_line() for child in node))
 
 
16
  return _TRANSCRIPTION_TEMPLATE.render(regions=regions)
17
 
18
 
@@ -46,7 +50,8 @@ def update_image_caption(collection, current_page_index):
46
 
47
 
48
  with gr.Blocks() as visualizer:
49
-
 
50
  with gr.Row():
51
  # Columns are needed here to get the scale right. The documentation
52
  # claims all components have the `scale` argument but it doesn't
@@ -62,10 +67,10 @@ with gr.Blocks() as visualizer:
62
  gr.Markdown("## Annotated image")
63
  image = gr.HTML(padding=False, elem_classes="svg-image", container=True)
64
 
65
- image_caption = gr.Markdown()
66
- with gr.Row():
67
- left = gr.Button("← Previous", visible=False, interactive=False)
68
- right = gr.Button("Next →", visible=False)
69
 
70
  collection = gr.State()
71
  current_page_index = gr.State(0)
@@ -80,18 +85,34 @@ with gr.Blocks() as visualizer:
80
  # - toggle visibility of navigation buttons (don't show them for single pages)
81
  # - update the image caption
82
  collection.change(render_image, inputs=[collection, current_page_index], outputs=image)
83
- collection.change(render_transcription, inputs=[collection, current_page_index], outputs=transcription)
 
 
 
 
84
  collection.change(lambda _: 0, current_page_index, current_page_index)
85
  collection.change(toggle_navigation_button, collection, left)
86
  collection.change(toggle_navigation_button, collection, right)
87
- collection.change(update_image_caption, inputs=[collection, current_page_index], outputs=image_caption)
 
 
 
 
88
 
89
  # Updates on page change:
90
  # - update the view
91
  # - activate/deactivate buttons
92
  # - update the image caption
93
  current_page_index.change(render_image, inputs=[collection, current_page_index], outputs=image)
94
- current_page_index.change(render_transcription, inputs=[collection, current_page_index], outputs=transcription)
 
 
 
 
95
  current_page_index.change(activate_left_button, current_page_index, left)
96
  current_page_index.change(activate_right_button, [collection, current_page_index], right)
97
- current_page_index.change(update_image_caption, inputs=[collection, current_page_index], outputs=image_caption)
 
 
 
 
 
1
  import gradio as gr
2
  from jinja2 import Environment, FileSystemLoader
3
 
 
4
  _ENV = Environment(loader=FileSystemLoader("app/assets/jinja-templates"))
5
  _IMAGE_TEMPLATE = _ENV.get_template("image")
6
  _TRANSCRIPTION_TEMPLATE = _ENV.get_template("transcription")
7
 
8
 
9
  def render_image(collection, current_page_index):
10
+ return _IMAGE_TEMPLATE.render(
11
+ page=collection[current_page_index],
12
+ lines=collection[current_page_index].traverse(lambda node: node.is_line()),
13
+ )
14
 
15
 
16
  def render_transcription(collection, current_page_index):
17
+ regions = collection[current_page_index].traverse(
18
+ lambda node: node.children and all(child.is_line() for child in node)
19
+ )
20
  return _TRANSCRIPTION_TEMPLATE.render(regions=regions)
21
 
22
 
 
50
 
51
 
52
  with gr.Blocks() as visualizer:
53
+ gr.Markdown("# Results")
54
+ gr.Markdown("Below is the results from the job that were submitted")
55
  with gr.Row():
56
  # Columns are needed here to get the scale right. The documentation
57
  # claims all components have the `scale` argument but it doesn't
 
67
  gr.Markdown("## Annotated image")
68
  image = gr.HTML(padding=False, elem_classes="svg-image", container=True)
69
 
70
+ image_caption = gr.Markdown(elem_classes="button-group-viz")
71
+ with gr.Row(elem_classes="button-group-viz"):
72
+ left = gr.Button("← Previous", visible=False, interactive=False, scale=0)
73
+ right = gr.Button("Next →", visible=False, scale=0)
74
 
75
  collection = gr.State()
76
  current_page_index = gr.State(0)
 
85
  # - toggle visibility of navigation buttons (don't show them for single pages)
86
  # - update the image caption
87
  collection.change(render_image, inputs=[collection, current_page_index], outputs=image)
88
+ collection.change(
89
+ render_transcription,
90
+ inputs=[collection, current_page_index],
91
+ outputs=transcription,
92
+ )
93
  collection.change(lambda _: 0, current_page_index, current_page_index)
94
  collection.change(toggle_navigation_button, collection, left)
95
  collection.change(toggle_navigation_button, collection, right)
96
+ collection.change(
97
+ update_image_caption,
98
+ inputs=[collection, current_page_index],
99
+ outputs=image_caption,
100
+ )
101
 
102
  # Updates on page change:
103
  # - update the view
104
  # - activate/deactivate buttons
105
  # - update the image caption
106
  current_page_index.change(render_image, inputs=[collection, current_page_index], outputs=image)
107
+ current_page_index.change(
108
+ render_transcription,
109
+ inputs=[collection, current_page_index],
110
+ outputs=transcription,
111
+ )
112
  current_page_index.change(activate_left_button, current_page_index, left)
113
  current_page_index.change(activate_right_button, [collection, current_page_index], right)
114
+ current_page_index.change(
115
+ update_image_caption,
116
+ inputs=[collection, current_page_index],
117
+ outputs=image_caption,
118
+ )
pyproject.toml CHANGED
@@ -18,7 +18,7 @@ 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",
 
18
 
19
  dependencies = [
20
  "htrflow==0.2.0",
21
+ "gradio>=5.15.0",
22
  "datasets>=3.2.0",
23
  "pandas>=2.2.3",
24
  "tqdm>=4.67.1",
uv.lock CHANGED
The diff for this file is too large to render. See raw diff