Gabriel commited on
Commit
5ebeb73
0 Parent(s):

bad merge quick fix..

Browse files
Files changed (42) hide show
  1. .dockerignore +13 -0
  2. .gitattributes +35 -0
  3. .github/workflows/sync_to_hub.yml +15 -0
  4. .gitignore +23 -0
  5. Dockerfile +36 -0
  6. Makefile +19 -0
  7. README.md +10 -0
  8. app.py +522 -0
  9. helper/__init__.py +0 -0
  10. helper/examples/__init__.py +0 -0
  11. helper/examples/examples.py +20 -0
  12. helper/examples/images/.gitkeep +0 -0
  13. helper/gradio_config.py +134 -0
  14. helper/text/__init__.py +0 -0
  15. helper/text/text_about.py +72 -0
  16. helper/text/text_app.py +8 -0
  17. helper/text/text_howto.py +94 -0
  18. helper/text/text_riksarkivet.py +10 -0
  19. helper/text/text_roadmap.py +17 -0
  20. models/RmtDet_lines/rtmdet_m_textlines_2_concat.py +580 -0
  21. models/RmtDet_regions/rtmdet_m_textregions_2_concat.py +380 -0
  22. models/SATRN/_base_satrn_shallow_concat.py +318 -0
  23. models/SATRN/dict1700.txt +148 -0
  24. pyproject.toml +80 -0
  25. requirements.txt +18 -0
  26. src/htr_pipeline/__init__.py +0 -0
  27. src/htr_pipeline/gradio_backend.py +143 -0
  28. src/htr_pipeline/inferencer.py +159 -0
  29. src/htr_pipeline/models.py +59 -0
  30. src/htr_pipeline/pipeline.py +70 -0
  31. src/htr_pipeline/utils/__init__.py +0 -0
  32. src/htr_pipeline/utils/filter_segmask.py +127 -0
  33. src/htr_pipeline/utils/helper.py +99 -0
  34. src/htr_pipeline/utils/order_of_object.py +88 -0
  35. src/htr_pipeline/utils/parser_xml.py +76 -0
  36. src/htr_pipeline/utils/preprocess_img.py +19 -0
  37. src/htr_pipeline/utils/process_segmask.py +87 -0
  38. src/htr_pipeline/utils/process_xml.py +150 -0
  39. src/htr_pipeline/utils/templates/arial.ttf +0 -0
  40. src/htr_pipeline/utils/templates/page_xml_2013.xml +30 -0
  41. src/tests/.gitkeep +0 -0
  42. test_api.ipynb +479 -0
.dockerignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .github/
2
+ __pycache__
3
+ *.pyc
4
+ *.pyo
5
+ *.pyd
6
+ .Python
7
+ env
8
+ .env
9
+ Makefile
10
+ page_txt.txt
11
+ page_xml.xml
12
+ helper/text/images/
13
+ helper/text/videos/
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tflite filter=lfs diff=lfs merge=lfs -text
29
+ *.tgz filter=lfs diff=lfs merge=lfs -text
30
+ *.wasm filter=lfs diff=lfs merge=lfs -text
31
+ *.xz filter=lfs diff=lfs merge=lfs -text
32
+ *.zip filter=lfs diff=lfs merge=lfs -text
33
+ *.zst filter=lfs diff=lfs merge=lfs -text
34
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
35
+ helper/text/videos/eating_spaghetti.mp4 filter=lfs diff=lfs merge=lfs -text
.github/workflows/sync_to_hub.yml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face hub
2
+ on: workflow_dispatch
3
+
4
+ jobs:
5
+ sync-to-hub:
6
+ runs-on: ubuntu-latest
7
+ steps:
8
+ - uses: actions/checkout@v3
9
+ with:
10
+ fetch-depth: 0
11
+ lfs: true
12
+ - name: Push to hub
13
+ env:
14
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
15
+ run: git push --force https://Riksarkivet:$HF_TOKEN@huggingface.co/spaces/Riksarkivet/HTR_pipeline main
.gitignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ venv/
2
+ .vscode/
3
+
4
+ # Byte-compiled / optimized / DLL files
5
+ */__pycache__
6
+ __pycache__/
7
+ *.py[cod]
8
+
9
+ vis_data/
10
+ notebooks/
11
+ output/
12
+ my_xml_filename.xml
13
+ models/RmtDet_regions/epoch_12.pth
14
+ models/RmtDet_lines/epoch_12.pth
15
+ models/SATRN/epoch_5.pth
16
+ helper/examples/images/*.jpg
17
+ flagged_data_points/
18
+ src/htr_pipeline.egg-info/
19
+
20
+ #
21
+ page_xml.xml
22
+ page_txt.txt
23
+ transcribed_text.txt
Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+
7
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y
10
+ #RUN apt-get update && apt-get install -y git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 libgl1-mesa-glx \
11
+
12
+ # mim openmmlabs installs
13
+ RUN mim install mmengine
14
+ RUN mim install mmcv
15
+ RUN mim install mmdet
16
+ RUN mim install mmocr
17
+
18
+ # Set up a new user named "user" with user ID 1000
19
+ RUN useradd -m -u 1000 user
20
+ # Switch to the "user" user
21
+ USER user
22
+
23
+ # for localrun
24
+ ENV AM_I_IN_A_DOCKER_CONTAINER Yes
25
+
26
+ # Set home to the user's home directory
27
+ ENV HOME=/home/user \
28
+ PATH=/home/user/.local/bin:$PATH
29
+
30
+ # Set the working directory to the user's home directory
31
+ WORKDIR $HOME/app
32
+
33
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
34
+ COPY --chown=user . $HOME/app
35
+
36
+ CMD ["python", "app.py"]
Makefile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ venv:
2
+ python -m venv venv
3
+
4
+
5
+ activate:
6
+ source ./venv/bin/activate
7
+
8
+ build:
9
+ pip install -e .
10
+ gradio app.py
11
+
12
+ # clean_for_actions:
13
+ # git lfs prune
14
+ # git filter-branch --force --index-filter "git rm --cached --ignore-unmatch helper/text/videos/eating_spaghetti.mp4" --prune-empty --tag-name-filter cat -- --all
15
+ # git push --force origin main
16
+
17
+ # add_space:
18
+ # git remote add demo https://huggingface.co/spaces/Riksarkivet/htr_demo
19
+ # git push --force demo main
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: HTR Pipeline
3
+ emoji: 🏢
4
+ colorFrom: purple
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ models: []
9
+ datasets: []
10
+ ---
app.py ADDED
@@ -0,0 +1,522 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ from helper.examples.examples import ExamplesImages
4
+ from helper.gradio_config import css, js, theme
5
+ from helper.text.text_about import TextAbout
6
+ from helper.text.text_app import TextApp
7
+ from helper.text.text_howto import TextHowTo
8
+ from helper.text.text_riksarkivet import TextRiksarkivet
9
+ from helper.text.text_roadmap import TextRoadmap
10
+ from htr_pipeline.gradio_backend import CustomTrack, FastTrack, SingletonModelLoader
11
+
12
+ model_loader = SingletonModelLoader()
13
+ fast_track = FastTrack(model_loader)
14
+ custom_track = CustomTrack(model_loader)
15
+
16
+ with gr.Blocks(title="HTR Riksarkivet", theme=theme, css=css) as demo:
17
+ gr.Markdown(" ")
18
+ gr.Markdown(TextApp.title_markdown)
19
+
20
+ with gr.Tabs():
21
+ with gr.Tab("HTR Tool"):
22
+ with gr.Row():
23
+ with gr.Column(scale=2):
24
+ with gr.Row():
25
+ fast_track_input_region_image = gr.Image(
26
+ label="Image to run HTR on", type="numpy", tool="editor", elem_id="image_upload"
27
+ ).style(height=395)
28
+
29
+ with gr.Row():
30
+ # with gr.Group():
31
+ # callback = gr.CSVLogger()
32
+ # # hf_writer = gr.HuggingFaceDatasetSaver(HF_API_TOKEN, "htr_pipelin_flags")
33
+ # flagging_button = gr.Button(
34
+ # "Flag",
35
+ # variant="secondary",
36
+ # visible=True,
37
+ # ).style(full_width=True)
38
+ radio_file_input = gr.Radio(
39
+ value="Text file", choices=["Text file", "Page XML"], label="What kind file output?"
40
+ )
41
+
42
+ htr_pipeline_button = gr.Button(
43
+ "Run HTR",
44
+ variant="primary",
45
+ visible=True,
46
+ elem_id="run_pipeline_button",
47
+ ).style(full_width=False)
48
+
49
+ with gr.Group():
50
+ with gr.Row():
51
+ fast_file_downlod = gr.File(label="Download output file", visible=False)
52
+ with gr.Row():
53
+ with gr.Accordion("Example images to use:", open=False) as fast_example_accord:
54
+ fast_name_files_placeholder = gr.Markdown(visible=False)
55
+
56
+ gr.Examples(
57
+ examples=ExamplesImages.example_images_with_info,
58
+ inputs=[fast_track_input_region_image, fast_name_files_placeholder],
59
+ label="Example images",
60
+ examples_per_page=3,
61
+ )
62
+
63
+ with gr.Column(scale=4):
64
+ with gr.Row():
65
+ fast_track_output_image = gr.Image(
66
+ label="HTR results visualizer",
67
+ type="numpy",
68
+ tool="editor",
69
+ ).style(height=650)
70
+
71
+ with gr.Row(visible=False) as api_placeholder:
72
+ htr_pipeline_button_api = gr.Button(
73
+ "Run pipeline",
74
+ variant="primary",
75
+ visible=False,
76
+ ).style(full_width=False)
77
+
78
+ xml_rendered_placeholder_for_api = gr.Textbox(visible=False)
79
+
80
+ with gr.Tab("Stepwise HTR Tool"):
81
+ with gr.Tabs():
82
+ with gr.Tab("1. Region Segmentation"):
83
+ with gr.Row():
84
+ with gr.Column(scale=2):
85
+ name_files_placeholder = gr.Markdown(visible=False)
86
+
87
+ with gr.Row():
88
+ input_region_image = gr.Image(
89
+ label="Image to Region segment",
90
+ # type="numpy",
91
+ tool="editor",
92
+ ).style(height=350)
93
+
94
+ with gr.Accordion("Region segment settings:", open=False):
95
+ with gr.Row():
96
+ reg_pred_score_threshold_slider = gr.Slider(
97
+ minimum=0.4,
98
+ maximum=1,
99
+ value=0.5,
100
+ step=0.05,
101
+ label="P-threshold",
102
+ info="""Filter and determine the confidence score
103
+ required for a prediction score to be considered""",
104
+ )
105
+ reg_containments_threshold_slider = gr.Slider(
106
+ minimum=0,
107
+ maximum=1,
108
+ value=0.5,
109
+ step=0.05,
110
+ label="C-threshold",
111
+ info="""The minimum required overlap or similarity
112
+ for a detected region or object to be considered valid""",
113
+ )
114
+
115
+ with gr.Row():
116
+ region_segment_model_dropdown = gr.Dropdown(
117
+ choices=["Riksarkivet/RmtDet_region"],
118
+ value="Riksarkivet/RmtDet_region",
119
+ label="Region segment model",
120
+ info="Will add more models later!",
121
+ )
122
+
123
+ with gr.Row():
124
+ clear_button = gr.Button("Clear", variant="secondary", elem_id="clear_button")
125
+
126
+ region_segment_button = gr.Button(
127
+ "Segment Region",
128
+ variant="primary",
129
+ elem_id="region_segment_button",
130
+ ) # .style(full_width=False)
131
+
132
+ with gr.Row():
133
+ with gr.Accordion("Example images to use:", open=False) as example_accord:
134
+ gr.Examples(
135
+ examples=ExamplesImages.example_images_with_info,
136
+ inputs=[input_region_image, name_files_placeholder],
137
+ label="Example images",
138
+ examples_per_page=2,
139
+ )
140
+
141
+ with gr.Column(scale=3):
142
+ output_region_image = gr.Image(label="Segmented regions", type="numpy").style(height=600)
143
+
144
+ ##############################################
145
+ with gr.Tab("2. Line Segmentation"):
146
+ image_placeholder_lines = gr.Image(
147
+ label="Segmented lines",
148
+ # type="numpy",
149
+ interactive="False",
150
+ visible=True,
151
+ ).style(height=600)
152
+
153
+ with gr.Row(visible=False) as control_line_segment:
154
+ with gr.Column(scale=2):
155
+ with gr.Box():
156
+ regions_cropped_gallery = gr.Gallery(
157
+ label="Segmented regions",
158
+ show_label=False,
159
+ elem_id="gallery",
160
+ ).style(
161
+ columns=[2],
162
+ rows=[2],
163
+ # object_fit="contain",
164
+ height=300,
165
+ preview=True,
166
+ container=False,
167
+ )
168
+
169
+ input_region_from_gallery = gr.Image(
170
+ label="Region segmentation to line segment", interactive="False", visible=False
171
+ ).style(height=400)
172
+ with gr.Row():
173
+ with gr.Accordion("Line segment settings:", open=False):
174
+ with gr.Row():
175
+ line_pred_score_threshold_slider = gr.Slider(
176
+ minimum=0.3,
177
+ maximum=1,
178
+ value=0.4,
179
+ step=0.05,
180
+ label="Pred_score threshold",
181
+ info="""Filter and determine the confidence score
182
+ required for a prediction score to be considered""",
183
+ )
184
+ line_containments_threshold_slider = gr.Slider(
185
+ minimum=0,
186
+ maximum=1,
187
+ value=0.5,
188
+ step=0.05,
189
+ label="Containments threshold",
190
+ info="""The minimum required overlap or similarity
191
+ for a detected region or object to be considered valid""",
192
+ )
193
+ with gr.Row().style(equal_height=False):
194
+ line_segment_model_dropdown = gr.Dropdown(
195
+ choices=["Riksarkivet/RmtDet_lines"],
196
+ value="Riksarkivet/RmtDet_lines",
197
+ label="Line segment model",
198
+ info="Will add more models later!",
199
+ )
200
+ with gr.Row():
201
+ clear_line_segment_button = gr.Button(
202
+ " ",
203
+ variant="Secondary",
204
+ # elem_id="center_button",
205
+ ).style(full_width=True)
206
+
207
+ line_segment_button = gr.Button(
208
+ "Segment Lines",
209
+ variant="primary",
210
+ # elem_id="center_button",
211
+ ).style(full_width=True)
212
+
213
+ with gr.Column(scale=3):
214
+ # gr.Markdown("""lorem ipsum""")
215
+
216
+ output_line_from_region = gr.Image(
217
+ label="Segmented lines",
218
+ type="numpy",
219
+ interactive="False",
220
+ ).style(height=600)
221
+
222
+ ###############################################
223
+ with gr.Tab("3. Transcribe Text"):
224
+ image_placeholder_htr = gr.Image(
225
+ label="Transcribed lines",
226
+ # type="numpy",
227
+ interactive="False",
228
+ visible=True,
229
+ ).style(height=600)
230
+
231
+ with gr.Row(visible=False) as control_htr:
232
+ inputs_lines_to_transcribe = gr.Variable()
233
+
234
+ with gr.Column(scale=2):
235
+ image_inputs_lines_to_transcribe = gr.Image(
236
+ label="Transcribed lines",
237
+ type="numpy",
238
+ interactive="False",
239
+ visible=False,
240
+ ).style(height=470)
241
+
242
+ with gr.Row():
243
+ with gr.Accordion("Transcribe settings:", open=False):
244
+ transcriber_model = gr.Dropdown(
245
+ choices=["Riksarkivet/SATRN_transcriber", "microsoft/trocr-base-handwritten"],
246
+ value="Riksarkivet/SATRN_transcriber",
247
+ label="Transcriber model",
248
+ info="Will add more models later!",
249
+ )
250
+ with gr.Row():
251
+ clear_transcribe_button = gr.Button(" ", variant="Secondary", visible=True).style(
252
+ full_width=True
253
+ )
254
+ transcribe_button = gr.Button(
255
+ "Transcribe lines", variant="primary", visible=True
256
+ ).style(full_width=True)
257
+
258
+ donwload_txt_button = gr.Button(
259
+ "Download text", variant="secondary", visible=False
260
+ ).style(full_width=True)
261
+
262
+ with gr.Row():
263
+ txt_file_downlod = gr.File(label="Download text", visible=False)
264
+
265
+ with gr.Column(scale=3):
266
+ with gr.Row():
267
+ transcribed_text_df = gr.Dataframe(
268
+ headers=["Transcribed text"],
269
+ max_rows=15,
270
+ col_count=(1, "fixed"),
271
+ wrap=True,
272
+ interactive=False,
273
+ overflow_row_behaviour="paginate",
274
+ ).style(height=600)
275
+
276
+ #####################################
277
+ with gr.Tab("4. Explore Results"):
278
+ image_placeholder_explore_results = gr.Image(
279
+ label="Cropped transcribed lines",
280
+ # type="numpy",
281
+ interactive="False",
282
+ visible=True,
283
+ ).style(height=600)
284
+
285
+ with gr.Row(visible=False) as control_results_transcribe:
286
+ with gr.Column(scale=1, visible=True):
287
+ with gr.Box():
288
+ temp_gallery_input = gr.Variable()
289
+
290
+ gallery_inputs_lines_to_transcribe = gr.Gallery(
291
+ label="Cropped transcribed lines",
292
+ show_label=True,
293
+ elem_id="gallery_lines",
294
+ ).style(
295
+ columns=[3],
296
+ rows=[3],
297
+ # object_fit="contain",
298
+ # height="600",
299
+ preview=True,
300
+ container=False,
301
+ )
302
+ with gr.Column(scale=1, visible=True):
303
+ mapping_dict = gr.Variable()
304
+ transcribed_text_df_finish = gr.Dataframe(
305
+ headers=["Transcribed text", "HTR prediction score"],
306
+ max_rows=15,
307
+ col_count=(2, "fixed"),
308
+ wrap=True,
309
+ interactive=False,
310
+ overflow_row_behaviour="paginate",
311
+ ).style(height=600)
312
+
313
+ with gr.Tab("How to use"):
314
+ with gr.Tabs():
315
+ with gr.Tab("HTR Tool"):
316
+ with gr.Row().style(equal_height=False):
317
+ with gr.Column():
318
+ gr.Markdown(TextHowTo.htr_tool)
319
+ with gr.Column():
320
+ gr.Markdown(TextHowTo.both_htr_tool_video)
321
+ gr.Video(
322
+ value="https://github.com/Borg93/htr_gradio_file_placeholder/raw/main/eating_spaghetti.mp4",
323
+ label="How to use HTR Tool",
324
+ )
325
+ gr.Markdown(TextHowTo.reach_out)
326
+
327
+ with gr.Tab("Stepwise HTR Tool"):
328
+ with gr.Row().style(equal_height=False):
329
+ with gr.Column():
330
+ gr.Markdown(TextHowTo.stepwise_htr_tool)
331
+ with gr.Row():
332
+ with gr.Accordion("The tabs for the Stepwise HTR Tool:", open=False):
333
+ with gr.Tabs():
334
+ with gr.Tab("1. Region Segmentation"):
335
+ gr.Markdown(TextHowTo.stepwise_htr_tool_tab1)
336
+ with gr.Tab("2. Line Segmentation"):
337
+ gr.Markdown(TextHowTo.stepwise_htr_tool_tab2)
338
+ with gr.Tab("3. Transcribe Text"):
339
+ gr.Markdown(TextHowTo.stepwise_htr_tool_tab3)
340
+ with gr.Tab("4. Explore Results"):
341
+ gr.Markdown(TextHowTo.stepwise_htr_tool_tab4)
342
+ gr.Markdown(TextHowTo.stepwise_htr_tool_end)
343
+ with gr.Column():
344
+ gr.Markdown(TextHowTo.both_htr_tool_video)
345
+ gr.Video(
346
+ value="https://github.com/Borg93/htr_gradio_file_placeholder/raw/main/eating_spaghetti.mp4",
347
+ label="How to use Stepwise HTR Tool",
348
+ )
349
+ gr.Markdown(TextHowTo.reach_out)
350
+
351
+ with gr.Tab("About"):
352
+ with gr.Tabs():
353
+ with gr.Tab("Project"):
354
+ with gr.Row():
355
+ with gr.Column():
356
+ gr.Markdown(TextAbout.intro_and_pipeline_overview_text)
357
+ with gr.Row():
358
+ with gr.Tabs():
359
+ with gr.Tab("I. Binarization"):
360
+ gr.Markdown(TextAbout.binarization)
361
+ with gr.Tab("II. Region Segmentation"):
362
+ gr.Markdown(TextAbout.text_region_segment)
363
+ with gr.Tab("III. Line Segmentation"):
364
+ gr.Markdown(TextAbout.text_line_segmentation)
365
+ with gr.Tab("IV. Transcriber"):
366
+ gr.Markdown(TextAbout.text_htr)
367
+ with gr.Row():
368
+ gr.Markdown(TextAbout.text_data)
369
+
370
+ with gr.Column():
371
+ gr.Markdown(TextAbout.filler_text_data)
372
+ gr.Markdown(TextAbout.text_models)
373
+ with gr.Row():
374
+ with gr.Tabs():
375
+ with gr.Tab("Region Segmentation"):
376
+ gr.Markdown(TextAbout.text_models_region)
377
+ with gr.Tab("Line Segmentation"):
378
+ gr.Markdown(TextAbout.text_line_segmentation)
379
+ with gr.Tab("Transcriber"):
380
+ gr.Markdown(TextAbout.text_models_htr)
381
+
382
+ with gr.Tab("Roadmap"):
383
+ with gr.Row():
384
+ with gr.Column():
385
+ gr.Markdown(TextRoadmap.roadmap)
386
+ with gr.Column():
387
+ gr.Markdown(TextRoadmap.notebook)
388
+
389
+ with gr.Tab("Riksarkivet"):
390
+ with gr.Row():
391
+ with gr.Column():
392
+ gr.Markdown(TextRiksarkivet.riksarkivet)
393
+ with gr.Column():
394
+ gr.Markdown(TextRiksarkivet.contact)
395
+
396
+ htr_pipeline_button.click(
397
+ fast_track.segment_to_xml,
398
+ inputs=[fast_track_input_region_image, radio_file_input],
399
+ outputs=[fast_track_output_image, fast_file_downlod, fast_file_downlod],
400
+ )
401
+
402
+ htr_pipeline_button_api.click(
403
+ fast_track.segment_to_xml_api,
404
+ inputs=[fast_track_input_region_image],
405
+ outputs=[xml_rendered_placeholder_for_api],
406
+ api_name="predict",
407
+ )
408
+
409
+ # fast_track_input_region_image.change(
410
+ # fn=lambda: (gr.Accordion.update(open=False)),
411
+ # outputs=[fast_example_accord],
412
+ # )
413
+
414
+ # input_region_image.change(
415
+ # fn=lambda: (gr.Accordion.update(open=False)),
416
+ # outputs=[example_accord],
417
+ # )
418
+
419
+ # callback.setup([fast_track_input_region_image], "flagged_data_points")
420
+ # flagging_button.click(lambda *args: callback.flag(args), [fast_track_input_region_image], None, preprocess=False)
421
+ # flagging_button.click(lambda: (gr.update(value="Flagged")), outputs=flagging_button)
422
+ # fast_track_input_region_image.change(lambda: (gr.update(value="Flag")), outputs=flagging_button)
423
+
424
+ # custom track
425
+ region_segment_button.click(
426
+ custom_track.region_segment,
427
+ inputs=[input_region_image, reg_pred_score_threshold_slider, reg_containments_threshold_slider],
428
+ outputs=[output_region_image, regions_cropped_gallery, image_placeholder_lines, control_line_segment],
429
+ )
430
+
431
+ regions_cropped_gallery.select(
432
+ custom_track.get_select_index_image, regions_cropped_gallery, input_region_from_gallery
433
+ )
434
+
435
+ transcribed_text_df_finish.select(
436
+ fn=custom_track.get_select_index_df,
437
+ inputs=[transcribed_text_df_finish, mapping_dict],
438
+ outputs=gallery_inputs_lines_to_transcribe,
439
+ )
440
+
441
+ line_segment_button.click(
442
+ custom_track.line_segment,
443
+ inputs=[input_region_from_gallery, line_pred_score_threshold_slider, line_containments_threshold_slider],
444
+ outputs=[
445
+ output_line_from_region,
446
+ image_inputs_lines_to_transcribe,
447
+ inputs_lines_to_transcribe,
448
+ gallery_inputs_lines_to_transcribe,
449
+ temp_gallery_input,
450
+ # Hide
451
+ transcribe_button,
452
+ image_inputs_lines_to_transcribe,
453
+ image_placeholder_htr,
454
+ control_htr,
455
+ ],
456
+ )
457
+
458
+ transcribe_button.click(
459
+ custom_track.transcribe_text,
460
+ inputs=[transcribed_text_df, inputs_lines_to_transcribe],
461
+ outputs=[
462
+ transcribed_text_df,
463
+ transcribed_text_df_finish,
464
+ mapping_dict,
465
+ txt_file_downlod,
466
+ control_results_transcribe,
467
+ image_placeholder_explore_results,
468
+ ],
469
+ )
470
+
471
+ donwload_txt_button.click(
472
+ custom_track.download_df_to_txt,
473
+ inputs=transcribed_text_df,
474
+ outputs=[txt_file_downlod, txt_file_downlod],
475
+ )
476
+
477
+ clear_button.click(
478
+ lambda: (
479
+ None,
480
+ None,
481
+ None,
482
+ gr.update(visible=False),
483
+ None,
484
+ None,
485
+ None,
486
+ gr.update(visible=False),
487
+ gr.update(visible=False),
488
+ gr.update(visible=True),
489
+ None,
490
+ gr.update(visible=False),
491
+ gr.update(visible=False),
492
+ gr.update(visible=True),
493
+ gr.update(visible=True),
494
+ ),
495
+ inputs=[],
496
+ outputs=[
497
+ input_region_image,
498
+ regions_cropped_gallery,
499
+ input_region_from_gallery,
500
+ control_line_segment,
501
+ output_line_from_region,
502
+ inputs_lines_to_transcribe,
503
+ transcribed_text_df,
504
+ control_htr,
505
+ inputs_lines_to_transcribe,
506
+ image_placeholder_htr,
507
+ output_region_image,
508
+ image_inputs_lines_to_transcribe,
509
+ control_results_transcribe,
510
+ image_placeholder_explore_results,
511
+ image_placeholder_lines,
512
+ ],
513
+ )
514
+
515
+ demo.load(None, None, None, _js=js)
516
+
517
+
518
+ demo.queue(concurrency_count=5, max_size=20)
519
+
520
+
521
+ if __name__ == "__main__":
522
+ demo.launch(server_name="0.0.0.0", server_port=7860, show_api=False, show_error=True)
helper/__init__.py ADDED
File without changes
helper/examples/__init__.py ADDED
File without changes
helper/examples/examples.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ExamplesImages:
2
+ image_path = "./helper/examples/images"
3
+ example_images_with_info = [
4
+ [f"{image_path}/1664-Handelskollegiet_A1_0014full.jpg", "1664 HandelsKollegiet"],
5
+ [
6
+ f"{image_path}/1735-Södra_förstadens_kämnärsrätt_00042-scan_2020-10-13_14-03-37.jpg",
7
+ "1735 Södra förstadens kämnärsrätt",
8
+ ],
9
+ [f"{image_path}/1777-Hall-_och_Manufakturrätten_HallMan_Sida_03.jpg", "1777 Hall och Manufakturrätten"],
10
+ [f"{image_path}/1840-1890_H0000304_00034.jpg", "1840-1890 --"],
11
+ [f"{image_path}/1861_R0000277_00153.jpg", "1861 --"],
12
+ [f"{image_path}/1664-Handelskollegiet_A1_0014full.jpg", "1664 HandelsKollegiet"],
13
+ [
14
+ f"{image_path}/1735-Södra_förstadens_kämnärsrätt_00042-scan_2020-10-13_14-03-37.jpg",
15
+ "1735 Södra förstadens kämnärsrätt",
16
+ ],
17
+ [f"{image_path}/1777-Hall-_och_Manufakturrätten_HallMan_Sida_03.jpg", "1777 Hall och Manufakturrätten"],
18
+ [f"{image_path}/1840-1890_H0000304_00034.jpg", "1840-1890 --"],
19
+ [f"{image_path}/1861_R0000277_00153.jpg", "1861 --"],
20
+ ]
helper/examples/images/.gitkeep ADDED
File without changes
helper/gradio_config.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+
4
+ class GradioConfig:
5
+ def __init__(self, tooltip_dict):
6
+ self.tooltip_dict = tooltip_dict
7
+ self.theme = gr.themes.Base(
8
+ primary_hue="blue",
9
+ secondary_hue="blue",
10
+ neutral_hue="slate",
11
+ font=[
12
+ gr.themes.GoogleFont("Open Sans"),
13
+ "ui-sans-serif",
14
+ "system-ui",
15
+ "sans-serif",
16
+ ],
17
+ )
18
+ self.css = """
19
+ footer {display: none !important;}
20
+ #image_upload {min-height:450}
21
+ #image_upload [data-testid="image"], #image_upload [data-testid="image"] > div{min-height: 450px}
22
+ #gallery {height: 400px}
23
+ .fixed-height.svelte-g4rw9.svelte-g4rw9 {min-height: 400px;}
24
+ """
25
+
26
+ def generate_tooltip_css(self):
27
+ temp_css_list = [self.css]
28
+ for button_id, tooltip_text in self.tooltip_dict.items():
29
+ temp_css_list.append(self.template_tooltip_css(button_id, tooltip_text))
30
+
31
+ return "\n".join(temp_css_list)
32
+
33
+ def template_tooltip_css(self, button_id, tooltip_text):
34
+ return f"""
35
+ /* For tooltip */
36
+ #{button_id} {{
37
+ position: relative;
38
+ }}
39
+
40
+ #{button_id}::before {{
41
+ visibility: hidden;
42
+ content: '';
43
+ position: absolute;
44
+ bottom: 100%; /* Position on top of the parent element */
45
+ left: 50%;
46
+ margin-left: 5px; /* Adjust for the desired space between the button and tooltip */
47
+ transform: translateY(-50%);
48
+ border-width: 7px;
49
+ border-style: solid;
50
+ border-color: rgba(51, 51, 51, 0) transparent transparent rgba(51, 51, 51, 0);
51
+ transition: opacity 0.4s ease-in-out, border-color 0.4s ease-in-out;
52
+ opacity: 0;
53
+ z-index: 999;
54
+ }}
55
+
56
+ #{button_id}::after {{
57
+ visibility: hidden;
58
+ content: '{tooltip_text}';
59
+ position: absolute;
60
+ bottom: 100%; /* Position on top of the parent element */
61
+ left: 42%;
62
+ background-color: rgba(51, 51, 51, 0);
63
+ color: white;
64
+ padding: 5px;
65
+ border-radius: 3px;
66
+ z-index: 998;
67
+ opacity: 0;
68
+ transition: opacity 0.4s ease-in-out, background-color 0.4s ease-in-out;
69
+ margin-bottom: 20px !important; /* Increased from 18px to 23px to move tooltip 5px upwards */
70
+ margin-left: 0px; /* Adjust for the arrow width and the desired space between the arrow and tooltip */
71
+ white-space: normal; /* Allows the text to wrap */
72
+ width: 200px; /* Maximum line length before wrapping */
73
+ box-sizing: border-box;
74
+ }}
75
+
76
+ #{button_id}.showTooltip::before {{
77
+ visibility: visible;
78
+ opacity: 1;
79
+ border-color: rgba(51, 51, 51, 0.7) transparent transparent rgba(51, 51, 51, 0.7);
80
+ }}
81
+
82
+ #{button_id}.showTooltip::after {{
83
+ visibility: visible;
84
+ opacity: 1;
85
+ background-color: rgba(51, 51, 51, 0.7);
86
+ }}
87
+ """
88
+
89
+ def add_interaction_to_buttons(self):
90
+ button_ids_list = ", ".join([f"'#{id}'" for id, _ in self.tooltip_dict.items()])
91
+ button_ids = button_ids_list.replace("'", "")
92
+ return f"""
93
+ function monitorButtonHover() {{
94
+
95
+ gradioURL = window.location.href
96
+ if (!gradioURL.endsWith('?__theme=dark')) {{
97
+ window.location.replace(gradioURL + '?__theme=dark');
98
+ }}
99
+
100
+ const buttons = document.querySelectorAll('{button_ids}');
101
+ buttons.forEach(function(button) {{
102
+ button.addEventListener('mouseenter', function() {{
103
+ this.classList.add('showTooltip');
104
+ }});
105
+
106
+ button.addEventListener('mouseleave', function() {{
107
+ this.classList.remove('showTooltip');
108
+ }});
109
+ }})
110
+ }}
111
+ """
112
+
113
+
114
+ buttons_with_tooltip = {
115
+ "run_pipeline_button": "Runs HTR on the image. Takes approx 1-2 mins per image (depending on hardware).",
116
+ "clear_button": "Clears all states and resets the entire workflow in the stepwise tool.",
117
+ "region_segment_button": "Segments text regions in the chosen image with the chosen settings.",
118
+ "line_segment_button": "Segments chosen regions from the image gallery into lines segments.",
119
+ "transcribe_button": "Transcribes each line segment into text and streams back the data.",
120
+ }
121
+ gradio_config = GradioConfig(buttons_with_tooltip)
122
+
123
+ theme = gradio_config.theme
124
+ css = gradio_config.generate_tooltip_css()
125
+ js = gradio_config.add_interaction_to_buttons()
126
+
127
+
128
+ if __name__ == "__main__":
129
+ tooltip = GradioConfig({"run_pipeline_button": "this is a tooltop", "clear_button": "this is a tooltop"})
130
+ css = tooltip.generate_tooltip_css()
131
+ js = tooltip.add_interaction_to_buttons()
132
+
133
+ print(css)
134
+ print(js)
helper/text/__init__.py ADDED
File without changes
helper/text/text_about.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class TextAbout:
2
+ # About text
3
+ intro_and_pipeline_overview_text = """
4
+
5
+ ## Introduction
6
+ The Swedish National Archives presents an end-to-end HTR-pipeline consisting of two RTMDet instance segmentation models, trained with MMDetection, one for segmenting text-regions, and one for segmenting text-lines within these regions, and one SATRN HTR-model trained with MMOCR. The aim is for a generic pipline for running-text documents ranging from 1600 to 1900. We will retrain and update the models continually as more data becomes avaialable. Feel free to try out the pipline yourself in our interactive demo (reference).
7
+
8
+ ## The Pipeline in Overview
9
+
10
+ The steps in the pipeline are as follows:
11
+ """
12
+
13
+ binarization = """
14
+
15
+ ### Binarization
16
+ The reason for binarizing the images before processing them is that we want the models to generalize as well as possible.
17
+ By training on only binarized images and by binarizing images before running them through the pipeline, we take the target domain closer to the training domain, and ruduce negative effects of background variation, background noise etc., on the final results.
18
+ The pipeline implements a simple adaptive thresholding algorithm for binarization.
19
+ """
20
+ text_region_segment = """
21
+ ### Text-region segmentation
22
+ To facilitate the text-line segmentation process, it is advantageous to segment the image into text-regions beforehand. This initial step offers several benefits, including reducing variations in line spacing, eliminating blank areas on the page, establishing a clear reading order, and distinguishing marginalia from the main text.
23
+ The segmentation model utilized in this process predicts both bounding boxes and masks. Although the model has the capability to predict both, only the masks are utilized for the segmentation tasks of lines and regions.
24
+ An essential post-processing step involves checking for regions that are contained within other regions. During this step, only the containing region is retained, while the contained region is discarded. This ensures that the final segmented text-regions are accurate and devoid of overlapping or redundant areas.
25
+ """
26
+ text_line_segmentation = """
27
+ ### Text-line segmentation
28
+
29
+ This is also an RTMDet model that's trained on extracting text-lines from cropped text-regions within an image.
30
+ The same post-processing on the instance segmentation masks is done here as in the text-region segmentation step.
31
+ """
32
+ text_htr = """
33
+ ### HTR
34
+
35
+ For the text-recognition a SATRN model (reference) was trained with mmocr on approximately one million handwritten text-line images ranging from 1600 to 1900.
36
+ It was trained on a wide variety of archival material to make it generalize as well as possible. See below for detailed evaluation results, and also some finetuning experiments.
37
+ """
38
+
39
+ text_data = """
40
+ ## The Data
41
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
42
+
43
+ """
44
+
45
+ filler_text_data = """
46
+ ##  
47
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
48
+ """
49
+
50
+ text_models = """
51
+ ## The Models
52
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
53
+
54
+ """
55
+
56
+ text_models_region = """
57
+
58
+ ### Text-Region Segmentation
59
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
60
+ """
61
+
62
+ text_models_lines = """
63
+ ### Text-Line Segmentation
64
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
65
+
66
+ """
67
+
68
+ text_models_htr = """
69
+ ### HTR
70
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
71
+
72
+ """
helper/text/text_app.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ class TextApp:
2
+ title_markdown = """
3
+
4
+ <img src="https://raw.githubusercontent.com/Borg93/Riksarkivet_docs/main/docs/assets/fav-removebg-preview.png" width="4%" align="right" margin-right="100" />
5
+
6
+ <h1><center> Handwritten Text Recognition Tool </center></h1>
7
+
8
+ <h3><center> Swedish National Archives - Riksarkivet </center></h3>"""
helper/text/text_howto.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class TextHowTo:
2
+ htr_tool = """
3
+ ## Getting Started with the HTR Tool
4
+ To quickly run the HTR Tool and transcribe handwritten text, follow these steps:
5
+ 1. Open the HTR Tool tab.
6
+ 2. Upload an image or choose an image from the provided Examples (under "Example images to use:" accordin).
7
+ Note that the accordin works like a "dropdown" and that you just need to press an example to use it (also, use the pagniation at the bottom to view more examples).
8
+ 3. The radio button specifes the output file extension, which can be either text or page XML.
9
+ 4. Click the "Run HTR" button to initiate the HTR process. You can refer to the screenshot below:
10
+ <figure>
11
+ <img src="https://raw.githubusercontent.com/Borg93/htr_gradio_file_placeholder/main/htr_run_example.png" alt="HTR_tool" style="width:65%; display: block; margin-left: auto; margin-right:auto;" >
12
+ <figcaption style="text-align: center;"> <em> Figure - How to Run the HTR Tool </em></figcaption>
13
+ </figure>
14
+ The HTR Tool will transform an image of handwritten text into structured, transcribed text within approximately 1-2 minutes (depending on your hardware).
15
+ Note that the generated page XML file is strucutred in such manner that it allows for an easy integration with other software, such as Transkribus.
16
+ <br>
17
+ """
18
+ reach_out = """ Feel free to reach out if you have any questions or need further assistance!
19
+
20
+ """
21
+
22
+ stepwise_htr_tool = """
23
+ ## Stepwise HTR Tool
24
+
25
+ The Stepwise HTR Tool is a powerful tool for performing Handwritten Text Recognition (HTR) tasks. The Stepwise version provides you with fine-grained control over each step of the HTR process, allowing for greater customization and troubleshooting capabilities.
26
+ With the Stepwise HTR Tool, you can break down the HTR process into distinct steps: region segmentation, line segmentation, text transcription, and result exploration.
27
+ This tool offers a range of configuration options to tailor the HTR process to your specific needs. You can adjust settings such as P-threshold and C-threshold to fine-tune the region and line segmentation, and choose from a selection of underlying machine learning models to drive each step of the process.
28
+ The Stepwise HTR Tool also provides a dedicated Explore Results tab, allowing you to thoroughly analyze and interact with the transcriptions. You can sort and identify both bad and good predictions, helping you gain insights and make improvements to the HTR accuracy.
29
+ Each step is interconnected, and the output of one step serves as the input for the next step, ensuring a seamless and efficient workflow.
30
+ <br><br>
31
+
32
+ Follow the instructions below provided in each tab to perform the respective step of the HTR process and ensure you work through the tabs sequentially:
33
+ """
34
+
35
+ stepwise_htr_tool_tab1 = """
36
+ ### Tab 1: Region Segmentation
37
+ The Region Segmentation tab allows you to perform the initial step of segmenting the handwritten text into regions of interest. By adjusting the P-threshold and C-threshold settings, you can control the confidence score required for a prediction and the minimum overlap or similarity for a detected region to be considered valid. Additionally, you can select an underlying machine learning model for region segmentation.
38
+ <br><br>
39
+ To perform region segmentation, follow these steps:
40
+ 1. Open the "Region Segmentation" tab.
41
+ 2. Upload an image or choose an image from the provided Examples (under "Example images to use:" accordin).
42
+ 3. Configure the region segmentation settings:
43
+ - Adjust the P-threshold: Filter and determine the confidence score required for a prediction score to be considered.
44
+ - Adjust the C-threshold: Set the minimum required overlap or similarity for a detected region or object to be considered valid.
45
+ - Select an underlying machine learning model.
46
+ 4. Click the "Run Region Segmentation" button to initiate the region segmentation process.
47
+ """
48
+ stepwise_htr_tool_tab2 = """
49
+
50
+ ### Tab 2: Line Segmentation
51
+ In the Line Segmentation tab, you can further refine the segmentation process by identifying individual lines of text.
52
+ Similar to the Region Segmentation tab, you can adjust the P-threshold and C-threshold settings for line segmentation and choose an appropriate machine learning model.
53
+ <br><br>
54
+ To perform line segmentation, follow these steps:
55
+ 1. Open the "Line Segmentation" tab.
56
+ 2. Choice a segmented region from image gallery, which populated with the results from the previous tab.
57
+ 3. Configure the line segmentation settings:
58
+ - Adjust the P-threshold: Filter and determine the confidence score required for a prediction score to be considered.
59
+ - Adjust the C-threshold: Set the minimum required overlap or similarity for a detected region or object to be considered valid.
60
+ - Select an underlying machine learning model.
61
+ 4. Click the "Run Line Segmentation" button to initiate the line segmentation process.
62
+ """
63
+
64
+ stepwise_htr_tool_tab3 = """
65
+ ### Tab 3: Transcribe Text
66
+ The Transcribe Text tab allows you to convert the segmented text into transcriptions. Here, you can select the desired machine learning model for text transcription.
67
+ <br><br>
68
+ To transcribe text, follow these steps:
69
+ 1. Open the "Transcribe Text" tab.
70
+ 2. The image to transcribe is predefined with the results from the previous tab.
71
+ 3. Configure the text transcription settings:
72
+ - Select an underlying machine learning model.
73
+ 4. Click the "Run Text Transcription" button to initiate the text transcription process.
74
+ """
75
+
76
+ stepwise_htr_tool_tab4 = """
77
+ ### Tab 4: Explore Results
78
+ Once the transcription is complete, you can explore the results in the Explore Results tab. This tab provides various features for analyzing and interacting with the transcriptions, allowing you to sort and identify both bad and good predictions.
79
+ <br><br>
80
+ To explore the HTR results, follow these steps:
81
+ 1. Open the "Explore Results" tab.
82
+ 2. Analyze the generated results. The image gallery of cropped text line segments is bi-directional coupled through interaction with the dataframe on the left.
83
+ 3. Use the provided features, such as the prediction score to sort and interact with the image gallery, identifying both bad and good transcriptions.
84
+ """
85
+
86
+ stepwise_htr_tool_end = """
87
+ As mentioned, please note that each tab in this workflow is dependent on the previous steps, where you progressively work through the process in a step-by-step manner.
88
+ <br>
89
+ """
90
+
91
+ both_htr_tool_video = """
92
+ ## &nbsp;
93
+ Alternatively, you can watch the instructional video below, which provides a step-by-step walkthrough of the HTR Tool and some additional features.
94
+ """
helper/text/text_riksarkivet.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ class TextRiksarkivet:
2
+ riksarkivet = """
3
+ ## Riksarkivet
4
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
5
+ """
6
+
7
+ contact = """
8
+ ## Contact us
9
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
10
+ """
helper/text/text_roadmap.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class TextRoadmap:
2
+ roadmap = """
3
+
4
+ ## Roadmap
5
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
6
+ """
7
+
8
+ notebook = """
9
+
10
+ ## Using the models
11
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
12
+
13
+ ## Implementing the Whole Pipeline
14
+ * add notebook as an example (for api)
15
+
16
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
17
+ """
models/RmtDet_lines/rtmdet_m_textlines_2_concat.py ADDED
@@ -0,0 +1,580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default_scope = 'mmdet'
2
+ default_hooks = dict(
3
+ timer=dict(type='IterTimerHook'),
4
+ logger=dict(type='LoggerHook', interval=100),
5
+ param_scheduler=dict(type='ParamSchedulerHook'),
6
+ checkpoint=dict(
7
+ type='CheckpointHook', interval=1, max_keep_ckpts=5, save_best='auto'),
8
+ sampler_seed=dict(type='DistSamplerSeedHook'),
9
+ visualization=dict(type='DetVisualizationHook'))
10
+ env_cfg = dict(
11
+ cudnn_benchmark=False,
12
+ mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0),
13
+ dist_cfg=dict(backend='nccl'))
14
+ vis_backends = [dict(type='LocalVisBackend')]
15
+ visualizer = dict(
16
+ type='DetLocalVisualizer',
17
+ vis_backends=[dict(type='LocalVisBackend')],
18
+ name='visualizer',
19
+ save_dir='./')
20
+ log_processor = dict(type='LogProcessor', window_size=50, by_epoch=True)
21
+ log_level = 'INFO'
22
+ load_from = '/home/erik/Riksarkivet/Projects/HTR_Pipeline/models/checkpoints/rtmdet_lines_pr_2/epoch_11.pth'
23
+ resume = True
24
+ train_cfg = dict(
25
+ type='EpochBasedTrainLoop',
26
+ max_epochs=12,
27
+ val_interval=12,
28
+ dynamic_intervals=[(10, 1)])
29
+ val_cfg = dict(type='ValLoop')
30
+ test_cfg = dict(
31
+ type='TestLoop',
32
+ pipeline=[
33
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
34
+ dict(type='Resize', scale=(640, 640), keep_ratio=True),
35
+ dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
36
+ dict(
37
+ type='PackDetInputs',
38
+ meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
39
+ 'scale_factor'))
40
+ ])
41
+ param_scheduler = [
42
+ dict(
43
+ type='LinearLR', start_factor=1e-05, by_epoch=False, begin=0,
44
+ end=1000),
45
+ dict(
46
+ type='CosineAnnealingLR',
47
+ eta_min=1.25e-05,
48
+ begin=6,
49
+ end=12,
50
+ T_max=6,
51
+ by_epoch=True,
52
+ convert_to_iter_based=True)
53
+ ]
54
+ optim_wrapper = dict(
55
+ type='OptimWrapper',
56
+ optimizer=dict(type='AdamW', lr=0.00025, weight_decay=0.05),
57
+ paramwise_cfg=dict(
58
+ norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True))
59
+ auto_scale_lr = dict(enable=False, base_batch_size=16)
60
+ dataset_type = 'CocoDataset'
61
+ data_root = 'data/coco/'
62
+ file_client_args = dict(backend='disk')
63
+ train_pipeline = [
64
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
65
+ dict(
66
+ type='LoadAnnotations',
67
+ with_bbox=True,
68
+ with_mask=True,
69
+ poly2mask=False),
70
+ dict(type='CachedMosaic', img_scale=(640, 640), pad_val=114.0),
71
+ dict(
72
+ type='RandomResize',
73
+ scale=(1280, 1280),
74
+ ratio_range=(0.1, 2.0),
75
+ keep_ratio=True),
76
+ dict(
77
+ type='RandomCrop',
78
+ crop_size=(640, 640),
79
+ recompute_bbox=True,
80
+ allow_negative_crop=True),
81
+ dict(type='YOLOXHSVRandomAug'),
82
+ dict(type='RandomFlip', prob=0.5),
83
+ dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
84
+ dict(
85
+ type='CachedMixUp',
86
+ img_scale=(640, 640),
87
+ ratio_range=(1.0, 1.0),
88
+ max_cached_images=20,
89
+ pad_val=(114, 114, 114)),
90
+ dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)),
91
+ dict(type='PackDetInputs')
92
+ ]
93
+ test_pipeline = [
94
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
95
+ dict(type='Resize', scale=(640, 640), keep_ratio=True),
96
+ dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
97
+ dict(
98
+ type='PackDetInputs',
99
+ meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
100
+ 'scale_factor'))
101
+ ]
102
+ tta_model = dict(
103
+ type='DetTTAModel',
104
+ tta_cfg=dict(nms=dict(type='nms', iou_threshold=0.6), max_per_img=100))
105
+ img_scales = [(640, 640), (320, 320), (960, 960)]
106
+ tta_pipeline = [
107
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
108
+ dict(
109
+ type='TestTimeAug',
110
+ transforms=[[{
111
+ 'type': 'Resize',
112
+ 'scale': (640, 640),
113
+ 'keep_ratio': True
114
+ }, {
115
+ 'type': 'Resize',
116
+ 'scale': (320, 320),
117
+ 'keep_ratio': True
118
+ }, {
119
+ 'type': 'Resize',
120
+ 'scale': (960, 960),
121
+ 'keep_ratio': True
122
+ }],
123
+ [{
124
+ 'type': 'RandomFlip',
125
+ 'prob': 1.0
126
+ }, {
127
+ 'type': 'RandomFlip',
128
+ 'prob': 0.0
129
+ }],
130
+ [{
131
+ 'type': 'Pad',
132
+ 'size': (960, 960),
133
+ 'pad_val': {
134
+ 'img': (114, 114, 114)
135
+ }
136
+ }],
137
+ [{
138
+ 'type':
139
+ 'PackDetInputs',
140
+ 'meta_keys':
141
+ ('img_id', 'img_path', 'ori_shape', 'img_shape',
142
+ 'scale_factor', 'flip', 'flip_direction')
143
+ }]])
144
+ ]
145
+ model = dict(
146
+ type='RTMDet',
147
+ data_preprocessor=dict(
148
+ type='DetDataPreprocessor',
149
+ mean=[103.53, 116.28, 123.675],
150
+ std=[57.375, 57.12, 58.395],
151
+ bgr_to_rgb=False,
152
+ batch_augments=None),
153
+ backbone=dict(
154
+ type='CSPNeXt',
155
+ arch='P5',
156
+ expand_ratio=0.5,
157
+ deepen_factor=0.67,
158
+ widen_factor=0.75,
159
+ channel_attention=True,
160
+ norm_cfg=dict(type='SyncBN'),
161
+ act_cfg=dict(type='SiLU', inplace=True)),
162
+ neck=dict(
163
+ type='CSPNeXtPAFPN',
164
+ in_channels=[192, 384, 768],
165
+ out_channels=192,
166
+ num_csp_blocks=2,
167
+ expand_ratio=0.5,
168
+ norm_cfg=dict(type='SyncBN'),
169
+ act_cfg=dict(type='SiLU', inplace=True)),
170
+ bbox_head=dict(
171
+ type='RTMDetInsSepBNHead',
172
+ num_classes=80,
173
+ in_channels=192,
174
+ stacked_convs=2,
175
+ share_conv=True,
176
+ pred_kernel_size=1,
177
+ feat_channels=192,
178
+ act_cfg=dict(type='SiLU', inplace=True),
179
+ norm_cfg=dict(type='SyncBN', requires_grad=True),
180
+ anchor_generator=dict(
181
+ type='MlvlPointGenerator', offset=0, strides=[8, 16, 32]),
182
+ bbox_coder=dict(type='DistancePointBBoxCoder'),
183
+ loss_cls=dict(
184
+ type='QualityFocalLoss',
185
+ use_sigmoid=True,
186
+ beta=2.0,
187
+ loss_weight=1.0),
188
+ loss_bbox=dict(type='GIoULoss', loss_weight=2.0),
189
+ loss_mask=dict(
190
+ type='DiceLoss', loss_weight=2.0, eps=5e-06, reduction='mean')),
191
+ train_cfg=dict(
192
+ assigner=dict(type='DynamicSoftLabelAssigner', topk=13),
193
+ allowed_border=-1,
194
+ pos_weight=-1,
195
+ debug=False),
196
+ test_cfg=dict(
197
+ nms_pre=400,
198
+ min_bbox_size=0,
199
+ score_thr=0.4,
200
+ nms=dict(type='nms', iou_threshold=0.6),
201
+ max_per_img=50,
202
+ mask_thr_binary=0.5))
203
+ train_pipeline_stage2 = [
204
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
205
+ dict(
206
+ type='LoadAnnotations',
207
+ with_bbox=True,
208
+ with_mask=True,
209
+ poly2mask=False),
210
+ dict(
211
+ type='RandomResize',
212
+ scale=(640, 640),
213
+ ratio_range=(0.1, 2.0),
214
+ keep_ratio=True),
215
+ dict(
216
+ type='RandomCrop',
217
+ crop_size=(640, 640),
218
+ recompute_bbox=True,
219
+ allow_negative_crop=True),
220
+ dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)),
221
+ dict(type='YOLOXHSVRandomAug'),
222
+ dict(type='RandomFlip', prob=0.5),
223
+ dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
224
+ dict(type='PackDetInputs')
225
+ ]
226
+ train_dataloader = dict(
227
+ batch_size=2,
228
+ num_workers=1,
229
+ batch_sampler=None,
230
+ pin_memory=True,
231
+ persistent_workers=True,
232
+ sampler=dict(type='DefaultSampler', shuffle=True),
233
+ dataset=dict(
234
+ type='ConcatDataset',
235
+ datasets=[
236
+ dict(
237
+ type='CocoDataset',
238
+ metainfo=dict(classes='text_line', palette=[(220, 20, 60)]),
239
+ data_prefix=dict(
240
+ img=
241
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/'
242
+ ),
243
+ ann_file=
244
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_lines2.json',
245
+ pipeline=[
246
+ dict(
247
+ type='LoadImageFromFile',
248
+ file_client_args=dict(backend='disk')),
249
+ dict(
250
+ type='LoadAnnotations',
251
+ with_bbox=True,
252
+ with_mask=True,
253
+ poly2mask=False),
254
+ dict(
255
+ type='CachedMosaic',
256
+ img_scale=(640, 640),
257
+ pad_val=114.0),
258
+ dict(
259
+ type='RandomResize',
260
+ scale=(1280, 1280),
261
+ ratio_range=(0.1, 2.0),
262
+ keep_ratio=True),
263
+ dict(
264
+ type='RandomCrop',
265
+ crop_size=(640, 640),
266
+ recompute_bbox=True,
267
+ allow_negative_crop=True),
268
+ dict(type='YOLOXHSVRandomAug'),
269
+ dict(type='RandomFlip', prob=0.5),
270
+ dict(
271
+ type='Pad',
272
+ size=(640, 640),
273
+ pad_val=dict(img=(114, 114, 114))),
274
+ dict(
275
+ type='CachedMixUp',
276
+ img_scale=(640, 640),
277
+ ratio_range=(1.0, 1.0),
278
+ max_cached_images=20,
279
+ pad_val=(114, 114, 114)),
280
+ dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)),
281
+ dict(type='PackDetInputs')
282
+ ])
283
+ ]))
284
+ val_dataloader = dict(
285
+ batch_size=1,
286
+ num_workers=10,
287
+ dataset=dict(
288
+ pipeline=[
289
+ dict(
290
+ type='LoadImageFromFile',
291
+ file_client_args=dict(backend='disk')),
292
+ dict(type='Resize', scale=(640, 640), keep_ratio=True),
293
+ dict(
294
+ type='Pad', size=(640, 640),
295
+ pad_val=dict(img=(114, 114, 114))),
296
+ dict(
297
+ type='PackDetInputs',
298
+ meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
299
+ 'scale_factor'))
300
+ ],
301
+ type='CocoDataset',
302
+ metainfo=dict(classes='text_line', palette=[(220, 20, 60)]),
303
+ data_prefix=dict(
304
+ img=
305
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/'
306
+ ),
307
+ ann_file=
308
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_regions2.json',
309
+ test_mode=True),
310
+ persistent_workers=True,
311
+ drop_last=False,
312
+ sampler=dict(type='DefaultSampler', shuffle=False))
313
+ test_dataloader = dict(
314
+ batch_size=1,
315
+ num_workers=10,
316
+ dataset=dict(
317
+ pipeline=[
318
+ dict(
319
+ type='LoadImageFromFile',
320
+ file_client_args=dict(backend='disk')),
321
+ dict(type='Resize', scale=(640, 640), keep_ratio=True),
322
+ dict(
323
+ type='Pad', size=(640, 640),
324
+ pad_val=dict(img=(114, 114, 114))),
325
+ dict(
326
+ type='PackDetInputs',
327
+ meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
328
+ 'scale_factor'))
329
+ ],
330
+ type='CocoDataset',
331
+ metainfo=dict(classes='text_line', palette=[(220, 20, 60)]),
332
+ data_prefix=dict(
333
+ img=
334
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/'
335
+ ),
336
+ ann_file=
337
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_regions2.json',
338
+ test_mode=True),
339
+ persistent_workers=True,
340
+ drop_last=False,
341
+ sampler=dict(type='DefaultSampler', shuffle=False))
342
+ max_epochs = 12
343
+ stage2_num_epochs = 2
344
+ base_lr = 0.00025
345
+ interval = 12
346
+ val_evaluator = dict(
347
+ proposal_nums=(100, 1, 10),
348
+ metric=['bbox', 'segm'],
349
+ type='CocoMetric',
350
+ ann_file=
351
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_lines2.json'
352
+ )
353
+ test_evaluator = dict(
354
+ proposal_nums=(100, 1, 10),
355
+ metric=['bbox', 'segm'],
356
+ type='CocoMetric',
357
+ ann_file=
358
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_lines2.json'
359
+ )
360
+ custom_hooks = [
361
+ dict(
362
+ type='EMAHook',
363
+ ema_type='ExpMomentumEMA',
364
+ momentum=0.0002,
365
+ update_buffers=True,
366
+ priority=49),
367
+ dict(
368
+ type='PipelineSwitchHook',
369
+ switch_epoch=10,
370
+ switch_pipeline=[
371
+ dict(
372
+ type='LoadImageFromFile',
373
+ file_client_args=dict(backend='disk')),
374
+ dict(
375
+ type='LoadAnnotations',
376
+ with_bbox=True,
377
+ with_mask=True,
378
+ poly2mask=False),
379
+ dict(
380
+ type='RandomResize',
381
+ scale=(640, 640),
382
+ ratio_range=(0.1, 2.0),
383
+ keep_ratio=True),
384
+ dict(
385
+ type='RandomCrop',
386
+ crop_size=(640, 640),
387
+ recompute_bbox=True,
388
+ allow_negative_crop=True),
389
+ dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)),
390
+ dict(type='YOLOXHSVRandomAug'),
391
+ dict(type='RandomFlip', prob=0.5),
392
+ dict(
393
+ type='Pad', size=(640, 640),
394
+ pad_val=dict(img=(114, 114, 114))),
395
+ dict(type='PackDetInputs')
396
+ ])
397
+ ]
398
+ work_dir = '/home/erik/Riksarkivet/Projects/HTR_Pipeline/models/checkpoints/rtmdet_lines_pr_2'
399
+ train_batch_size_per_gpu = 2
400
+ val_batch_size_per_gpu = 1
401
+ train_num_workers = 1
402
+ num_classes = 1
403
+ metainfo = dict(classes='text_line', palette=[(220, 20, 60)])
404
+ icdar_2019 = dict(
405
+ type='CocoDataset',
406
+ metainfo=dict(classes='text_line', palette=[(220, 20, 60)]),
407
+ data_prefix=dict(
408
+ img=
409
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/'
410
+ ),
411
+ ann_file=
412
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_regions2.json',
413
+ pipeline=[
414
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
415
+ dict(
416
+ type='LoadAnnotations',
417
+ with_bbox=True,
418
+ with_mask=True,
419
+ poly2mask=False),
420
+ dict(type='CachedMosaic', img_scale=(640, 640), pad_val=114.0),
421
+ dict(
422
+ type='RandomResize',
423
+ scale=(1280, 1280),
424
+ ratio_range=(0.1, 2.0),
425
+ keep_ratio=True),
426
+ dict(
427
+ type='RandomCrop',
428
+ crop_size=(640, 640),
429
+ recompute_bbox=True,
430
+ allow_negative_crop=True),
431
+ dict(type='YOLOXHSVRandomAug'),
432
+ dict(type='RandomFlip', prob=0.5),
433
+ dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
434
+ dict(
435
+ type='CachedMixUp',
436
+ img_scale=(640, 640),
437
+ ratio_range=(1.0, 1.0),
438
+ max_cached_images=20,
439
+ pad_val=(114, 114, 114)),
440
+ dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)),
441
+ dict(type='PackDetInputs')
442
+ ])
443
+ icdar_2019_test = dict(
444
+ type='CocoDataset',
445
+ metainfo=dict(classes='text_line', palette=[(220, 20, 60)]),
446
+ data_prefix=dict(
447
+ img=
448
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/'
449
+ ),
450
+ ann_file=
451
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_lines.json',
452
+ test_mode=True,
453
+ pipeline=[
454
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
455
+ dict(type='Resize', scale=(640, 640), keep_ratio=True),
456
+ dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
457
+ dict(
458
+ type='PackDetInputs',
459
+ meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
460
+ 'scale_factor'))
461
+ ])
462
+ police_records = dict(
463
+ type='CocoDataset',
464
+ metainfo=dict(classes='text_line', palette=[(220, 20, 60)]),
465
+ data_prefix=dict(
466
+ img=
467
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/'
468
+ ),
469
+ ann_file=
470
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_lines2.json',
471
+ pipeline=[
472
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
473
+ dict(
474
+ type='LoadAnnotations',
475
+ with_bbox=True,
476
+ with_mask=True,
477
+ poly2mask=False),
478
+ dict(type='CachedMosaic', img_scale=(640, 640), pad_val=114.0),
479
+ dict(
480
+ type='RandomResize',
481
+ scale=(1280, 1280),
482
+ ratio_range=(0.1, 2.0),
483
+ keep_ratio=True),
484
+ dict(
485
+ type='RandomCrop',
486
+ crop_size=(640, 640),
487
+ recompute_bbox=True,
488
+ allow_negative_crop=True),
489
+ dict(type='YOLOXHSVRandomAug'),
490
+ dict(type='RandomFlip', prob=0.5),
491
+ dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
492
+ dict(
493
+ type='CachedMixUp',
494
+ img_scale=(640, 640),
495
+ ratio_range=(1.0, 1.0),
496
+ max_cached_images=20,
497
+ pad_val=(114, 114, 114)),
498
+ dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)),
499
+ dict(type='PackDetInputs')
500
+ ])
501
+ train_list = [
502
+ dict(
503
+ type='CocoDataset',
504
+ metainfo=dict(classes='text_line', palette=[(220, 20, 60)]),
505
+ data_prefix=dict(
506
+ img=
507
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/'
508
+ ),
509
+ ann_file=
510
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_lines2.json',
511
+ pipeline=[
512
+ dict(
513
+ type='LoadImageFromFile',
514
+ file_client_args=dict(backend='disk')),
515
+ dict(
516
+ type='LoadAnnotations',
517
+ with_bbox=True,
518
+ with_mask=True,
519
+ poly2mask=False),
520
+ dict(type='CachedMosaic', img_scale=(640, 640), pad_val=114.0),
521
+ dict(
522
+ type='RandomResize',
523
+ scale=(1280, 1280),
524
+ ratio_range=(0.1, 2.0),
525
+ keep_ratio=True),
526
+ dict(
527
+ type='RandomCrop',
528
+ crop_size=(640, 640),
529
+ recompute_bbox=True,
530
+ allow_negative_crop=True),
531
+ dict(type='YOLOXHSVRandomAug'),
532
+ dict(type='RandomFlip', prob=0.5),
533
+ dict(
534
+ type='Pad', size=(640, 640),
535
+ pad_val=dict(img=(114, 114, 114))),
536
+ dict(
537
+ type='CachedMixUp',
538
+ img_scale=(640, 640),
539
+ ratio_range=(1.0, 1.0),
540
+ max_cached_images=20,
541
+ pad_val=(114, 114, 114)),
542
+ dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)),
543
+ dict(type='PackDetInputs')
544
+ ])
545
+ ]
546
+ test_list = [
547
+ dict(
548
+ type='CocoDataset',
549
+ metainfo=dict(classes='text_line', palette=[(220, 20, 60)]),
550
+ data_prefix=dict(
551
+ img=
552
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/'
553
+ ),
554
+ ann_file=
555
+ '/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_lines.json',
556
+ test_mode=True,
557
+ pipeline=[
558
+ dict(
559
+ type='LoadImageFromFile',
560
+ file_client_args=dict(backend='disk')),
561
+ dict(type='Resize', scale=(640, 640), keep_ratio=True),
562
+ dict(
563
+ type='Pad', size=(640, 640),
564
+ pad_val=dict(img=(114, 114, 114))),
565
+ dict(
566
+ type='PackDetInputs',
567
+ meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
568
+ 'scale_factor'))
569
+ ])
570
+ ]
571
+ pipeline = [
572
+ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),
573
+ dict(type='Resize', scale=(640, 640), keep_ratio=True),
574
+ dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
575
+ dict(
576
+ type='PackDetInputs',
577
+ meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
578
+ 'scale_factor'))
579
+ ]
580
+ launcher = 'pytorch'
models/RmtDet_regions/rtmdet_m_textregions_2_concat.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default_scope = "mmdet"
2
+ default_hooks = dict(
3
+ timer=dict(type="IterTimerHook"),
4
+ logger=dict(type="LoggerHook", interval=100),
5
+ param_scheduler=dict(type="ParamSchedulerHook"),
6
+ checkpoint=dict(type="CheckpointHook", interval=1, max_keep_ckpts=5, save_best="auto"),
7
+ sampler_seed=dict(type="DistSamplerSeedHook"),
8
+ visualization=dict(type="DetVisualizationHook"),
9
+ )
10
+ env_cfg = dict(cudnn_benchmark=False, mp_cfg=dict(mp_start_method="fork", opencv_num_threads=0), dist_cfg=dict(backend="nccl"))
11
+ vis_backends = [dict(type="LocalVisBackend")]
12
+ visualizer = dict(type="DetLocalVisualizer", vis_backends=[dict(type="LocalVisBackend")], name="visualizer", save_dir="./")
13
+ log_processor = dict(type="LogProcessor", window_size=50, by_epoch=True)
14
+ log_level = "INFO"
15
+ load_from = "/home/erik/Riksarkivet/Projects/HTR_Pipeline/models/checkpoints/rtmdet_regions_6/epoch_11.pth"
16
+ resume = True
17
+ train_cfg = dict(type="EpochBasedTrainLoop", max_epochs=12, val_interval=12, dynamic_intervals=[(10, 1)])
18
+ val_cfg = dict(type="ValLoop")
19
+ test_cfg = dict(
20
+ type="TestLoop",
21
+ pipeline=[
22
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
23
+ dict(type="Resize", scale=(640, 640), keep_ratio=True),
24
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
25
+ dict(type="PackDetInputs", meta_keys=("img_id", "img_path", "ori_shape", "img_shape", "scale_factor")),
26
+ ],
27
+ )
28
+ param_scheduler = [
29
+ dict(type="LinearLR", start_factor=1e-05, by_epoch=False, begin=0, end=1000),
30
+ dict(type="CosineAnnealingLR", eta_min=1.25e-05, begin=6, end=12, T_max=6, by_epoch=True, convert_to_iter_based=True),
31
+ ]
32
+ optim_wrapper = dict(
33
+ type="OptimWrapper",
34
+ optimizer=dict(type="AdamW", lr=0.00025, weight_decay=0.05),
35
+ paramwise_cfg=dict(norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True),
36
+ )
37
+ auto_scale_lr = dict(enable=False, base_batch_size=16)
38
+ dataset_type = "CocoDataset"
39
+ data_root = "data/coco/"
40
+ file_client_args = dict(backend="disk")
41
+ train_pipeline = [
42
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
43
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
44
+ dict(type="CachedMosaic", img_scale=(640, 640), pad_val=114.0),
45
+ dict(type="RandomResize", scale=(1280, 1280), ratio_range=(0.1, 2.0), keep_ratio=True),
46
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
47
+ dict(type="YOLOXHSVRandomAug"),
48
+ dict(type="RandomFlip", prob=0.5),
49
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
50
+ dict(type="CachedMixUp", img_scale=(640, 640), ratio_range=(1.0, 1.0), max_cached_images=20, pad_val=(114, 114, 114)),
51
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
52
+ dict(type="PackDetInputs"),
53
+ ]
54
+ test_pipeline = [
55
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
56
+ dict(type="Resize", scale=(640, 640), keep_ratio=True),
57
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
58
+ dict(type="PackDetInputs", meta_keys=("img_id", "img_path", "ori_shape", "img_shape", "scale_factor")),
59
+ ]
60
+ tta_model = dict(type="DetTTAModel", tta_cfg=dict(nms=dict(type="nms", iou_threshold=0.6), max_per_img=100))
61
+ img_scales = [(640, 640), (320, 320), (960, 960)]
62
+ tta_pipeline = [
63
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
64
+ dict(
65
+ type="TestTimeAug",
66
+ transforms=[
67
+ [
68
+ {"type": "Resize", "scale": (640, 640), "keep_ratio": True},
69
+ {"type": "Resize", "scale": (320, 320), "keep_ratio": True},
70
+ {"type": "Resize", "scale": (960, 960), "keep_ratio": True},
71
+ ],
72
+ [{"type": "RandomFlip", "prob": 1.0}, {"type": "RandomFlip", "prob": 0.0}],
73
+ [{"type": "Pad", "size": (960, 960), "pad_val": {"img": (114, 114, 114)}}],
74
+ [
75
+ {
76
+ "type": "PackDetInputs",
77
+ "meta_keys": ("img_id", "img_path", "ori_shape", "img_shape", "scale_factor", "flip", "flip_direction"),
78
+ }
79
+ ],
80
+ ],
81
+ ),
82
+ ]
83
+ model = dict(
84
+ type="RTMDet",
85
+ data_preprocessor=dict(
86
+ type="DetDataPreprocessor", mean=[103.53, 116.28, 123.675], std=[57.375, 57.12, 58.395], bgr_to_rgb=False, batch_augments=None
87
+ ),
88
+ backbone=dict(
89
+ type="CSPNeXt",
90
+ arch="P5",
91
+ expand_ratio=0.5,
92
+ deepen_factor=0.67,
93
+ widen_factor=0.75,
94
+ channel_attention=True,
95
+ norm_cfg=dict(type="SyncBN"),
96
+ act_cfg=dict(type="SiLU", inplace=True),
97
+ ),
98
+ neck=dict(
99
+ type="CSPNeXtPAFPN",
100
+ in_channels=[192, 384, 768],
101
+ out_channels=192,
102
+ num_csp_blocks=2,
103
+ expand_ratio=0.5,
104
+ norm_cfg=dict(type="SyncBN"),
105
+ act_cfg=dict(type="SiLU", inplace=True),
106
+ ),
107
+ bbox_head=dict(
108
+ type="RTMDetInsSepBNHead",
109
+ num_classes=80,
110
+ in_channels=192,
111
+ stacked_convs=2,
112
+ share_conv=True,
113
+ pred_kernel_size=1,
114
+ feat_channels=192,
115
+ act_cfg=dict(type="SiLU", inplace=True),
116
+ norm_cfg=dict(type="SyncBN", requires_grad=True),
117
+ anchor_generator=dict(type="MlvlPointGenerator", offset=0, strides=[8, 16, 32]),
118
+ bbox_coder=dict(type="DistancePointBBoxCoder"),
119
+ loss_cls=dict(type="QualityFocalLoss", use_sigmoid=True, beta=2.0, loss_weight=1.0),
120
+ loss_bbox=dict(type="GIoULoss", loss_weight=2.0),
121
+ loss_mask=dict(type="DiceLoss", loss_weight=2.0, eps=5e-06, reduction="mean"),
122
+ ),
123
+ train_cfg=dict(assigner=dict(type="DynamicSoftLabelAssigner", topk=13), allowed_border=-1, pos_weight=-1, debug=False),
124
+ test_cfg=dict(nms_pre=200, min_bbox_size=0, score_thr=0.4, nms=dict(type="nms", iou_threshold=0.6), max_per_img=50, mask_thr_binary=0.5),
125
+ )
126
+ train_pipeline_stage2 = [
127
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
128
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
129
+ dict(type="RandomResize", scale=(640, 640), ratio_range=(0.1, 2.0), keep_ratio=True),
130
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
131
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
132
+ dict(type="YOLOXHSVRandomAug"),
133
+ dict(type="RandomFlip", prob=0.5),
134
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
135
+ dict(type="PackDetInputs"),
136
+ ]
137
+ train_dataloader = dict(
138
+ batch_size=2,
139
+ num_workers=1,
140
+ batch_sampler=None,
141
+ pin_memory=True,
142
+ persistent_workers=True,
143
+ sampler=dict(type="DefaultSampler", shuffle=True),
144
+ dataset=dict(
145
+ type="ConcatDataset",
146
+ datasets=[
147
+ dict(
148
+ type="CocoDataset",
149
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
150
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/"),
151
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_regions2.json",
152
+ pipeline=[
153
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
154
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
155
+ dict(type="CachedMosaic", img_scale=(640, 640), pad_val=114.0),
156
+ dict(type="RandomResize", scale=(1280, 1280), ratio_range=(0.1, 2.0), keep_ratio=True),
157
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
158
+ dict(type="YOLOXHSVRandomAug"),
159
+ dict(type="RandomFlip", prob=0.5),
160
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
161
+ dict(type="CachedMixUp", img_scale=(640, 640), ratio_range=(1.0, 1.0), max_cached_images=20, pad_val=(114, 114, 114)),
162
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
163
+ dict(type="PackDetInputs"),
164
+ ],
165
+ ),
166
+ dict(
167
+ type="CocoDataset",
168
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
169
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/"),
170
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_regions2.json",
171
+ pipeline=[
172
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
173
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
174
+ dict(type="CachedMosaic", img_scale=(640, 640), pad_val=114.0),
175
+ dict(type="RandomResize", scale=(1280, 1280), ratio_range=(0.1, 2.0), keep_ratio=True),
176
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
177
+ dict(type="YOLOXHSVRandomAug"),
178
+ dict(type="RandomFlip", prob=0.5),
179
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
180
+ dict(type="CachedMixUp", img_scale=(640, 640), ratio_range=(1.0, 1.0), max_cached_images=20, pad_val=(114, 114, 114)),
181
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
182
+ dict(type="PackDetInputs"),
183
+ ],
184
+ ),
185
+ ],
186
+ ),
187
+ )
188
+ val_dataloader = dict(
189
+ batch_size=1,
190
+ num_workers=10,
191
+ dataset=dict(
192
+ pipeline=[
193
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
194
+ dict(type="Resize", scale=(640, 640), keep_ratio=True),
195
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
196
+ dict(type="PackDetInputs", meta_keys=("img_id", "img_path", "ori_shape", "img_shape", "scale_factor")),
197
+ ],
198
+ type="CocoDataset",
199
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
200
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/"),
201
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_regions2.json",
202
+ test_mode=True,
203
+ ),
204
+ persistent_workers=True,
205
+ drop_last=False,
206
+ sampler=dict(type="DefaultSampler", shuffle=False),
207
+ )
208
+ test_dataloader = dict(
209
+ batch_size=1,
210
+ num_workers=10,
211
+ dataset=dict(
212
+ pipeline=[
213
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
214
+ dict(type="Resize", scale=(640, 640), keep_ratio=True),
215
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
216
+ dict(type="PackDetInputs", meta_keys=("img_id", "img_path", "ori_shape", "img_shape", "scale_factor")),
217
+ ],
218
+ type="CocoDataset",
219
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
220
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/"),
221
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_regions2.json",
222
+ test_mode=True,
223
+ ),
224
+ persistent_workers=True,
225
+ drop_last=False,
226
+ sampler=dict(type="DefaultSampler", shuffle=False),
227
+ )
228
+ max_epochs = 12
229
+ stage2_num_epochs = 2
230
+ base_lr = 0.00025
231
+ interval = 12
232
+ val_evaluator = dict(
233
+ proposal_nums=(100, 1, 10),
234
+ metric=["bbox", "segm"],
235
+ type="CocoMetric",
236
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_regions2.json",
237
+ )
238
+ test_evaluator = dict(
239
+ proposal_nums=(100, 1, 10),
240
+ metric=["bbox", "segm"],
241
+ type="CocoMetric",
242
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_regions2.json",
243
+ )
244
+ custom_hooks = [
245
+ dict(type="EMAHook", ema_type="ExpMomentumEMA", momentum=0.0002, update_buffers=True, priority=49),
246
+ dict(
247
+ type="PipelineSwitchHook",
248
+ switch_epoch=10,
249
+ switch_pipeline=[
250
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
251
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
252
+ dict(type="RandomResize", scale=(640, 640), ratio_range=(0.1, 2.0), keep_ratio=True),
253
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
254
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
255
+ dict(type="YOLOXHSVRandomAug"),
256
+ dict(type="RandomFlip", prob=0.5),
257
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
258
+ dict(type="PackDetInputs"),
259
+ ],
260
+ ),
261
+ ]
262
+ work_dir = "/home/erik/Riksarkivet/Projects/HTR_Pipeline/models/checkpoints/rtmdet_regions_6"
263
+ train_batch_size_per_gpu = 2
264
+ val_batch_size_per_gpu = 1
265
+ train_num_workers = 1
266
+ num_classes = 1
267
+ metainfo = dict(classes="TextRegion", palette=[(220, 20, 60)])
268
+ icdar_2019 = dict(
269
+ type="CocoDataset",
270
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
271
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/"),
272
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_regions2.json",
273
+ pipeline=[
274
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
275
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
276
+ dict(type="CachedMosaic", img_scale=(640, 640), pad_val=114.0),
277
+ dict(type="RandomResize", scale=(1280, 1280), ratio_range=(0.1, 2.0), keep_ratio=True),
278
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
279
+ dict(type="YOLOXHSVRandomAug"),
280
+ dict(type="RandomFlip", prob=0.5),
281
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
282
+ dict(type="CachedMixUp", img_scale=(640, 640), ratio_range=(1.0, 1.0), max_cached_images=20, pad_val=(114, 114, 114)),
283
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
284
+ dict(type="PackDetInputs"),
285
+ ],
286
+ )
287
+ icdar_2019_test = dict(
288
+ type="CocoDataset",
289
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
290
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/"),
291
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_regions2.json",
292
+ test_mode=True,
293
+ pipeline=[
294
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
295
+ dict(type="Resize", scale=(640, 640), keep_ratio=True),
296
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
297
+ dict(type="PackDetInputs", meta_keys=("img_id", "img_path", "ori_shape", "img_shape", "scale_factor")),
298
+ ],
299
+ )
300
+ police_records = dict(
301
+ type="CocoDataset",
302
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
303
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/"),
304
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_regions2.json",
305
+ pipeline=[
306
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
307
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
308
+ dict(type="CachedMosaic", img_scale=(640, 640), pad_val=114.0),
309
+ dict(type="RandomResize", scale=(1280, 1280), ratio_range=(0.1, 2.0), keep_ratio=True),
310
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
311
+ dict(type="YOLOXHSVRandomAug"),
312
+ dict(type="RandomFlip", prob=0.5),
313
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
314
+ dict(type="CachedMixUp", img_scale=(640, 640), ratio_range=(1.0, 1.0), max_cached_images=20, pad_val=(114, 114, 114)),
315
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
316
+ dict(type="PackDetInputs"),
317
+ ],
318
+ )
319
+ train_list = [
320
+ dict(
321
+ type="CocoDataset",
322
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
323
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/"),
324
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/police_records/gt_files/coco_regions2.json",
325
+ pipeline=[
326
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
327
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
328
+ dict(type="CachedMosaic", img_scale=(640, 640), pad_val=114.0),
329
+ dict(type="RandomResize", scale=(1280, 1280), ratio_range=(0.1, 2.0), keep_ratio=True),
330
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
331
+ dict(type="YOLOXHSVRandomAug"),
332
+ dict(type="RandomFlip", prob=0.5),
333
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
334
+ dict(type="CachedMixUp", img_scale=(640, 640), ratio_range=(1.0, 1.0), max_cached_images=20, pad_val=(114, 114, 114)),
335
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
336
+ dict(type="PackDetInputs"),
337
+ ],
338
+ ),
339
+ dict(
340
+ type="CocoDataset",
341
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
342
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/"),
343
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_regions2.json",
344
+ pipeline=[
345
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
346
+ dict(type="LoadAnnotations", with_bbox=True, with_mask=True, poly2mask=False),
347
+ dict(type="CachedMosaic", img_scale=(640, 640), pad_val=114.0),
348
+ dict(type="RandomResize", scale=(1280, 1280), ratio_range=(0.1, 2.0), keep_ratio=True),
349
+ dict(type="RandomCrop", crop_size=(640, 640), recompute_bbox=True, allow_negative_crop=True),
350
+ dict(type="YOLOXHSVRandomAug"),
351
+ dict(type="RandomFlip", prob=0.5),
352
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
353
+ dict(type="CachedMixUp", img_scale=(640, 640), ratio_range=(1.0, 1.0), max_cached_images=20, pad_val=(114, 114, 114)),
354
+ dict(type="FilterAnnotations", min_gt_bbox_wh=(1, 1)),
355
+ dict(type="PackDetInputs"),
356
+ ],
357
+ ),
358
+ ]
359
+ test_list = [
360
+ dict(
361
+ type="CocoDataset",
362
+ metainfo=dict(classes="TextRegion", palette=[(220, 20, 60)]),
363
+ data_prefix=dict(img="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/"),
364
+ ann_file="/media/erik/Elements/Riksarkivet/data/datasets/htr/segmentation/ICDAR-2019/clean/gt_files/coco_regions2.json",
365
+ test_mode=True,
366
+ pipeline=[
367
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
368
+ dict(type="Resize", scale=(640, 640), keep_ratio=True),
369
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
370
+ dict(type="PackDetInputs", meta_keys=("img_id", "img_path", "ori_shape", "img_shape", "scale_factor")),
371
+ ],
372
+ )
373
+ ]
374
+ pipeline = [
375
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
376
+ dict(type="Resize", scale=(640, 640), keep_ratio=True),
377
+ dict(type="Pad", size=(640, 640), pad_val=dict(img=(114, 114, 114))),
378
+ dict(type="PackDetInputs", meta_keys=("img_id", "img_path", "ori_shape", "img_shape", "scale_factor")),
379
+ ]
380
+ launcher = "pytorch"
models/SATRN/_base_satrn_shallow_concat.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default_scope = "mmocr"
2
+ env_cfg = dict(
3
+ cudnn_benchmark=True, mp_cfg=dict(mp_start_method="fork", opencv_num_threads=0), dist_cfg=dict(backend="nccl")
4
+ )
5
+ randomness = dict(seed=None)
6
+ default_hooks = dict(
7
+ timer=dict(type="IterTimerHook"),
8
+ logger=dict(type="LoggerHook", interval=100),
9
+ param_scheduler=dict(type="ParamSchedulerHook"),
10
+ checkpoint=dict(type="CheckpointHook", interval=1),
11
+ sampler_seed=dict(type="DistSamplerSeedHook"),
12
+ sync_buffer=dict(type="SyncBuffersHook"),
13
+ visualization=dict(type="VisualizationHook", interval=1, enable=False, show=False, draw_gt=False, draw_pred=False),
14
+ )
15
+ log_level = "INFO"
16
+ log_processor = dict(type="LogProcessor", window_size=10, by_epoch=True)
17
+ load_from = (
18
+ "/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/models/checkpoints/1700_1800_combined_satrn/epoch_5.pth"
19
+ )
20
+ resume = False
21
+ val_evaluator = dict(
22
+ type="Evaluator",
23
+ metrics=[
24
+ dict(
25
+ type="WordMetric",
26
+ mode=["exact", "ignore_case", "ignore_case_symbol"],
27
+ valid_symbol="[^A-Z^a-z^0-9^一-龥^å^ä^ö^Å^Ä^Ö]",
28
+ ),
29
+ dict(type="CharMetric", valid_symbol="[^A-Z^a-z^0-9^一-龥^å^ä^ö^Å^Ä^Ö]"),
30
+ dict(type="OneMinusNEDMetric", valid_symbol="[^A-Z^a-z^0-9^一-龥^å^ä^ö^Å^Ä^Ö]"),
31
+ ],
32
+ )
33
+ test_evaluator = dict(
34
+ type="Evaluator",
35
+ metrics=[
36
+ dict(
37
+ type="WordMetric",
38
+ mode=["exact", "ignore_case", "ignore_case_symbol"],
39
+ valid_symbol="[^A-Z^a-z^0-9^一-龥^å^ä^ö^Å^Ä^Ö]",
40
+ ),
41
+ dict(type="CharMetric", valid_symbol="[^A-Z^a-z^0-9^一-龥^å^ä^ö^Å^Ä^Ö]"),
42
+ dict(type="OneMinusNEDMetric", valid_symbol="[^A-Z^a-z^0-9^一-龥^å^ä^ö^Å^Ä^Ö]"),
43
+ ],
44
+ )
45
+ vis_backends = [dict(type="LocalVisBackend")]
46
+ visualizer = dict(type="TextRecogLocalVisualizer", name="visualizer", vis_backends=[dict(type="TensorboardVisBackend")])
47
+ optim_wrapper = dict(type="OptimWrapper", optimizer=dict(type="Adam", lr=0.0003))
48
+ train_cfg = dict(type="EpochBasedTrainLoop", max_epochs=5, val_interval=1)
49
+ val_cfg = dict(type="ValLoop")
50
+ test_cfg = dict(type="TestLoop")
51
+ param_scheduler = [dict(type="MultiStepLR", milestones=[3, 4], end=5)]
52
+ file_client_args = dict(backend="disk")
53
+ dictionary = dict(
54
+ type="Dictionary",
55
+ dict_file="./models/SATRN/dict1700.txt",
56
+ with_padding=True,
57
+ with_unknown=True,
58
+ same_start_end=True,
59
+ with_start=True,
60
+ with_end=True,
61
+ )
62
+ model = dict(
63
+ type="SATRN",
64
+ backbone=dict(type="ShallowCNN", input_channels=3, hidden_dim=512),
65
+ encoder=dict(
66
+ type="SATRNEncoder",
67
+ n_layers=12,
68
+ n_head=8,
69
+ d_k=64,
70
+ d_v=64,
71
+ d_model=512,
72
+ n_position=100,
73
+ d_inner=2048,
74
+ dropout=0.1,
75
+ ),
76
+ decoder=dict(
77
+ type="NRTRDecoder",
78
+ n_layers=6,
79
+ d_embedding=512,
80
+ n_head=8,
81
+ d_model=512,
82
+ d_inner=2048,
83
+ d_k=64,
84
+ d_v=64,
85
+ module_loss=dict(type="CEModuleLoss", flatten=True, ignore_first_char=True),
86
+ dictionary=dict(
87
+ type="Dictionary",
88
+ dict_file="./models/SATRN/dict1700.txt",
89
+ with_padding=True,
90
+ with_unknown=True,
91
+ same_start_end=True,
92
+ with_start=True,
93
+ with_end=True,
94
+ ),
95
+ max_seq_len=100,
96
+ postprocessor=dict(type="AttentionPostprocessor"),
97
+ ),
98
+ data_preprocessor=dict(
99
+ type="TextRecogDataPreprocessor", mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375]
100
+ ),
101
+ )
102
+ train_pipeline = [
103
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk"), ignore_empty=True, min_size=2),
104
+ dict(type="LoadOCRAnnotations", with_text=True),
105
+ dict(type="Resize", scale=(400, 64), keep_ratio=False),
106
+ dict(type="PackTextRecogInputs", meta_keys=("img_path", "ori_shape", "img_shape", "valid_ratio")),
107
+ ]
108
+ test_pipeline = [
109
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
110
+ dict(type="Resize", scale=(400, 64), keep_ratio=False),
111
+ dict(type="LoadOCRAnnotations", with_text=True),
112
+ dict(type="PackTextRecogInputs", meta_keys=("img_path", "ori_shape", "img_shape", "valid_ratio")),
113
+ ]
114
+ HTR_1700_combined_train = dict(
115
+ type="RecogTextDataset",
116
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
117
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_clean",
118
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_HTR_shuffled_train.jsonl",
119
+ test_mode=False,
120
+ pipeline=None,
121
+ )
122
+ HTR_1700_combined_test = dict(
123
+ type="RecogTextDataset",
124
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
125
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_clean",
126
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_HTR_shuffled_val.jsonl",
127
+ test_mode=True,
128
+ pipeline=None,
129
+ )
130
+ pr_cr_combined_train = dict(
131
+ type="RecogTextDataset",
132
+ parser_cfg=dict(type="LineStrParser", keys=["filename", "text"], separator="|"),
133
+ data_root="/ceph/hpc/scratch/user/euerikl/data/line_images",
134
+ ann_file="/ceph/hpc/home/euerikl/projects/htr_1800/gt_files/combined_train.txt",
135
+ test_mode=False,
136
+ pipeline=None,
137
+ )
138
+ pr_cr_combined_test = dict(
139
+ type="RecogTextDataset",
140
+ parser_cfg=dict(type="LineStrParser", keys=["filename", "text"], separator="|"),
141
+ data_root="/ceph/hpc/scratch/user/euerikl/data/line_images",
142
+ ann_file="/ceph/hpc/home/euerikl/projects/htr_1800/gt_files/combined_eval.txt",
143
+ test_mode=True,
144
+ pipeline=None,
145
+ )
146
+ out_of_domain_1700_all_test = dict(
147
+ type="RecogTextDataset",
148
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
149
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_testsets_clean",
150
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_testsets_gt/1700_HTR_testsets_all.jsonl",
151
+ test_mode=True,
152
+ pipeline=None,
153
+ )
154
+ train_list = [
155
+ dict(
156
+ type="RecogTextDataset",
157
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
158
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_clean",
159
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_HTR_shuffled_train.jsonl",
160
+ test_mode=False,
161
+ pipeline=None,
162
+ ),
163
+ dict(
164
+ type="RecogTextDataset",
165
+ parser_cfg=dict(type="LineStrParser", keys=["filename", "text"], separator="|"),
166
+ data_root="/ceph/hpc/scratch/user/euerikl/data/line_images",
167
+ ann_file="/ceph/hpc/home/euerikl/projects/htr_1800/gt_files/combined_train.txt",
168
+ test_mode=False,
169
+ pipeline=None,
170
+ ),
171
+ ]
172
+ test_list = [
173
+ dict(
174
+ type="RecogTextDataset",
175
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
176
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_testsets_clean",
177
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_testsets_gt/1700_HTR_testsets_all.jsonl",
178
+ test_mode=True,
179
+ pipeline=None,
180
+ )
181
+ ]
182
+ train_dataset = dict(
183
+ type="ConcatDataset",
184
+ datasets=[
185
+ dict(
186
+ type="RecogTextDataset",
187
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
188
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_clean",
189
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_HTR_shuffled_train.jsonl",
190
+ test_mode=False,
191
+ pipeline=None,
192
+ ),
193
+ dict(
194
+ type="RecogTextDataset",
195
+ parser_cfg=dict(type="LineStrParser", keys=["filename", "text"], separator="|"),
196
+ data_root="/ceph/hpc/scratch/user/euerikl/data/line_images",
197
+ ann_file="/ceph/hpc/home/euerikl/projects/htr_1800/gt_files/combined_train.txt",
198
+ test_mode=False,
199
+ pipeline=None,
200
+ ),
201
+ ],
202
+ pipeline=[
203
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk"), ignore_empty=True, min_size=2),
204
+ dict(type="LoadOCRAnnotations", with_text=True),
205
+ dict(type="Resize", scale=(400, 64), keep_ratio=False),
206
+ dict(type="PackTextRecogInputs", meta_keys=("img_path", "ori_shape", "img_shape", "valid_ratio")),
207
+ ],
208
+ )
209
+ test_dataset = dict(
210
+ type="ConcatDataset",
211
+ datasets=[
212
+ dict(
213
+ type="RecogTextDataset",
214
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
215
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_testsets_clean",
216
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_testsets_gt/1700_HTR_testsets_all.jsonl",
217
+ test_mode=True,
218
+ pipeline=None,
219
+ )
220
+ ],
221
+ pipeline=[
222
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
223
+ dict(type="Resize", scale=(400, 64), keep_ratio=False),
224
+ dict(type="LoadOCRAnnotations", with_text=True),
225
+ dict(type="PackTextRecogInputs", meta_keys=("img_path", "ori_shape", "img_shape", "valid_ratio")),
226
+ ],
227
+ )
228
+ train_dataloader = dict(
229
+ batch_size=8,
230
+ num_workers=1,
231
+ persistent_workers=True,
232
+ sampler=dict(type="DefaultSampler", shuffle=True),
233
+ dataset=dict(
234
+ type="ConcatDataset",
235
+ datasets=[
236
+ dict(
237
+ type="RecogTextDataset",
238
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
239
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_clean",
240
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_HTR_shuffled_train.jsonl",
241
+ test_mode=False,
242
+ pipeline=None,
243
+ ),
244
+ dict(
245
+ type="RecogTextDataset",
246
+ parser_cfg=dict(type="LineStrParser", keys=["filename", "text"], separator="|"),
247
+ data_root="/ceph/hpc/scratch/user/euerikl/data/line_images",
248
+ ann_file="/ceph/hpc/home/euerikl/projects/htr_1800/gt_files/combined_train.txt",
249
+ test_mode=False,
250
+ pipeline=None,
251
+ ),
252
+ ],
253
+ pipeline=[
254
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk"), ignore_empty=True, min_size=2),
255
+ dict(type="LoadOCRAnnotations", with_text=True),
256
+ dict(type="Resize", scale=(400, 64), keep_ratio=False),
257
+ dict(type="PackTextRecogInputs", meta_keys=("img_path", "ori_shape", "img_shape", "valid_ratio")),
258
+ ],
259
+ ),
260
+ )
261
+ test_dataloader = dict(
262
+ batch_size=8,
263
+ num_workers=1,
264
+ persistent_workers=True,
265
+ drop_last=False,
266
+ sampler=dict(type="DefaultSampler", shuffle=False),
267
+ dataset=dict(
268
+ type="ConcatDataset",
269
+ datasets=[
270
+ dict(
271
+ type="RecogTextDataset",
272
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
273
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_testsets_clean",
274
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_testsets_gt/1700_HTR_testsets_all.jsonl",
275
+ test_mode=True,
276
+ pipeline=None,
277
+ )
278
+ ],
279
+ pipeline=[
280
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
281
+ dict(type="Resize", scale=(400, 64), keep_ratio=False),
282
+ dict(type="LoadOCRAnnotations", with_text=True),
283
+ dict(type="PackTextRecogInputs", meta_keys=("img_path", "ori_shape", "img_shape", "valid_ratio")),
284
+ ],
285
+ ),
286
+ )
287
+ val_dataloader = dict(
288
+ batch_size=8,
289
+ num_workers=1,
290
+ persistent_workers=True,
291
+ drop_last=False,
292
+ sampler=dict(type="DefaultSampler", shuffle=False),
293
+ dataset=dict(
294
+ type="ConcatDataset",
295
+ datasets=[
296
+ dict(
297
+ type="RecogTextDataset",
298
+ parser_cfg=dict(type="LineJsonParser", keys=["filename", "text"]),
299
+ data_root="/ceph/hpc/scratch/user/euerikl/data/HTR_1700_testsets_clean",
300
+ ann_file="/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/data/processed/1700_testsets_gt/1700_HTR_testsets_all.jsonl",
301
+ test_mode=True,
302
+ pipeline=None,
303
+ )
304
+ ],
305
+ pipeline=[
306
+ dict(type="LoadImageFromFile", file_client_args=dict(backend="disk")),
307
+ dict(type="Resize", scale=(400, 64), keep_ratio=False),
308
+ dict(type="LoadOCRAnnotations", with_text=True),
309
+ dict(type="PackTextRecogInputs", meta_keys=("img_path", "ori_shape", "img_shape", "valid_ratio")),
310
+ ],
311
+ ),
312
+ )
313
+ gpu_ids = range(0, 4)
314
+ cudnn_benchmark = True
315
+ work_dir = "/ceph/hpc/home/euerikl/projects/hf_openmmlab_models/models/checkpoints/1700_1800_combined_satrn"
316
+ checkpoint_config = dict(interval=1)
317
+ auto_scale_lr = dict(base_batch_size=32)
318
+ launcher = "pytorch"
models/SATRN/dict1700.txt ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ !
4
+ "
5
+ #
6
+ %
7
+ &
8
+ '
9
+ (
10
+ )
11
+ *
12
+ +
13
+ ,
14
+ -
15
+ .
16
+ /
17
+ 0
18
+ 1
19
+ 2
20
+ 3
21
+ 4
22
+ 5
23
+ 6
24
+ 7
25
+ 8
26
+ 9
27
+ :
28
+ ;
29
+ <
30
+ =
31
+ ?
32
+ A
33
+ B
34
+ C
35
+ D
36
+ E
37
+ F
38
+ G
39
+ H
40
+ I
41
+ J
42
+ K
43
+ L
44
+ M
45
+ N
46
+ O
47
+ P
48
+ Q
49
+ R
50
+ S
51
+ T
52
+ U
53
+ V
54
+ W
55
+ X
56
+ Y
57
+ Z
58
+ [
59
+ \
60
+ ]
61
+ _
62
+ a
63
+ b
64
+ c
65
+ d
66
+ e
67
+ f
68
+ g
69
+ h
70
+ i
71
+ j
72
+ k
73
+ l
74
+ m
75
+ n
76
+ o
77
+ p
78
+ q
79
+ r
80
+ s
81
+ t
82
+ u
83
+ v
84
+ w
85
+ x
86
+ y
87
+ z
88
+ {
89
+ |
90
+ }
91
+ ~
92
+ £
93
+ §
94
+ ¨
95
+ ¬
96
+ ¼
97
+ ½
98
+ ¾
99
+ Ä
100
+ Å
101
+ Ö
102
+ Ü
103
+ ß
104
+ à
105
+ á
106
+ ä
107
+ å
108
+ æ
109
+ ç
110
+ è
111
+ é
112
+ ê
113
+ ë
114
+ ï
115
+ ô
116
+ ö
117
+ ü
118
+ ý
119
+ ÿ
120
+ œ
121
+ ƒ
122
+ ̄
123
+ ̅
124
+ Ψ
125
+ β
126
+ ӕ
127
+ َ
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+ 🜍
148
+ 🜔
pyproject.toml ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # [build-system]
2
+ # requires = ["setuptools"]
3
+ # build-backend = "setuptools.build_meta"
4
+
5
+
6
+ # [project]
7
+ # name = "htr_pipeline"
8
+ # description = "The purpose of the project is to demo Riksarkivets HTR-pipeline"
9
+ # requires-python = ">= 3.10"
10
+ # version="0.0.0.dev1"
11
+ # authors = [{ name = "The Swedish National Archives Face team "}]
12
+ # license = { text = "MIT" }
13
+
14
+ # dependencies = [
15
+ # "torch",
16
+ # "torchvision",
17
+ # "openmim",
18
+ # "gradio",
19
+ # "pandas",
20
+ # "numpy",
21
+ # "opencv-python-headless",
22
+ # "jinja2",
23
+ # "transformers",
24
+ # "huggingface_hub",
25
+ # "requests",
26
+ # ]
27
+
28
+ # # !mim install mmengine
29
+ # # !mim install mmcv
30
+ # # !mim install mmdet
31
+ # # !mim install mmocr
32
+
33
+ [tool.ruff]
34
+ # Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
35
+ select = ["E", "F"]
36
+ ignore = []
37
+
38
+ # Allow autofix for all enabled rules (when `--fix`) is provided.
39
+ fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
40
+ unfixable = []
41
+
42
+ # Exclude a variety of commonly ignored directories.
43
+ exclude = [
44
+ ".bzr",
45
+ ".direnv",
46
+ ".eggs",
47
+ ".git",
48
+ ".hg",
49
+ ".mypy_cache",
50
+ ".nox",
51
+ ".pants.d",
52
+ ".pytype",
53
+ ".ruff_cache",
54
+ ".svn",
55
+ ".tox",
56
+ ".venv",
57
+ "__pypackages__",
58
+ "_build",
59
+ "buck-out",
60
+ "build",
61
+ "dist",
62
+ "node_modules",
63
+ "venv",
64
+ ]
65
+
66
+ # Same as Black.
67
+ line-length = 120
68
+
69
+ # Allow unused variables when underscore-prefixed.
70
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
71
+
72
+ # Assume Python 3.10.
73
+ target-version = "py310"
74
+
75
+ [tool.ruff.mccabe]
76
+ # Unlike Flake8, default to a complexity level of 10.
77
+ max-complexity = 10
78
+
79
+ [tool.black]
80
+ line-length = 120
requirements.txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # !pip install -U openmim
2
+ # !mim install mmengine
3
+ # !mim install mmcv
4
+ # !mim install mmdet
5
+ # !mim install mmocr
6
+ torch
7
+ torchvision
8
+ openmim
9
+ gradio
10
+ pandas
11
+ numpy
12
+ opencv-python-headless
13
+ jinja2
14
+ transformers
15
+ huggingface_hub
16
+ requests
17
+ # scipy
18
+ # sklearn
src/htr_pipeline/__init__.py ADDED
File without changes
src/htr_pipeline/gradio_backend.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import gradio as gr
4
+ import pandas as pd
5
+
6
+ from src.htr_pipeline.inferencer import Inferencer, InferencerInterface
7
+ from src.htr_pipeline.pipeline import Pipeline, PipelineInterface
8
+
9
+
10
+ class SingletonModelLoader:
11
+ _instance = None
12
+
13
+ def __new__(cls, *args, **kwargs):
14
+ if not cls._instance:
15
+ cls._instance = super(SingletonModelLoader, cls).__new__(cls, *args, **kwargs)
16
+ return cls._instance
17
+
18
+ def __init__(self):
19
+ self.inferencer = Inferencer(local_run=True)
20
+ self.pipeline = Pipeline(self.inferencer)
21
+
22
+
23
+ # fast track
24
+ class FastTrack:
25
+ def __init__(self, model_loader):
26
+ self.pipeline: PipelineInterface = model_loader.pipeline
27
+
28
+ def segment_to_xml(self, image, radio_button_choices):
29
+ xml_xml = "page_xml.xml"
30
+ xml_txt = "page_txt.txt"
31
+
32
+ if os.path.exists(f"./{xml_xml}"):
33
+ os.remove(f"./{xml_xml}")
34
+
35
+ rendered_xml = self.pipeline.running_htr_pipeline(image)
36
+
37
+ with open(xml_xml, "w") as f:
38
+ f.write(rendered_xml)
39
+
40
+ xml_img = self.visualize_xml_and_return_txt(image, xml_txt)
41
+
42
+ if radio_button_choices == "Text file":
43
+ returned_file_extension = xml_txt
44
+ else:
45
+ returned_file_extension = xml_xml
46
+
47
+ return xml_img, returned_file_extension, gr.update(visible=True)
48
+
49
+ def segment_to_xml_api(self, image):
50
+ rendered_xml = self.pipeline.running_htr_pipeline(image)
51
+ return rendered_xml
52
+
53
+ def visualize_xml_and_return_txt(self, img, xml_txt):
54
+ xml_img = self.pipeline.visualize_xml(img)
55
+
56
+ if os.path.exists(f"./{xml_txt}"):
57
+ os.remove(f"./{xml_txt}")
58
+
59
+ self.pipeline.parse_xml_to_txt()
60
+
61
+ return xml_img
62
+
63
+
64
+ # Custom track
65
+ class CustomTrack:
66
+ def __init__(self, model_loader):
67
+ self.inferencer: InferencerInterface = model_loader.inferencer
68
+
69
+ def region_segment(self, image, pred_score_threshold, containments_treshold):
70
+ predicted_regions, regions_cropped_ordered, _, _ = self.inferencer.predict_regions(
71
+ image, pred_score_threshold, containments_treshold
72
+ )
73
+ return predicted_regions, regions_cropped_ordered, gr.update(visible=False), gr.update(visible=True)
74
+
75
+ def line_segment(self, image, pred_score_threshold, containments_threshold):
76
+ predicted_lines, lines_cropped_ordered, _ = self.inferencer.predict_lines(
77
+ image, pred_score_threshold, containments_threshold
78
+ )
79
+ return (
80
+ predicted_lines,
81
+ image,
82
+ lines_cropped_ordered,
83
+ lines_cropped_ordered, #
84
+ lines_cropped_ordered, # temp_gallery
85
+ gr.update(visible=True),
86
+ gr.update(visible=True),
87
+ gr.update(visible=False),
88
+ gr.update(visible=True),
89
+ )
90
+
91
+ def transcribe_text(self, df, images):
92
+ transcription_temp_list_with_score = []
93
+ mapping_dict = {}
94
+
95
+ for image in images:
96
+ transcribed_text, prediction_score_from_htr = self.inferencer.transcribe(image)
97
+ transcription_temp_list_with_score.append((transcribed_text, prediction_score_from_htr))
98
+
99
+ df_trans_explore = pd.DataFrame(
100
+ transcription_temp_list_with_score, columns=["Transcribed text", "HTR prediction score"]
101
+ )
102
+
103
+ mapping_dict[transcribed_text] = image
104
+
105
+ yield df_trans_explore[["Transcribed text"]], df_trans_explore, mapping_dict, gr.update(
106
+ visible=False
107
+ ), gr.update(visible=True), gr.update(visible=False)
108
+
109
+ def get_select_index_image(self, images_from_gallery, evt: gr.SelectData):
110
+ return images_from_gallery[evt.index]["name"]
111
+
112
+ def get_select_index_df(self, transcribed_text_df_finish, mapping_dict, evt: gr.SelectData):
113
+ df_list = transcribed_text_df_finish["Transcribed text"].tolist()
114
+ key_text = df_list[evt.index[0]]
115
+ sorted_image = mapping_dict[key_text]
116
+ new_first = [sorted_image]
117
+ new_list = [img for txt, img in mapping_dict.items() if txt != key_text]
118
+ new_first.extend(new_list)
119
+ return new_first
120
+
121
+ def download_df_to_txt(self, transcribed_df):
122
+ text_in_list = transcribed_df["Transcribed text"].tolist()
123
+
124
+ file_name = "./transcribed_text.txt"
125
+ text_file = open(file_name, "w")
126
+
127
+ for text in text_in_list:
128
+ text_file.write(text + "\n")
129
+ text_file.close()
130
+
131
+ return file_name, gr.update(visible=True)
132
+
133
+ # def transcribe_text_another_model(self, df, images):
134
+ # transcription_temp_list = []
135
+ # for image in images:
136
+ # transcribed_text = inferencer.transcribe_different_model(image)
137
+ # transcription_temp_list.append(transcribed_text)
138
+ # df_trans = pd.DataFrame(transcription_temp_list, columns=["Transcribed_text"])
139
+ # yield df_trans, df_trans, gr.update(visible=False)
140
+
141
+
142
+ if __name__ == "__main__":
143
+ pass
src/htr_pipeline/inferencer.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Protocol, Tuple
2
+
3
+ import gradio as gr
4
+ import mmcv
5
+ import numpy as np
6
+
7
+ from src.htr_pipeline.models import HtrModels
8
+ from src.htr_pipeline.utils.filter_segmask import FilterSegMask
9
+ from src.htr_pipeline.utils.helper import timer_func
10
+ from src.htr_pipeline.utils.order_of_object import OrderObject
11
+ from src.htr_pipeline.utils.preprocess_img import Preprocess
12
+ from src.htr_pipeline.utils.process_segmask import SegMaskHelper
13
+
14
+
15
+ class Inferencer:
16
+ def __init__(self, local_run=False):
17
+ htr_models = HtrModels(local_run)
18
+ self.seg_model = htr_models.load_region_model()
19
+ self.line_model = htr_models.load_line_model()
20
+ self.htr_model_inferencer = htr_models.load_htr_model()
21
+
22
+ self.process_seg_mask = SegMaskHelper()
23
+ self.postprocess_seg_mask = FilterSegMask()
24
+ self.ordering = OrderObject()
25
+ self.preprocess_img = Preprocess()
26
+
27
+ @timer_func
28
+ def predict_regions(self, input_image, pred_score_threshold=0.5, containments_threshold=0.5, visualize=True):
29
+ input_image = self.preprocess_img.binarize_img(input_image)
30
+
31
+ image = mmcv.imread(input_image)
32
+ result = self.seg_model(image, return_datasample=True)
33
+ result_pred = result["predictions"][0]
34
+
35
+ filtered_result_pred = self.postprocess_seg_mask.filter_on_pred_threshold(
36
+ result_pred, pred_score_threshold=pred_score_threshold
37
+ )
38
+
39
+ if len(filtered_result_pred.pred_instances.masks) == 0:
40
+ raise gr.Error("No Regions were predicted by the model")
41
+
42
+ else:
43
+ result_align = self.process_seg_mask.align_masks_with_image(filtered_result_pred, image)
44
+ result_clean = self.postprocess_seg_mask.remove_overlapping_masks(
45
+ predicted_mask=result_align, containments_threshold=containments_threshold
46
+ )
47
+
48
+ if visualize:
49
+ result_viz = self.seg_model.visualize(
50
+ inputs=[image], preds=[result_clean], return_vis=True, no_save_vis=True
51
+ )[0]
52
+ else:
53
+ result_viz = None
54
+
55
+ regions_cropped, polygons = self.process_seg_mask.crop_masks(result_clean, image)
56
+ order = self.ordering.order_regions_marginalia(result_clean)
57
+
58
+ regions_cropped_ordered = [regions_cropped[i] for i in order]
59
+ polygons_ordered = [polygons[i] for i in order]
60
+ masks_ordered = [result_clean.pred_instances.masks[i] for i in order]
61
+
62
+ return result_viz, regions_cropped_ordered, polygons_ordered, masks_ordered
63
+
64
+ @timer_func
65
+ def predict_lines(
66
+ self,
67
+ image,
68
+ pred_score_threshold=0.5,
69
+ containments_threshold=0.5,
70
+ line_spacing_factor=0.5,
71
+ visualize=True,
72
+ custom_track=True,
73
+ ):
74
+ result_tl = self.line_model(image, return_datasample=True)
75
+ result_tl_pred = result_tl["predictions"][0]
76
+
77
+ filtered_result_tl_pred = self.postprocess_seg_mask.filter_on_pred_threshold(
78
+ result_tl_pred, pred_score_threshold=pred_score_threshold
79
+ )
80
+
81
+ if len(filtered_result_tl_pred.pred_instances.masks) == 0 and custom_track:
82
+ raise gr.Error("No Lines were predicted by the model")
83
+
84
+ elif len(filtered_result_tl_pred.pred_instances.masks) == 0 and not custom_track:
85
+ return None, None, None
86
+
87
+ else:
88
+ result_tl_align = self.process_seg_mask.align_masks_with_image(filtered_result_tl_pred, image)
89
+ result_tl_clean = self.postprocess_seg_mask.remove_overlapping_masks(
90
+ predicted_mask=result_tl_align, containments_threshold=containments_threshold
91
+ )
92
+
93
+ if visualize:
94
+ result_viz = self.seg_model.visualize(
95
+ inputs=[image], preds=[result_tl_clean], return_vis=True, no_save_vis=True
96
+ )[0]
97
+ else:
98
+ result_viz = None
99
+
100
+ lines_cropped, lines_polygons = self.process_seg_mask.crop_masks(result_tl_clean, image)
101
+ ordered_indices = self.ordering.order_lines(
102
+ line_image=result_tl_clean, line_spacing_factor=line_spacing_factor
103
+ )
104
+
105
+ lines_cropped_ordered = [lines_cropped[i] for i in ordered_indices]
106
+ lines_polygons_ordered = [lines_polygons[i] for i in ordered_indices]
107
+
108
+ return result_viz, lines_cropped_ordered, lines_polygons_ordered
109
+
110
+ @timer_func
111
+ def transcribe(self, line_cropped):
112
+ result_rec = self.htr_model_inferencer(line_cropped)
113
+ return result_rec["predictions"][0]["text"], result_rec["predictions"][0]["scores"]
114
+
115
+ # def transcribe_different_model(self, image):
116
+ # processor = TrOCRProcessor.from_pretrained("microsoft/trocr-base-handwritten")
117
+ # model = VisionEncoderDecoderModel.from_pretrained("microsoft/trocr-base-handwritten")
118
+
119
+ # # prepare image
120
+ # pixel_values = processor(image, return_tensors="pt").pixel_values
121
+
122
+ # # generate (no beam search)
123
+ # generated_ids = model.generate(pixel_values)
124
+
125
+ # # decode
126
+ # generated_text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
127
+
128
+ # return generated_text
129
+
130
+
131
+ class InferencerInterface(Protocol):
132
+ def predict_regions(
133
+ self,
134
+ image: np.array,
135
+ pred_score_threshold: float,
136
+ containments_threshold: float,
137
+ visualize: bool = False,
138
+ ) -> Tuple:
139
+ ...
140
+
141
+ def predict_lines(
142
+ self,
143
+ text_region: np.array,
144
+ pred_score_threshold: float,
145
+ containments_threshold: float,
146
+ visualize: bool = False,
147
+ custom_track: bool = False,
148
+ ) -> Tuple:
149
+ ...
150
+
151
+ def transcribe(
152
+ self,
153
+ line: np.array,
154
+ ) -> Tuple[str, float]:
155
+ ...
156
+
157
+
158
+ if __name__ == "__main__":
159
+ prediction_model = Inferencer()
src/htr_pipeline/models.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import torch
4
+ from huggingface_hub import snapshot_download
5
+ from mmdet.apis import DetInferencer
6
+
7
+ # from mmengine import Config
8
+ from mmocr.apis import TextRecInferencer
9
+
10
+
11
+ class HtrModels:
12
+ def __init__(self, local_run=False):
13
+ self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
14
+ SECRET_KEY = os.environ.get("AM_I_IN_A_DOCKER_CONTAINER", False)
15
+
16
+ model_folder = "./models"
17
+ self.region_config = f"{model_folder}/RmtDet_regions/rtmdet_m_textregions_2_concat.py"
18
+
19
+ self.line_config = f"{model_folder}/RmtDet_lines/rtmdet_m_textlines_2_concat.py"
20
+ self.line_checkpoint = f"{model_folder}/RmtDet_lines/epoch_12.pth"
21
+ self.mmocr_config = f"{model_folder}/SATRN/_base_satrn_shallow_concat.py"
22
+
23
+ if SECRET_KEY:
24
+ config_path = self.get_config()
25
+ self.region_checkpoint = config_path["region_checkpoint"]
26
+ self.line_checkpoint = config_path["line_checkpoint"]
27
+ self.mmocr_checkpoint = config_path["mmocr_checkpoint"]
28
+
29
+ else:
30
+ self.region_checkpoint = f"{model_folder}/RmtDet_regions/epoch_12.pth"
31
+ self.line_checkpoint = f"{model_folder}/RmtDet_lines/epoch_12.pth"
32
+ self.mmocr_checkpoint = f"{model_folder}/SATRN/epoch_5.pth"
33
+
34
+ def load_region_model(self):
35
+ # build the model from a config file and a checkpoint file
36
+ return DetInferencer(self.region_config, self.region_checkpoint, device=self.device)
37
+
38
+ def load_line_model(self):
39
+ return DetInferencer(self.line_config, self.line_checkpoint, device=self.device)
40
+
41
+ def load_htr_model(self):
42
+ inferencer = TextRecInferencer(self.mmocr_config, self.mmocr_checkpoint, device=self.device)
43
+ return inferencer
44
+
45
+ @staticmethod
46
+ def get_config():
47
+ path_models = snapshot_download(
48
+ "Riksarkivet/HTR_pipeline_models",
49
+ allow_patterns=["*.pth"],
50
+ token="__INSERT__FINS_HUGGINFACE_TOKEN__",
51
+ cache_dir="./",
52
+ )
53
+ config_path = {
54
+ "region_checkpoint": os.path.join(path_models, "RmtDet_regions/epoch_12.pth"),
55
+ "line_checkpoint": os.path.join(path_models, "RmtDet_lines/epoch_12.pth"),
56
+ "mmocr_checkpoint": os.path.join(path_models, "SATRN/epoch_5.pth"),
57
+ }
58
+
59
+ return config_path
src/htr_pipeline/pipeline.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Protocol
2
+
3
+ import mmcv
4
+ import numpy as np
5
+
6
+ from htr_pipeline.utils.parser_xml import XmlParser
7
+ from src.htr_pipeline.inferencer import Inferencer
8
+ from src.htr_pipeline.utils.helper import timer_func
9
+ from src.htr_pipeline.utils.preprocess_img import Preprocess
10
+ from src.htr_pipeline.utils.process_xml import XMLHelper
11
+
12
+
13
+ class Pipeline:
14
+ def __init__(self, inferencer: Inferencer) -> None:
15
+ self.inferencer = inferencer
16
+ self.xml = XMLHelper()
17
+ self.preprocess_img = Preprocess()
18
+
19
+ @timer_func
20
+ def running_htr_pipeline(
21
+ self,
22
+ input_image: np.ndarray,
23
+ pred_score_threshold_regions: float = 0.4,
24
+ pred_score_threshold_lines: float = 0.4,
25
+ containments_threshold: float = 0.5,
26
+ ) -> str:
27
+ input_image = self.preprocess_img.binarize_img(input_image)
28
+ image = mmcv.imread(input_image)
29
+
30
+ rendered_xml = self.xml.image_to_page_xml(
31
+ image, pred_score_threshold_regions, pred_score_threshold_lines, containments_threshold, self.inferencer
32
+ )
33
+
34
+ return rendered_xml
35
+
36
+ @timer_func
37
+ def visualize_xml(self, input_image: np.ndarray) -> np.ndarray:
38
+ self.xml_visualizer_and_parser = XmlParser()
39
+ bin_input_image = self.preprocess_img.binarize_img(input_image)
40
+ xml_image = self.xml_visualizer_and_parser.visualize_xml(bin_input_image)
41
+ return xml_image
42
+
43
+ @timer_func
44
+ def parse_xml_to_txt(self) -> None:
45
+ self.xml_visualizer_and_parser.xml_to_txt()
46
+
47
+
48
+ class PipelineInterface(Protocol):
49
+ def __init__(self, inferencer: Inferencer) -> None:
50
+ ...
51
+
52
+ def running_htr_pipeline(
53
+ self,
54
+ input_image: np.ndarray,
55
+ pred_score_threshold_regions: float = 0.4,
56
+ pred_score_threshold_lines: float = 0.4,
57
+ containments_threshold: float = 0.5,
58
+ ) -> str:
59
+ ...
60
+
61
+ def visualize_xml(self, input_image: np.ndarray) -> np.ndarray:
62
+ ...
63
+
64
+ def parse_xml_to_txt(self) -> None:
65
+ ...
66
+
67
+
68
+ if __name__ == "__main__":
69
+ prediction_model = Inferencer()
70
+ pipeline = Pipeline(prediction_model)
src/htr_pipeline/utils/__init__.py ADDED
File without changes
src/htr_pipeline/utils/filter_segmask.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import torch
4
+ from mmdet.structures import DetDataSample
5
+ from mmengine.structures import InstanceData
6
+
7
+
8
+ class FilterSegMask:
9
+ def __init__(self):
10
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
11
+
12
+ # Removes smaller masks that are contained in a bigger mask
13
+ # @timer_func
14
+ def remove_overlapping_masks(self, predicted_mask, method="mask", containments_threshold=0.5):
15
+ # Convert masks to binary images
16
+ masks = [mask.cpu().numpy() for mask in predicted_mask.pred_instances.masks]
17
+ masks_binary = [(mask > 0).astype(np.uint8) for mask in masks]
18
+
19
+ masks_tensor = predicted_mask.pred_instances.masks
20
+ masks_tensor = [mask.to(self.device) for mask in masks_tensor]
21
+
22
+ # Compute bounding boxes and areas
23
+ boxes = [cv2.boundingRect(mask) for mask in masks_binary]
24
+
25
+ # Compute pairwise containment
26
+ containments = np.zeros((len(masks), len(masks)))
27
+
28
+ for i in range(len(masks)):
29
+ box_i = boxes[i]
30
+
31
+ for j in range(i + 1, len(masks)):
32
+ box_j = boxes[j]
33
+
34
+ if method == "mask":
35
+ containment = self._calculate_containment_mask(masks_tensor[i], masks_tensor[j])
36
+ containments[i, j] = containment
37
+ containment = self._calculate_containment_mask(masks_tensor[j], masks_tensor[i])
38
+ containments[j, i] = containment
39
+ elif method == "bbox":
40
+ containment = self._calculate_containment_bbox(box_i, box_j)
41
+ containments[i, j] = containment
42
+ containment = self._calculate_containment_bbox(box_j, box_i)
43
+ containments[j, i] = containment
44
+
45
+ # Keep only the biggest masks for overlapping pairs
46
+ keep_mask = np.ones(len(masks), dtype=np.bool_)
47
+ for i in range(len(masks)):
48
+ if not keep_mask[i]:
49
+ continue
50
+ if np.any(containments[i] > containments_threshold):
51
+ contained_indices = np.where(containments[i] > containments_threshold)[0]
52
+ for j in contained_indices:
53
+ if np.count_nonzero(masks_binary[i]) >= np.count_nonzero(masks_binary[j]):
54
+ keep_mask[j] = False
55
+ else:
56
+ keep_mask[i] = False
57
+
58
+ # Create a new DetDataSample with only selected instances
59
+ filtered_result = DetDataSample(metainfo=predicted_mask.metainfo)
60
+ pred_instances = InstanceData(metainfo=predicted_mask.metainfo)
61
+
62
+ masks = [mask for i, mask in enumerate(masks) if keep_mask[i]]
63
+ list_of_tensor_masks = [torch.from_numpy(mask) for mask in masks]
64
+ stacked_masks = torch.stack(list_of_tensor_masks)
65
+
66
+ updated_filtered_result = self._stacked_masks_update_data_sample(
67
+ filtered_result, stacked_masks, pred_instances, keep_mask, predicted_mask
68
+ )
69
+
70
+ return updated_filtered_result
71
+
72
+ def _stacked_masks_update_data_sample(self, filtered_result, stacked_masks, pred_instances, keep_mask, result):
73
+ pred_instances.masks = stacked_masks
74
+ pred_instances.bboxes = self._update_datasample_cat(result.pred_instances.bboxes.tolist(), keep_mask)
75
+ pred_instances.scores = self._update_datasample_cat(result.pred_instances.scores.tolist(), keep_mask)
76
+ pred_instances.kernels = self._update_datasample_cat(result.pred_instances.kernels.tolist(), keep_mask)
77
+ pred_instances.labels = self._update_datasample_cat(result.pred_instances.labels.tolist(), keep_mask)
78
+ pred_instances.priors = self._update_datasample_cat(result.pred_instances.priors.tolist(), keep_mask)
79
+
80
+ filtered_result.pred_instances = pred_instances
81
+
82
+ return filtered_result
83
+
84
+ def _calculate_containment_bbox(self, box_a, box_b):
85
+ xA = max(box_a[0], box_b[0]) # max x0
86
+ yA = max(box_a[1], box_b[1]) # max y0
87
+ xB = min(box_a[0] + box_a[2], box_b[0] + box_b[2]) # min x1
88
+ yB = min(box_a[1] + box_a[3], box_b[1] + box_b[3]) # min y1
89
+
90
+ box_a_area = box_a[2] * box_a[3]
91
+ box_b_area = box_b[2] * box_b[3]
92
+
93
+ intersection_area = max(0, xB - xA + 1) * max(0, yB - yA + 1)
94
+ containment = intersection_area / box_a_area if box_a_area > 0 else 0
95
+ return containment
96
+
97
+ def _calculate_containment_mask(self, mask_a, mask_b):
98
+ intersection = torch.logical_and(mask_a, mask_b).sum().float()
99
+ containment = intersection / mask_b.sum().float() if mask_b.sum() > 0 else 0
100
+ return containment
101
+
102
+ def _update_datasample_cat(self, cat_list, keep_mask):
103
+ cat_keep = [cat for i, cat in enumerate(cat_list) if keep_mask[i]]
104
+ tensor_cat_keep = torch.tensor(cat_keep)
105
+ return tensor_cat_keep
106
+
107
+ # @timer_func
108
+ def filter_on_pred_threshold(self, result_pred, pred_score_threshold=0.5):
109
+ id_list = []
110
+ for id, pred_score in enumerate(result_pred.pred_instances.scores):
111
+ if pred_score > pred_score_threshold:
112
+ id_list.append(id)
113
+
114
+ # Create a new DetDataSample with only selected instances
115
+ new_filtered_result = DetDataSample(metainfo=result_pred.metainfo)
116
+ new_pred_instances = InstanceData(metainfo=result_pred.metainfo)
117
+
118
+ new_pred_instances.masks = result_pred.pred_instances.masks[id_list]
119
+ new_pred_instances.bboxes = result_pred.pred_instances.bboxes[id_list]
120
+ new_pred_instances.scores = result_pred.pred_instances.scores[id_list]
121
+ new_pred_instances.kernels = result_pred.pred_instances.kernels[id_list]
122
+ new_pred_instances.labels = result_pred.pred_instances.labels[id_list]
123
+ new_pred_instances.priors = result_pred.pred_instances.priors[id_list]
124
+
125
+ new_filtered_result.pred_instances = new_pred_instances
126
+ return new_filtered_result
127
+ return new_filtered_result
src/htr_pipeline/utils/helper.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import threading
3
+ import time
4
+
5
+ import tqdm
6
+
7
+
8
+ def timer_func(func):
9
+ # This function shows the execution time of
10
+ # the function object passed
11
+ def wrap_func(*args, **kwargs):
12
+ t1 = time.time()
13
+ result = func(*args, **kwargs)
14
+ t2 = time.time()
15
+ print(f"Function {func.__name__!r} executed in {(t2-t1):.4f}s")
16
+ return result
17
+
18
+ return wrap_func
19
+
20
+
21
+ def long_running_function(*args, **kwargs):
22
+ # print("Running with args:%s and kwargs:%s" % (args, kwargs))
23
+ time.sleep(5)
24
+ return "success"
25
+
26
+
27
+ def provide_progress_bar(function, estimated_time, tstep=0.2, tqdm_kwargs={}, args=[], kwargs={}):
28
+ """Tqdm wrapper for a long-running function
29
+
30
+ args:
31
+ function - function to run
32
+ estimated_time - how long you expect the function to take
33
+ tstep - time delta (seconds) for progress bar updates
34
+ tqdm_kwargs - kwargs to construct the progress bar
35
+ args - args to pass to the function
36
+ kwargs - keyword args to pass to the function
37
+ ret:
38
+ function(*args, **kwargs)
39
+ """
40
+ ret = [None] # Mutable var so the function can store its return value
41
+
42
+ def myrunner(function, ret, *args, **kwargs):
43
+ ret[0] = function(*args, **kwargs)
44
+
45
+ thread = threading.Thread(target=myrunner, args=(function, ret) + tuple(args), kwargs=kwargs)
46
+ pbar = tqdm.tqdm(total=estimated_time, **tqdm_kwargs)
47
+
48
+ thread.start()
49
+ while thread.is_alive():
50
+ thread.join(timeout=tstep)
51
+ pbar.update(tstep)
52
+ pbar.close()
53
+ return ret[0]
54
+
55
+
56
+ def progress_wrapped(estimated_time, tstep=0.2, tqdm_kwargs={}):
57
+ """Decorate a function to add a progress bar"""
58
+
59
+ def real_decorator(function):
60
+ @functools.wraps(function)
61
+ def wrapper(*args, **kwargs):
62
+ return provide_progress_bar(
63
+ function, estimated_time=estimated_time, tstep=tstep, tqdm_kwargs=tqdm_kwargs, args=args, kwargs=kwargs
64
+ )
65
+
66
+ return wrapper
67
+
68
+ return real_decorator
69
+
70
+
71
+ @progress_wrapped(estimated_time=5)
72
+ def another_long_running_function(*args, **kwargs):
73
+ # print("Running with args:%s and kwargs:%s" % (args, kwargs))
74
+ time.sleep(5)
75
+ return "success"
76
+
77
+
78
+ if __name__ == "__main__":
79
+ # Basic example
80
+ retval = provide_progress_bar(long_running_function, estimated_time=5)
81
+ print(retval)
82
+
83
+ # Full example
84
+ retval = provide_progress_bar(
85
+ long_running_function,
86
+ estimated_time=5,
87
+ tstep=1 / 5.0,
88
+ tqdm_kwargs={"bar_format": "{desc}: {percentage:3.0f}%|{bar}| {n:.1f}/{total:.1f} [{elapsed}<{remaining}]"},
89
+ args=(1, "foo"),
90
+ kwargs={"spam": "eggs"},
91
+ )
92
+ print(retval)
93
+
94
+ # Example of using the decorator
95
+ retval = another_long_running_function()
96
+ print(retval)
97
+ retval = another_long_running_function()
98
+ print(retval)
99
+ print(retval)
src/htr_pipeline/utils/order_of_object.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+
5
+ class OrderObject:
6
+ def __init__(self):
7
+ pass
8
+
9
+ def order_lines(self, line_image, line_spacing_factor=0.5):
10
+ bounding_boxes = line_image.pred_instances.bboxes.tolist()
11
+ center_points = [(box[1] + box[3]) / 2 for box in bounding_boxes]
12
+ horizontal_positions = [(box[0] + box[2]) / 2 for box in bounding_boxes]
13
+
14
+ # Calculate the threshold distance
15
+ threshold_distance = self._calculate_threshold_distance(bounding_boxes, line_spacing_factor)
16
+
17
+ # Sort the indices based on vertical center points and horizontal positions
18
+ indices = list(range(len(bounding_boxes)))
19
+ indices.sort(
20
+ key=lambda i: (
21
+ center_points[i] // threshold_distance,
22
+ horizontal_positions[i],
23
+ )
24
+ )
25
+
26
+ # Order text lines
27
+ return indices
28
+
29
+ def _calculate_threshold_distance(self, bounding_boxes, line_spacing_factor=0.5):
30
+ # Calculate the average height of the text lines
31
+ total_height = sum(box[3] - box[1] for box in bounding_boxes)
32
+ average_height = total_height / len(bounding_boxes)
33
+
34
+ # Calculate the threshold distance, Set a factor for the threshold distance (adjust as needed)
35
+ threshold_distance = average_height * line_spacing_factor
36
+
37
+ # Return the threshold distance
38
+ return threshold_distance
39
+
40
+ def order_regions_marginalia(self, region_image, margin_ratio=0.2, histogram_bins=50, histogram_dip_ratio=0.5):
41
+ bounding_boxes = region_image.pred_instances.bboxes.tolist()
42
+ img_width = region_image.metainfo["ori_shape"][1]
43
+
44
+ regions = [[i, x[0], x[1], x[0] + x[2], x[1] + x[3]] for i, x in enumerate(bounding_boxes)]
45
+
46
+ # Create a pandas DataFrame from the regions
47
+ df = pd.DataFrame(regions, columns=["region_id", "x_min", "y_min", "x_max", "y_max"])
48
+
49
+ # Calculate the centroids of the bounding boxes
50
+ df["centroid_x"] = (df["x_min"] + df["x_max"]) / 2
51
+ df["centroid_y"] = (df["y_min"] + df["y_max"]) / 2
52
+
53
+ # Calculate a histogram of the x-coordinates of the centroids
54
+ histogram, bin_edges = np.histogram(df["centroid_x"], bins=histogram_bins)
55
+
56
+ # Determine if there's a significant dip in the histogram, which would suggest a two-page layout
57
+ is_two_pages = np.min(histogram) < np.max(histogram) * histogram_dip_ratio
58
+
59
+ if is_two_pages:
60
+ # Determine which page each region is on
61
+ page_width = int(img_width / 2)
62
+ df["page"] = (df["centroid_x"] > page_width).astype(int)
63
+
64
+ # Determine if the region is in the margin
65
+ margin_width = page_width * margin_ratio
66
+ df["is_margin"] = ((df["page"] == 0) & (df["centroid_x"] < margin_width)) | (
67
+ (df["page"] == 1) & (df["centroid_x"] > img_width - margin_width)
68
+ )
69
+ else:
70
+ df["page"] = 0
71
+ df["is_margin"] = (df["centroid_x"] < img_width * margin_ratio) | (
72
+ df["centroid_x"] > img_width - page_width * margin_ratio
73
+ )
74
+
75
+ # Define a custom sorting function
76
+ sort_regions = lambda row: (
77
+ row["page"],
78
+ row["is_margin"],
79
+ row["centroid_y"],
80
+ row["centroid_x"],
81
+ )
82
+
83
+ # Sort the DataFrame using the custom function
84
+ df["sort_key"] = df.apply(sort_regions, axis=1)
85
+ df = df.sort_values("sort_key")
86
+
87
+ # Return the ordered regions
88
+ return df["region_id"].tolist()
src/htr_pipeline/utils/parser_xml.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import os
3
+ import random
4
+ import xml.etree.ElementTree as ET
5
+
6
+ from PIL import Image, ImageDraw, ImageFont
7
+
8
+
9
+ class XmlParser:
10
+ def __init__(self, page_xml="./page_xml.xml"):
11
+ self.tree = ET.parse(page_xml, parser=ET.XMLParser(encoding="utf-8"))
12
+ self.root = self.tree.getroot()
13
+ self.namespace = "{http://schema.primaresearch.org/PAGE/gts/pagecontent/2013-07-15}"
14
+
15
+ def visualize_xml(
16
+ self,
17
+ background_image,
18
+ font_size=9,
19
+ text_offset=10,
20
+ font_path_tff="./src/htr_pipeline/utils/templates/arial.ttf",
21
+ ):
22
+ image = Image.fromarray(background_image).convert("RGBA")
23
+ image_width = int(self.root.find(f"{self.namespace}Page").attrib["imageWidth"])
24
+ image_height = int(self.root.find(f"{self.namespace}Page").attrib["imageHeight"])
25
+
26
+ text_offset = -text_offset
27
+ base_font_size = font_size
28
+ font_path = font_path_tff
29
+
30
+ max_bbox_width = 0 # Initialize maximum bounding box width
31
+
32
+ for textregion in self.root.findall(f".//{self.namespace}TextRegion"):
33
+ coords = textregion.find(f"{self.namespace}Coords").attrib["points"].split()
34
+ points = [tuple(map(int, point.split(","))) for point in coords]
35
+ x_coords, y_coords = zip(*points)
36
+ min_x, max_x = min(x_coords), max(x_coords)
37
+ bbox_width = max_x - min_x # Width of the current bounding box
38
+ max_bbox_width = max(max_bbox_width, bbox_width) # Update maximum bounding box width
39
+
40
+ scaling_factor = max_bbox_width / 400.0 # Use maximum bounding box width for scaling
41
+ font_size_scaled = int(base_font_size * scaling_factor)
42
+ font = ImageFont.truetype(font_path, font_size_scaled)
43
+
44
+ for textregion in self.root.findall(f".//{self.namespace}TextRegion"):
45
+ fill_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), 100)
46
+ for textline in textregion.findall(f".//{self.namespace}TextLine"):
47
+ coords = textline.find(f"{self.namespace}Coords").attrib["points"].split()
48
+ points = [tuple(map(int, point.split(","))) for point in coords]
49
+
50
+ poly_image = Image.new("RGBA", image.size)
51
+ poly_draw = ImageDraw.Draw(poly_image)
52
+ poly_draw.polygon(points, fill=fill_color)
53
+
54
+ text = textline.find(f"{self.namespace}TextEquiv").find(f"{self.namespace}Unicode").text
55
+
56
+ x_coords, y_coords = zip(*points)
57
+ min_x, max_x = min(x_coords), max(x_coords)
58
+ min_y = min(y_coords)
59
+ text_width, text_height = poly_draw.textsize(text, font=font) # Get text size
60
+ text_position = (
61
+ (min_x + max_x) // 2 - text_width // 2,
62
+ min_y + text_offset,
63
+ ) # Center text horizontally
64
+
65
+ poly_draw.text(text_position, text, fill=(0, 0, 0), font=font)
66
+ image = Image.alpha_composite(image, poly_image)
67
+
68
+ return image
69
+
70
+ def xml_to_txt(self, output_file="page_txt.txt"):
71
+ with open(output_file, "w", encoding="utf-8") as f:
72
+ for textregion in self.root.findall(f".//{self.namespace}TextRegion"):
73
+ for textline in textregion.findall(f".//{self.namespace}TextLine"):
74
+ text = textline.find(f"{self.namespace}TextEquiv").find(f"{self.namespace}Unicode").text
75
+ f.write(text + "\n")
76
+ f.write("\n")
src/htr_pipeline/utils/preprocess_img.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+
5
+ class Preprocess:
6
+ def __init__(self):
7
+ pass
8
+
9
+ def binarize_img(self, img):
10
+ # print(img)
11
+ # img_ori = cv2.imread(img)
12
+ img_ori = cv2.cvtColor(img.astype("uint8"), cv2.COLOR_RGB2BGR)
13
+ img_gray = cv2.cvtColor(img_ori, cv2.COLOR_BGR2GRAY)
14
+ dst = cv2.fastNlMeansDenoising(img_gray, h=31, templateWindowSize=7, searchWindowSize=21)
15
+ img_blur = cv2.medianBlur(dst, 3).astype("uint8")
16
+ threshed = cv2.adaptiveThreshold(img_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
17
+ img_gradio = cv2.cvtColor(threshed, cv2.COLOR_BGR2RGB)
18
+
19
+ return img_gradio
src/htr_pipeline/utils/process_segmask.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import torch
4
+ from mmdet.registry import VISUALIZERS
5
+
6
+
7
+ class SegMaskHelper:
8
+ def __init__(self):
9
+ pass
10
+
11
+ # Pad the masks to image size (bug in RTMDet config?)
12
+ # @timer_func
13
+ def align_masks_with_image(self, result, img):
14
+ masks = list()
15
+
16
+ img = img[..., ::-1].copy()
17
+
18
+ for j, mask in enumerate(result.pred_instances.masks):
19
+ numpy_mask = mask.cpu().numpy()
20
+ mask = cv2.resize(
21
+ numpy_mask.astype(np.uint8),
22
+ (img.shape[1], img.shape[0]),
23
+ interpolation=cv2.INTER_NEAREST,
24
+ )
25
+
26
+ # Pad the mask to match the size of the image
27
+ padded_mask = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8)
28
+ padded_mask[: mask.shape[0], : mask.shape[1]] = mask
29
+ mask = padded_mask
30
+ mask = torch.from_numpy(mask)
31
+ masks.append(mask)
32
+
33
+ stacked_masks = torch.stack(masks)
34
+ result.pred_instances.masks = stacked_masks
35
+
36
+ return result
37
+
38
+ # Crops the images using masks and put the cropped images on a white background
39
+ # @timer_func
40
+ def crop_masks(self, result, img):
41
+ cropped_imgs = list()
42
+ polygons = list()
43
+
44
+ for j, mask in enumerate(result.pred_instances.masks):
45
+ np_array = mask.cpu().numpy()
46
+ contours, _ = cv2.findContours(
47
+ np_array.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE
48
+ ) # fix so only one contour (the largest one) is extracted
49
+ largest_contour = max(contours, key=cv2.contourArea)
50
+
51
+ epsilon = 0.003 * cv2.arcLength(largest_contour, True)
52
+ approx_poly = cv2.approxPolyDP(largest_contour, epsilon, True)
53
+ approx_poly = np.squeeze(approx_poly)
54
+ approx_poly = approx_poly.tolist()
55
+ polygons.append(approx_poly)
56
+
57
+ x, y, w, h = cv2.boundingRect(largest_contour)
58
+
59
+ # Crop masked region and put on white background
60
+ masked_region = img[y : y + h, x : x + w]
61
+ white_background = np.ones_like(masked_region)
62
+ white_background.fill(255)
63
+ masked_region_on_white = cv2.bitwise_and(
64
+ white_background, masked_region, mask=np_array.astype(np.uint8)[y : y + h, x : x + w]
65
+ )
66
+
67
+ cv2.bitwise_not(white_background, white_background, mask=np_array.astype(np.uint8)[y : y + h, x : x + w])
68
+ res = white_background + masked_region_on_white
69
+
70
+ cropped_imgs.append(res)
71
+
72
+ return cropped_imgs, polygons
73
+
74
+ def visualize_result(self, result, img, model_visualizer):
75
+ visualizer = VISUALIZERS.build(model_visualizer)
76
+ visualizer.add_datasample("result", img, data_sample=result, draw_gt=False)
77
+
78
+ return visualizer.get_image()
79
+
80
+ def _translate_line_coords(self, region_mask, line_polygons):
81
+ region_mask = region_mask.cpu().numpy()
82
+ region_masks_binary = (region_mask > 0).astype(np.uint8)
83
+
84
+ box = cv2.boundingRect(region_masks_binary)
85
+ translated_line_polygons = [[[a + box[0], b + box[1]] for [a, b] in poly] for poly in line_polygons]
86
+
87
+ return translated_line_polygons
src/htr_pipeline/utils/process_xml.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from datetime import datetime
4
+
5
+ import jinja2
6
+ from tqdm import tqdm
7
+
8
+ from src.htr_pipeline.inferencer import InferencerInterface
9
+ from src.htr_pipeline.utils.process_segmask import SegMaskHelper
10
+
11
+
12
+ class XMLHelper:
13
+ def __init__(self):
14
+ self.process_seg_mask = SegMaskHelper()
15
+
16
+ def image_to_page_xml(
17
+ self,
18
+ image,
19
+ pred_score_threshold_regions,
20
+ pred_score_threshold_lines,
21
+ containments_threshold,
22
+ inferencer: InferencerInterface,
23
+ xml_file_name="page_xml.xml",
24
+ ):
25
+ img_height = image.shape[0]
26
+ img_width = image.shape[1]
27
+ img_file_name = xml_file_name
28
+
29
+ template_data = self.prepare_template_data(img_file_name, img_width, img_height)
30
+
31
+ template_data["textRegions"] = self._process_regions(
32
+ image,
33
+ inferencer,
34
+ pred_score_threshold_regions,
35
+ pred_score_threshold_lines,
36
+ containments_threshold,
37
+ )
38
+
39
+ rendered_xml = self._render_xml(template_data)
40
+
41
+ return rendered_xml
42
+
43
+ def _transform_coords(self, input_string):
44
+ pattern = r"\[\s*([^\s,]+)\s*,\s*([^\s\]]+)\s*\]"
45
+ replacement = r"\1,\2"
46
+ return re.sub(pattern, replacement, input_string)
47
+
48
+ def _render_xml(self, template_data):
49
+ template_loader = jinja2.FileSystemLoader(searchpath="./src/htr_pipeline/utils/templates")
50
+ template_env = jinja2.Environment(loader=template_loader, trim_blocks=True)
51
+ template = template_env.get_template("page_xml_2013.xml")
52
+ rendered_xml = template.render(template_data)
53
+ rendered_xml = self._transform_coords(rendered_xml)
54
+ return rendered_xml
55
+
56
+ def prepare_template_data(self, img_file_name, img_width, img_height):
57
+ now = datetime.now()
58
+ date_time = now.strftime("%Y-%m-%d, %H:%M:%S")
59
+ return {
60
+ "created": date_time,
61
+ "imageFilename": img_file_name,
62
+ "imageWidth": img_width,
63
+ "imageHeight": img_height,
64
+ "textRegions": list(),
65
+ }
66
+
67
+ def _process_regions(
68
+ self,
69
+ image,
70
+ inferencer: InferencerInterface,
71
+ pred_score_threshold_regions,
72
+ pred_score_threshold_lines,
73
+ containments_threshold,
74
+ htr_threshold=0.7,
75
+ ):
76
+ _, regions_cropped_ordered, reg_polygons_ordered, reg_masks_ordered = inferencer.predict_regions(
77
+ image,
78
+ pred_score_threshold=pred_score_threshold_regions,
79
+ containments_threshold=containments_threshold,
80
+ visualize=False,
81
+ )
82
+
83
+ region_data_list = []
84
+ for i, (text_region, reg_pol, mask) in tqdm(
85
+ enumerate(zip(regions_cropped_ordered, reg_polygons_ordered, reg_masks_ordered))
86
+ ):
87
+ region_id = "region_" + str(i)
88
+ region_data = dict()
89
+ region_data["id"] = region_id
90
+ region_data["boundary"] = reg_pol
91
+
92
+ text_lines, htr_scores = self._process_lines(
93
+ text_region,
94
+ inferencer,
95
+ pred_score_threshold_lines,
96
+ containments_threshold,
97
+ mask,
98
+ region_id,
99
+ )
100
+
101
+ if text_lines is None:
102
+ continue
103
+
104
+ region_data["textLines"] = text_lines
105
+ mean_htr_score = sum(htr_scores) / len(htr_scores)
106
+
107
+ if mean_htr_score > htr_threshold:
108
+ region_data_list.append(region_data)
109
+
110
+ return region_data_list
111
+
112
+ def _process_lines(
113
+ self,
114
+ text_region,
115
+ inferencer: InferencerInterface,
116
+ pred_score_threshold_lines,
117
+ containments_threshold,
118
+ mask,
119
+ region_id,
120
+ htr_threshold=0.7,
121
+ ):
122
+ _, lines_cropped_ordered, line_polygons_ordered = inferencer.predict_lines(
123
+ text_region,
124
+ pred_score_threshold=pred_score_threshold_lines,
125
+ containments_threshold=containments_threshold,
126
+ visualize=False,
127
+ custom_track=False,
128
+ )
129
+
130
+ if lines_cropped_ordered is None:
131
+ return None, None
132
+
133
+ line_polygons_ordered_trans = self.process_seg_mask._translate_line_coords(mask, line_polygons_ordered)
134
+
135
+ htr_scores = list()
136
+ text_lines = list()
137
+
138
+ for j, (line, line_pol) in enumerate(zip(lines_cropped_ordered, line_polygons_ordered_trans)):
139
+ line_id = "line_" + region_id + "_" + str(j)
140
+ line_data = dict()
141
+ line_data["id"] = line_id
142
+ line_data["boundary"] = line_pol
143
+
144
+ line_data["unicode"], htr_score = inferencer.transcribe(line)
145
+ htr_scores.append(htr_score)
146
+
147
+ if htr_score > htr_threshold:
148
+ text_lines.append(line_data)
149
+
150
+ return text_lines, htr_scores
src/htr_pipeline/utils/templates/arial.ttf ADDED
Binary file (367 kB). View file
 
src/htr_pipeline/utils/templates/page_xml_2013.xml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <PcGts xmlns="http://schema.primaresearch.org/PAGE/gts/pagecontent/2013-07-15" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schema.primaresearch.org/PAGE/gts/pagecontent/2013-07-15 http://schema.primaresearch.org/PAGE/gts/pagecontent/2013-07-15/pagecontent.xsd">
3
+ <Metadata>
4
+ <Creator>Swedish National Archives</Creator>
5
+ <Created>{{ created }}</Created>
6
+ </Metadata>
7
+ <Page imageFilename="{{ imageFilename }}" imageWidth="{{ imageWidth }}" imageHeight="{{ imageHeight }}">
8
+ {% for textRegion in textRegions %}
9
+ <TextRegion id="{{ textRegion.id }}" custom="readingOrder {index:{{ loop.index0 }};}">
10
+ <Coords points="{% for point in textRegion.boundary %}{{ point|join(',') }}{% if not loop.last %} {% endif %}{% endfor %}"/>
11
+ {% for textLine in textRegion.textLines %}
12
+ <TextLine id="{{ textLine.id }}" custom="readingOrder {index:{{ loop.index0 }};}">
13
+ {% if textLine.boundary %}
14
+ <Coords points="{% for point in textLine.boundary %}{{ point|join(',') }}{% if not loop.last %} {% endif %}{% endfor %}"/>
15
+ {% endif %}
16
+ {% if textLine.baseline %}
17
+ <Baseline points="{% for point in textLine.baseline %}{{ point|join(',') }}{% if not loop.last %} {% endif %}{% endfor %}"/>
18
+ {% endif %}
19
+ {% if textLine.unicode %}
20
+ <TextEquiv>
21
+ <Unicode>{{ textLine.unicode }}</Unicode>
22
+ </TextEquiv>
23
+ {% endif %}
24
+ </TextLine>
25
+ {% endfor %}
26
+ </TextRegion>
27
+ {% endfor %}
28
+ </Page>
29
+ </PcGts>
30
+
src/tests/.gitkeep ADDED
File without changes
test_api.ipynb ADDED
@@ -0,0 +1,479 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 5,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stdout",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "Loaded as API: http://127.0.0.1:7860/ ✔\n",
13
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
14
+ "<PcGts xmlns=\"http://schema.primaresearch.org/PAGE/gts/pagecontent/2013-07-15\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://schema.primaresearch.org/PAGE/gts/pagecontent/2013-07-15 http://schema.primaresearch.org/PAGE/gts/pagecontent/2013-07-15/pagecontent.xsd\">\n",
15
+ " <Metadata>\n",
16
+ " <Creator>Swedish National Archives</Creator>\n",
17
+ " <Created>2023-06-13, 15:45:54</Created>\n",
18
+ " </Metadata>\n",
19
+ " <Page imageFilename=\"test_api\" imageWidth=\"8992\" imageHeight=\"6144\">\n",
20
+ " <TextRegion id=\"region_0\" custom=\"readingOrder {index:0;}\">\n",
21
+ " <Coords points=\"2154,1057 2170,1112 2205,1147 2300,1181 2401,1195 2643,1162 3188,1151 3311,1159 3367,1128 3392,1077 3373,1041 3316,1017 2597,983 2323,958 2200,966 2175,997\"/>\n",
22
+ " <TextLine id=\"line_0_0\" custom=\"readingOrder {index:0;}\">\n",
23
+ " <Coords points=\"2196,1087 2206,1115 2259,1114 2313,1149 2534,1153 2714,1133 2748,1136 2810,1165 2963,1134 3309,1143 3340,1136 3351,1111 3343,1064 3230,1036 3113,1027 2901,1038 2814,997 2751,1014 2594,1027 2489,986 2430,993 2377,977 2322,980 2302,996 2242,1009 2203,1041\"/>\n",
24
+ " <TextEquiv>\n",
25
+ " <Unicode>Ugglebo socken.</Unicode>\n",
26
+ " </TextEquiv>\n",
27
+ " </TextLine>\n",
28
+ " </TextRegion>\n",
29
+ " <TextRegion id=\"region_1\" custom=\"readingOrder {index:1;}\">\n",
30
+ " <Coords points=\"4161,1345 1523,1306 1351,1372 1364,5271 4223,5273 4243,3085\"/>\n",
31
+ " <TextLine id=\"line_1_0\" custom=\"readingOrder {index:0;}\">\n",
32
+ " <Coords points=\"1376,1376 1388,1499 1497,1526 1707,1480 3303,1477 3502,1519 3598,1486 4122,1462 4167,1432 4151,1366 1503,1345 1403,1347\"/>\n",
33
+ " <TextEquiv>\n",
34
+ " <Unicode>Uggleto socken med Amots Kapelllag, grändas¬</Unicode>\n",
35
+ " </TextEquiv>\n",
36
+ " </TextLine>\n",
37
+ " <TextLine id=\"line_1_1\" custom=\"readingOrder {index:1;}\">\n",
38
+ " <Coords points=\"1365,1590 1382,1628 2618,1621 2812,1654 3009,1624 3660,1619 3949,1659 4179,1618 4199,1526 4147,1489 3903,1505 3805,1484 3505,1527 2808,1501 2397,1526 2143,1478 1402,1538\"/>\n",
39
+ " <TextEquiv>\n",
40
+ " <Unicode>i norr Till Hauebe och Skogs socknar i Helsingland,</Unicode>\n",
41
+ " </TextEquiv>\n",
42
+ " </TextLine>\n",
43
+ " <TextLine id=\"line_1_2\" custom=\"readingOrder {index:2;}\">\n",
44
+ " <Coords points=\"1387,1733 1415,1776 1499,1794 3108,1803 4146,1773 4162,1724 4147,1674 3993,1685 3851,1652 2383,1688 2045,1651 1462,1689\"/>\n",
45
+ " <TextEquiv>\n",
46
+ " <Unicode>i oster till Hamrange socken, i sydost till Hille och</Unicode>\n",
47
+ " </TextEquiv>\n",
48
+ " </TextLine>\n",
49
+ " <TextLine id=\"line_1_3\" custom=\"readingOrder {index:3;}\">\n",
50
+ " <Coords points=\"4146,1826 4066,1814 3113,1843 1494,1821 1380,1835 1362,1930 1398,1956 2247,1936 2447,2019 2597,1955 2904,1948 3152,1997 3394,1941 3995,1938 4136,1916\"/>\n",
51
+ " <TextEquiv>\n",
52
+ " <Unicode>Wahlbo, i söder till Ofvansjö, i sydvest och vester</Unicode>\n",
53
+ " </TextEquiv>\n",
54
+ " </TextLine>\n",
55
+ " <TextLine id=\"line_1_4\" custom=\"readingOrder {index:4;}\">\n",
56
+ " <Coords points=\"1353,2083 1394,2115 1609,2106 1850,2150 2398,2101 4085,2100 4150,2083 4148,1977 4061,1965 3032,2006 2747,1945 2447,2015 2303,1974 1991,1996 1698,1954 1394,1967 1363,1997\"/>\n",
57
+ " <TextEquiv>\n",
58
+ " <Unicode>till Svärdsjö socken i Dalarne, och i nordvest till</Unicode>\n",
59
+ " </TextEquiv>\n",
60
+ " </TextLine>\n",
61
+ " <TextLine id=\"line_1_5\" custom=\"readingOrder {index:5;}\">\n",
62
+ " <Coords points=\"1361,2222 1396,2269 2462,2259 2651,2281 3003,2261 3303,2291 3554,2257 4058,2255 4153,2235 4159,2181 3910,2120 3668,2145 2638,2147 2445,2119 2250,2150 1700,2156 1497,2118 1376,2147\"/>\n",
63
+ " <TextEquiv>\n",
64
+ " <Unicode>Bollnäs socken i Helsingland, intager en större å</Unicode>\n",
65
+ " </TextEquiv>\n",
66
+ " </TextLine>\n",
67
+ " <TextLine id=\"line_1_6\" custom=\"readingOrder {index:6;}\">\n",
68
+ " <Coords points=\"1366,2374 1396,2421 1775,2423 1947,2474 2067,2428 2183,2432 2205,2478 2264,2444 2469,2422 3013,2425 3146,2491 3503,2420 4147,2416 4183,2377 4149,2296 3239,2314 2853,2275 2649,2291 2506,2261 2179,2314 1604,2287 1400,2304\"/>\n",
69
+ " <TextEquiv>\n",
70
+ " <Unicode>real än någon af Gestriklands öfrige socknar el¬</Unicode>\n",
71
+ " </TextEquiv>\n",
72
+ " </TextLine>\n",
73
+ " <TextLine id=\"line_1_7\" custom=\"readingOrder {index:7;}\">\n",
74
+ " <Coords points=\"4149,2517 4142,2476 4003,2429 3799,2465 3243,2464 3056,2488 2703,2483 2295,2431 1955,2477 1408,2442 1370,2466 1365,2534 1399,2582 1810,2575 2003,2645 2245,2573 2466,2591 3453,2572 3653,2634 3816,2577 4115,2568\"/>\n",
75
+ " <TextEquiv>\n",
76
+ " <Unicode>ler nära 1/4 af hela provinsen och år ett af Större</Unicode>\n",
77
+ " </TextEquiv>\n",
78
+ " </TextLine>\n",
79
+ " <TextLine id=\"line_1_8\" custom=\"readingOrder {index:8;}\">\n",
80
+ " <Coords points=\"4154,2677 4117,2647 3846,2600 3647,2640 2691,2601 2008,2639 1403,2614 1370,2683 1401,2726 1776,2725 1952,2784 2204,2722 2903,2721 3104,2797 3349,2782 3592,2731 3999,2783 4145,2726\"/>\n",
81
+ " <TextEquiv>\n",
82
+ " <Unicode>vattendrag genomskuret, med berg, sjöar och myr¬</Unicode>\n",
83
+ " </TextEquiv>\n",
84
+ " </TextLine>\n",
85
+ " <TextLine id=\"line_1_9\" custom=\"readingOrder {index:9;}\">\n",
86
+ " <Coords points=\"1355,2871 1394,2898 2057,2893 2356,2941 2713,2892 4103,2896 4196,2882 4200,2832 4172,2797 3092,2789 2890,2751 2247,2787 2104,2754 1952,2767 1844,2729 1549,2766 1402,2755 1365,2792\"/>\n",
87
+ " <TextEquiv>\n",
88
+ " <Unicode>landta träkter uppfylldt land, som genom sin nå¬</Unicode>\n",
89
+ " </TextEquiv>\n",
90
+ " </TextLine>\n",
91
+ " <TextLine id=\"line_1_10\" custom=\"readingOrder {index:10;}\">\n",
92
+ " <Coords points=\"1326,2955 1326,3040 1395,3066 2064,3049 2684,3089 2847,3074 2991,3102 3621,3053 3899,3104 4152,3060 4190,3015 4160,2948 2534,2954 2153,2910 1771,2929 1445,2900\"/>\n",
93
+ " <TextEquiv>\n",
94
+ " <Unicode>turskonhet utmärker sig framför provinsens öfrige</Unicode>\n",
95
+ " </TextEquiv>\n",
96
+ " </TextLine>\n",
97
+ " <TextLine id=\"line_1_11\" custom=\"readingOrder {index:11;}\">\n",
98
+ " <Coords points=\"1357,3150 1364,3203 1402,3221 2659,3208 3051,3281 3254,3219 4148,3213 4196,3192 4204,3132 4148,3101 3814,3088 2952,3107 2797,3076 2635,3095 2413,3071 2200,3108 2038,3064 1855,3099 1400,3097\"/>\n",
99
+ " <TextEquiv>\n",
100
+ " <Unicode>socknar. Dess största längd från hon till söder</Unicode>\n",
101
+ " </TextEquiv>\n",
102
+ " </TextLine>\n",
103
+ " <TextLine id=\"line_1_12\" custom=\"readingOrder {index:12;}\">\n",
104
+ " <Coords points=\"3801,3315 3796,3275 3705,3237 3573,3255 3348,3240 2991,3277 2806,3228 2405,3264 1643,3232 1397,3263 1363,3328 1397,3370 1656,3389 1909,3369 2113,3433 2307,3368 3405,3381 3714,3364 3796,3340\"/>\n",
105
+ " <TextEquiv>\n",
106
+ " <Unicode>år 25/6 mil och från vester till Öster 3 2/3 mil.</Unicode>\n",
107
+ " </TextEquiv>\n",
108
+ " </TextLine>\n",
109
+ " <TextLine id=\"line_1_13\" custom=\"readingOrder {index:13;}\">\n",
110
+ " <Coords points=\"4176,3424 4152,3376 3675,3420 3252,3392 2959,3439 2699,3415 2498,3440 1950,3410 1740,3430 1543,3399 1398,3413 1365,3488 1402,3542 3254,3521 3452,3591 3663,3525 4140,3517\"/>\n",
111
+ " <TextEquiv>\n",
112
+ " <Unicode>Man räknar, inom denna socken öfver 300 stor</Unicode>\n",
113
+ " </TextEquiv>\n",
114
+ " </TextLine>\n",
115
+ " <TextLine id=\"line_1_14\" custom=\"readingOrder {index:14;}\">\n",
116
+ " <Coords points=\"4208,3677 4181,3593 3999,3539 3809,3567 3543,3549 3347,3589 3003,3540 2661,3587 1937,3583 1728,3555 1397,3581 1361,3624 1369,3677 1401,3694 2654,3683 2895,3723 3096,3687 4109,3698\"/>\n",
117
+ " <TextEquiv>\n",
118
+ " <Unicode>re och mindre sjöar och tjernar, hvilka tillsam¬</Unicode>\n",
119
+ " </TextEquiv>\n",
120
+ " </TextLine>\n",
121
+ " <TextLine id=\"line_1_15\" custom=\"readingOrder {index:15;}\">\n",
122
+ " <Coords points=\"1356,3828 1401,3853 2235,3839 2433,3880 2659,3841 2876,3895 3139,3843 3503,3842 3700,3880 4132,3839 4159,3774 4126,3741 3752,3714 3420,3740 1987,3719 1397,3737 1361,3764\"/>\n",
123
+ " <TextEquiv>\n",
124
+ " <Unicode>utans med vattendragen intaga den ansenliga area</Unicode>\n",
125
+ " </TextEquiv>\n",
126
+ " </TextLine>\n",
127
+ " <TextLine id=\"line_1_16\" custom=\"readingOrder {index:16;}\">\n",
128
+ " <Coords points=\"2972,3931 2945,3913 2850,3906 2657,3858 2547,3892 2106,3914 1850,3890 1557,3890 1446,3864 1376,3889 1364,3945 1386,4009 1558,4071 1602,4072 1797,4008 1933,3997 2137,4029 2253,3999 2449,3998 2650,4065 2753,4021 2953,3985\"/>\n",
129
+ " <TextEquiv>\n",
130
+ " <Unicode>len af 69093, i qvädratrefvar</Unicode>\n",
131
+ " </TextEquiv>\n",
132
+ " </TextLine>\n",
133
+ " <TextLine id=\"line_1_17\" custom=\"readingOrder {index:17;}\">\n",
134
+ " <Coords points=\"3383,3930 3382,3976 3401,3995 3899,3990 4008,4024 4177,4000 4210,3978 4198,3909 4182,3896 3658,3888 3600,3834 3478,3856 3403,3892\"/>\n",
135
+ " <TextEquiv>\n",
136
+ " <Unicode>På norra grån</Unicode>\n",
137
+ " </TextEquiv>\n",
138
+ " </TextLine>\n",
139
+ " <TextLine id=\"line_1_18\" custom=\"readingOrder {index:18;}\">\n",
140
+ " <Coords points=\"4138,4116 4104,4047 3904,4009 3620,4050 3449,4018 3148,4050 2947,4014 2719,4064 1951,4011 1612,4061 1397,4056 1362,4132 1397,4177 2790,4166 2878,4181 2954,4234 3083,4173 3238,4152 3750,4165 3903,4222 4107,4162\"/>\n",
141
+ " <TextEquiv>\n",
142
+ " <Unicode>sen och till hälften inom Skogs socken är Lingbo</Unicode>\n",
143
+ " </TextEquiv>\n",
144
+ " </TextLine>\n",
145
+ " <TextLine id=\"line_1_19\" custom=\"readingOrder {index:19;}\">\n",
146
+ " <Coords points=\"4115,4231 4082,4210 3846,4224 3401,4198 3254,4164 2990,4226 2255,4186 2091,4212 1704,4180 1396,4209 1363,4327 1396,4365 1561,4318 3347,4316 3544,4347 4088,4319\"/>\n",
147
+ " <TextEquiv>\n",
148
+ " <Unicode>sjön belägen, denna sammanbindes genom ett</Unicode>\n",
149
+ " </TextEquiv>\n",
150
+ " </TextLine>\n",
151
+ " <TextLine id=\"line_1_20\" custom=\"readingOrder {index:20;}\">\n",
152
+ " <Coords points=\"4193,4413 4149,4369 3500,4356 3355,4315 3150,4350 1800,4339 1400,4382 1372,4404 1366,4477 1404,4498 1646,4469 3143,4470 3449,4510 3678,4469 4152,4468\"/>\n",
153
+ " <TextEquiv>\n",
154
+ " <Unicode>mält sund med den vidsträckta, sydost derom</Unicode>\n",
155
+ " </TextEquiv>\n",
156
+ " </TextLine>\n",
157
+ " <TextLine id=\"line_1_21\" custom=\"readingOrder {index:21;}\">\n",
158
+ " <Coords points=\"1326,4630 1707,4642 1901,4684 2138,4629 3109,4631 3266,4684 3395,4634 4150,4638 4186,4621 4196,4570 4177,4532 4046,4491 3848,4534 3547,4536 2844,4482 2542,4532 2292,4467 2108,4530 1402,4505 1364,4531 1353,4614\"/>\n",
159
+ " <TextEquiv>\n",
160
+ " <Unicode>belägna sjön Ekaren. Vid Östra gränsen utbreda</Unicode>\n",
161
+ " </TextEquiv>\n",
162
+ " </TextLine>\n",
163
+ " <TextLine id=\"line_1_22\" custom=\"readingOrder {index:22;}\">\n",
164
+ " <Coords points=\"1364,4784 1399,4829 2365,4780 2749,4837 3162,4774 3396,4839 3592,4791 4118,4791 4150,4776 4163,4674 4103,4650 3976,4683 3659,4693 3386,4630 3329,4677 3247,4694 2740,4689 2502,4625 2350,4677 2043,4650 1840,4690 1552,4645 1392,4689\"/>\n",
165
+ " <TextEquiv>\n",
166
+ " <Unicode>sig Lilla och Stora Daninsjöarne, Tolfvorn samt</Unicode>\n",
167
+ " </TextEquiv>\n",
168
+ " </TextLine>\n",
169
+ " <TextLine id=\"line_1_23\" custom=\"readingOrder {index:23;}\">\n",
170
+ " <Coords points=\"4197,4828 3987,4848 3806,4812 3639,4849 3341,4848 3104,4781 2853,4851 2364,4838 2228,4795 2041,4840 1757,4812 1392,4847 1372,4927 1400,4957 3153,4946 3445,5014 3601,4963 3776,5012 3951,4964 4172,4948\"/>\n",
171
+ " <TextEquiv>\n",
172
+ " <Unicode>den till en liten del inom Hansränge liggande</Unicode>\n",
173
+ " </TextEquiv>\n",
174
+ " </TextLine>\n",
175
+ " <TextLine id=\"line_1_24\" custom=\"readingOrder {index:24;}\">\n",
176
+ " <Coords points=\"1361,5070 1397,5112 1645,5135 2054,5098 2944,5112 3104,5146 3309,5113 4049,5114 4145,5087 4150,5029 4054,4977 3857,5005 3696,4980 3387,5006 3104,4978 2765,5004 2400,4993 2202,4953 2050,4995 1895,4999 1483,4962 1380,4985\"/>\n",
177
+ " <TextEquiv>\n",
178
+ " <Unicode>Vittersjön. På gränsen emot Ofvansjö bemärkas</Unicode>\n",
179
+ " </TextEquiv>\n",
180
+ " </TextLine>\n",
181
+ " <TextLine id=\"line_1_25\" custom=\"readingOrder {index:25;}\">\n",
182
+ " <Coords points=\"4198,5132 3632,5162 3357,5126 3056,5145 2896,5112 2680,5166 2305,5115 1806,5151 1545,5121 1390,5158 1367,5233 1397,5266 1653,5281 2036,5261 4152,5271 4195,5254\"/>\n",
183
+ " <TextEquiv>\n",
184
+ " <Unicode>Hällsjön och Långsjön, hvilka till någon del lig¬</Unicode>\n",
185
+ " </TextEquiv>\n",
186
+ " </TextLine>\n",
187
+ " </TextRegion>\n",
188
+ " <TextRegion id=\"region_2\" custom=\"readingOrder {index:2;}\">\n",
189
+ " <Coords points=\"7922,724 7937,736 8051,707 8101,688 8148,659 8162,625 8159,613 8148,602 8106,597 7934,593 7923,597 7909,611 7905,624\"/>\n",
190
+ " <TextLine id=\"line_2_0\" custom=\"readingOrder {index:0;}\">\n",
191
+ " <Coords points=\"7905,613 7905,710 7922,717 7929,716 7932,723 7942,727 7946,733 7982,727 8022,714 8048,716 8100,701 8108,677 8113,671 8119,639 8127,620 8117,621 8103,615 8033,610 8006,602 7974,606 7955,602 7942,609 7940,616 7923,619\"/>\n",
192
+ " <TextEquiv>\n",
193
+ " <Unicode>297.</Unicode>\n",
194
+ " </TextEquiv>\n",
195
+ " </TextLine>\n",
196
+ " </TextRegion>\n",
197
+ " <TextRegion id=\"region_3\" custom=\"readingOrder {index:3;}\">\n",
198
+ " <Coords points=\"7926,824 5441,851 5062,908 5054,4678 5110,5261 7990,5223 7998,2514\"/>\n",
199
+ " <TextLine id=\"line_3_0\" custom=\"readingOrder {index:0;}\">\n",
200
+ " <Coords points=\"7934,902 7909,860 5383,870 5110,884 5082,908 5079,974 5116,1025 5449,983 7898,966\"/>\n",
201
+ " <TextEquiv>\n",
202
+ " <Unicode>ga inom sistnämnde socken, samt i vestra delen</Unicode>\n",
203
+ " </TextEquiv>\n",
204
+ " </TextLine>\n",
205
+ " <TextLine id=\"line_3_1\" custom=\"readingOrder {index:1;}\">\n",
206
+ " <Coords points=\"7871,1070 7842,1027 7279,1016 7131,993 6899,1026 6585,1000 6302,1032 5336,1013 5104,1046 5077,1130 5111,1161 5288,1190 5726,1138 6996,1121 7230,1133 7413,1180 7844,1132\"/>\n",
207
+ " <TextEquiv>\n",
208
+ " <Unicode>af Ugglevo eller den så kallade Finnbyggden,</Unicode>\n",
209
+ " </TextEquiv>\n",
210
+ " </TextLine>\n",
211
+ " <TextLine id=\"line_3_2\" custom=\"readingOrder {index:2;}\">\n",
212
+ " <Coords points=\"5067,1240 5078,1330 5122,1345 5395,1295 7111,1286 7351,1322 7553,1287 7898,1284 7929,1232 7899,1180 7090,1157 6692,1189 6400,1131 6109,1188 5900,1176 5727,1132 5515,1194 5124,1178\"/>\n",
213
+ " <TextEquiv>\n",
214
+ " <Unicode>Sjöarne Quidsjön, Holmsjön och Macksjon såsom</Unicode>\n",
215
+ " </TextEquiv>\n",
216
+ " </TextLine>\n",
217
+ " <TextLine id=\"line_3_3\" custom=\"readingOrder {index:3;}\">\n",
218
+ " <Coords points=\"5072,1459 7920,1440 7942,1401 7897,1346 7683,1319 6062,1340 5839,1299 5679,1337 5387,1312 5107,1350 5078,1376\"/>\n",
219
+ " <TextEquiv>\n",
220
+ " <Unicode>de största. Från nordvestra hörnet, der rå skillna¬</Unicode>\n",
221
+ " </TextEquiv>\n",
222
+ " </TextLine>\n",
223
+ " <TextLine id=\"line_3_4\" custom=\"readingOrder {index:4;}\">\n",
224
+ " <Coords points=\"7885,1559 7852,1499 7620,1467 7163,1487 6896,1460 6342,1506 5123,1514 5079,1578 5114,1632 6255,1614 6458,1638 6686,1613 7866,1599\"/>\n",
225
+ " <TextEquiv>\n",
226
+ " <Unicode>derna mellan denna samt Hanebo och Ballnäs</Unicode>\n",
227
+ " </TextEquiv>\n",
228
+ " </TextLine>\n",
229
+ " <TextLine id=\"line_3_5\" custom=\"readingOrder {index:5;}\">\n",
230
+ " <Coords points=\"7919,1677 7898,1622 7577,1655 7223,1654 7015,1618 6735,1653 6338,1636 6037,1667 5337,1647 5113,1662 5076,1747 5112,1783 6179,1779 6399,1810 6676,1763 7888,1761\"/>\n",
231
+ " <TextEquiv>\n",
232
+ " <Unicode>socknar sammanträffa med hvarandra och till</Unicode>\n",
233
+ " </TextEquiv>\n",
234
+ " </TextLine>\n",
235
+ " <TextLine id=\"line_3_6\" custom=\"readingOrder {index:6;}\">\n",
236
+ " <Coords points=\"7915,1800 7898,1780 7564,1795 7399,1775 7097,1808 6173,1811 6012,1782 5096,1817 5078,1905 5111,1943 5229,1948 5341,1990 5574,1939 6276,1918 7854,1936 7904,1911\"/>\n",
237
+ " <TextEquiv>\n",
238
+ " <Unicode>det sydvestra hörnet, der Wahlbo och Hille tillstöta</Unicode>\n",
239
+ " </TextEquiv>\n",
240
+ " </TextLine>\n",
241
+ " <TextLine id=\"line_3_7\" custom=\"readingOrder {index:7;}\">\n",
242
+ " <Coords points=\"7928,2016 7880,1975 7630,1951 5819,1981 5558,1958 5389,1988 5117,1984 5081,2074 5115,2120 5383,2092 6047,2092 6295,2147 6627,2069 6789,2135 7052,2134 7338,2076 7898,2097\"/>\n",
243
+ " <TextEquiv>\n",
244
+ " <Unicode>genomskäres socknen af dess betydligaste vattendrag,</Unicode>\n",
245
+ " </TextEquiv>\n",
246
+ " </TextLine>\n",
247
+ " <TextLine id=\"line_3_8\" custom=\"readingOrder {index:8;}\">\n",
248
+ " <Coords points=\"7863,2134 7837,2114 7635,2142 6862,2147 6623,2079 6349,2146 5175,2117 5095,2137 5077,2191 5120,2260 6183,2241 6395,2270 6659,2238 7076,2266 7817,2242 7849,2221\"/>\n",
249
+ " <TextEquiv>\n",
250
+ " <Unicode>hvilket under namn af Fansan upprinner uti</Unicode>\n",
251
+ " </TextEquiv>\n",
252
+ " </TextLine>\n",
253
+ " <TextLine id=\"line_3_9\" custom=\"readingOrder {index:9;}\">\n",
254
+ " <Coords points=\"5085,2414 5115,2458 5282,2413 5895,2396 6124,2416 6288,2390 7346,2397 7510,2431 7853,2390 7894,2347 7856,2269 7683,2247 7525,2281 7286,2247 7032,2306 6791,2294 6618,2253 5440,2307 5225,2243 5103,2293\"/>\n",
255
+ " <TextEquiv>\n",
256
+ " <Unicode>Grannäs sjön i Alfta socken inom Helsingland,</Unicode>\n",
257
+ " </TextEquiv>\n",
258
+ " </TextLine>\n",
259
+ " <TextLine id=\"line_3_10\" custom=\"readingOrder {index:10;}\">\n",
260
+ " <Coords points=\"5082,2576 5125,2602 5241,2567 5470,2571 5626,2635 5794,2564 6313,2544 7910,2549 7933,2493 7899,2437 7398,2402 7120,2443 6837,2425 6563,2451 6282,2394 6064,2444 5561,2421 5287,2467 5119,2465 5090,2487\"/>\n",
261
+ " <TextEquiv>\n",
262
+ " <Unicode>gar vidare genom Bollnäs socken, samt sedan det</Unicode>\n",
263
+ " </TextEquiv>\n",
264
+ " </TextLine>\n",
265
+ " <TextLine id=\"line_3_11\" custom=\"readingOrder {index:11;}\">\n",
266
+ " <Coords points=\"5092,2691 5117,2728 6344,2705 6559,2773 6847,2715 7119,2709 7341,2749 7515,2710 7683,2763 7932,2716 7962,2690 7965,2629 7918,2584 7735,2571 7503,2607 7245,2556 7007,2602 6692,2606 6448,2546 6246,2597 5788,2575 5615,2622 5401,2583 5116,2617\"/>\n",
267
+ " <TextEquiv>\n",
268
+ " <Unicode>derstädes utbredt sig i Fanssjöarne intränger i Uggle¬</Unicode>\n",
269
+ " </TextEquiv>\n",
270
+ " </TextLine>\n",
271
+ " <TextLine id=\"line_3_12\" custom=\"readingOrder {index:12;}\">\n",
272
+ " <Coords points=\"5088,2854 5284,2949 5565,2865 5688,2877 5837,2935 6063,2873 6291,2929 6398,2882 6544,2865 6779,2873 7007,2930 7119,2895 7900,2869 7937,2851 7944,2798 7899,2764 7512,2712 7170,2762 6512,2768 6212,2710 6026,2753 5853,2718 5631,2755 5222,2734 5106,2762\"/>\n",
273
+ " <TextEquiv>\n",
274
+ " <Unicode>bo, efter att hafva vid gränsen deraf, upptagit den</Unicode>\n",
275
+ " </TextEquiv>\n",
276
+ " </TextLine>\n",
277
+ " <TextLine id=\"line_3_13\" custom=\"readingOrder {index:13;}\">\n",
278
+ " <Coords points=\"7948,2959 7937,2913 7674,2920 7517,2883 7344,2923 6897,2925 6712,2894 6522,2929 6178,2906 5961,2937 5543,2886 5341,2936 5118,2915 5085,2987 5116,3066 5350,3035 5736,3083 6057,3027 7039,3026 7289,3043 7454,3091 7909,3019\"/>\n",
279
+ " <TextEquiv>\n",
280
+ " <Unicode>från Nybosjon i Bollnäs, kommande Rymje el¬</Unicode>\n",
281
+ " </TextEquiv>\n",
282
+ " </TextLine>\n",
283
+ " <TextLine id=\"line_3_14\" custom=\"readingOrder {index:14;}\">\n",
284
+ " <Coords points=\"5084,3130 5088,3191 5120,3212 5820,3183 7463,3178 7676,3258 7953,3192 7954,3071 7784,3057 7469,3087 6514,3056 6263,3083 6072,3045 5850,3091 5463,3058 5111,3084\"/>\n",
285
+ " <TextEquiv>\n",
286
+ " <Unicode>ler Svartån, Så snart det inkommit i Uggle¬</Unicode>\n",
287
+ " </TextEquiv>\n",
288
+ " </TextLine>\n",
289
+ " <TextLine id=\"line_3_15\" custom=\"readingOrder {index:15;}\">\n",
290
+ " <Coords points=\"5086,3308 5120,3364 6291,3341 6510,3383 6618,3350 6980,3341 7236,3372 7416,3345 7901,3341 7952,3242 7515,3242 7291,3191 7091,3247 6671,3250 6347,3206 6115,3233 5833,3191 5691,3222 5122,3223\"/>\n",
291
+ " <TextEquiv>\n",
292
+ " <Unicode>bo socken bildar det några smärre tjernar, samt</Unicode>\n",
293
+ " </TextEquiv>\n",
294
+ " </TextLine>\n",
295
+ " <TextLine id=\"line_3_16\" custom=\"readingOrder {index:16;}\">\n",
296
+ " <Coords points=\"7931,3412 7899,3366 7244,3399 6894,3360 6509,3388 6182,3370 5905,3404 5222,3373 5107,3391 5081,3508 5122,3563 5180,3527 5336,3511 5538,3524 5623,3563 5786,3511 6129,3506 6509,3565 6623,3517 7010,3512 7129,3555 7341,3501 7459,3507 7574,3555 7898,3493\"/>\n",
297
+ " <TextEquiv>\n",
298
+ " <Unicode>flyter derefter med obetydliga krökningar i sydost¬</Unicode>\n",
299
+ " </TextEquiv>\n",
300
+ " </TextLine>\n",
301
+ " <TextLine id=\"line_3_17\" custom=\"readingOrder {index:17;}\">\n",
302
+ " <Coords points=\"7945,3622 7902,3546 7587,3539 7460,3502 7232,3546 6467,3567 6175,3524 5796,3578 5522,3539 5286,3554 5223,3522 5115,3562 5082,3649 5121,3733 5346,3676 5623,3698 6064,3679 6183,3711 6337,3671 6572,3689 6675,3729 6789,3675 6952,3652 7349,3687 7576,3672 7740,3704 7915,3665\"/>\n",
303
+ " <TextEquiv>\n",
304
+ " <Unicode>lig riktning, upptager ofvanför Amots by, ett, från</Unicode>\n",
305
+ " </TextEquiv>\n",
306
+ " </TextLine>\n",
307
+ " <TextLine id=\"line_3_18\" custom=\"readingOrder {index:18;}\">\n",
308
+ " <Coords points=\"7939,3742 7905,3703 7797,3689 7174,3708 6897,3673 6736,3711 6343,3683 6173,3714 5778,3722 5505,3686 5123,3733 5090,3806 5116,3843 5350,3841 5505,3882 5679,3842 6512,3816 6974,3863 7899,3814 7930,3797\"/>\n",
309
+ " <TextEquiv>\n",
310
+ " <Unicode>de vid gränsen emot Bollnäs belägne tjernar kom¬</Unicode>\n",
311
+ " </TextEquiv>\n",
312
+ " </TextLine>\n",
313
+ " <TextLine id=\"line_3_19\" custom=\"readingOrder {index:19;}\">\n",
314
+ " <Coords points=\"7881,3892 7851,3843 7637,3870 5119,3890 5080,3971 5115,4008 6343,3977 6574,4039 6810,3984 7843,3970\"/>\n",
315
+ " <TextEquiv>\n",
316
+ " <Unicode>mande mindre vattendrag, samt vidare vid</Unicode>\n",
317
+ " </TextEquiv>\n",
318
+ " </TextLine>\n",
319
+ " <TextLine id=\"line_3_20\" custom=\"readingOrder {index:20;}\">\n",
320
+ " <Coords points=\"5109,4130 5179,4151 6633,4137 6850,4201 7053,4136 7886,4122 7929,4073 7900,4018 6933,4016 6446,4042 6119,4006 5509,4002 5142,4036 5114,4058\"/>\n",
321
+ " <TextEquiv>\n",
322
+ " <Unicode>Amots bruk Kölsjö än, som lopande söder om</Unicode>\n",
323
+ " </TextEquiv>\n",
324
+ " </TextLine>\n",
325
+ " <TextLine id=\"line_3_21\" custom=\"readingOrder {index:21;}\">\n",
326
+ " <Coords points=\"7904,4191 7475,4164 7226,4186 7008,4156 6853,4194 6506,4169 6276,4198 5835,4163 5450,4201 5281,4163 5117,4188 5093,4298 5124,4320 7788,4289 7889,4271\"/>\n",
327
+ " <TextEquiv>\n",
328
+ " <Unicode>Fans- och Svartåarne har sin kalla i den inom</Unicode>\n",
329
+ " </TextEquiv>\n",
330
+ " </TextLine>\n",
331
+ " <TextLine id=\"line_3_22\" custom=\"readingOrder {index:22;}\">\n",
332
+ " <Coords points=\"7921,4358 7887,4330 7671,4336 7511,4301 7126,4348 6792,4315 6517,4359 6240,4309 5962,4348 5234,4326 5115,4350 5097,4415 5120,4467 5989,4453 6239,4484 6571,4450 6846,4475 7843,4451 7911,4424\"/>\n",
333
+ " <TextEquiv>\n",
334
+ " <Unicode>Ballnäs socken belägna Kölsjön. Förenade</Unicode>\n",
335
+ " </TextEquiv>\n",
336
+ " </TextLine>\n",
337
+ " <TextLine id=\"line_3_23\" custom=\"readingOrder {index:23;}\">\n",
338
+ " <Coords points=\"5136,4640 5174,4675 5237,4641 5397,4624 7060,4608 7406,4657 7619,4608 7905,4598 7939,4519 7906,4481 6605,4506 5284,4469 5152,4520\"/>\n",
339
+ " <TextEquiv>\n",
340
+ " <Unicode>fortsätta nu dessa vatten under hamn af Amots</Unicode>\n",
341
+ " </TextEquiv>\n",
342
+ " </TextLine>\n",
343
+ " <TextLine id=\"line_3_24\" custom=\"readingOrder {index:24;}\">\n",
344
+ " <Coords points=\"7959,4659 7922,4613 7736,4603 7516,4657 7006,4612 6749,4668 6479,4609 6226,4666 5500,4648 5126,4703 5106,4750 5160,4787 5889,4802 6291,4769 6505,4820 6734,4768 7053,4761 7294,4827 7522,4763 7844,4799 7955,4751\"/>\n",
345
+ " <TextEquiv>\n",
346
+ " <Unicode>än sitt lopp genom Påls jon och Wallsjön till By¬</Unicode>\n",
347
+ " </TextEquiv>\n",
348
+ " </TextLine>\n",
349
+ " <TextLine id=\"line_3_25\" custom=\"readingOrder {index:25;}\">\n",
350
+ " <Coords points=\"7972,4853 7952,4823 7633,4779 7245,4819 6847,4787 6564,4831 5667,4805 5505,4840 5178,4845 5147,4922 5170,4970 5504,4920 6632,4960 7111,4928 7961,4912\"/>\n",
351
+ " <TextEquiv>\n",
352
+ " <Unicode>sjön. Sistnämnde sjö upphemtar dessutom norr</Unicode>\n",
353
+ " </TextEquiv>\n",
354
+ " </TextLine>\n",
355
+ " <TextLine id=\"line_3_26\" custom=\"readingOrder {index:26;}\">\n",
356
+ " <Coords points=\"5125,5078 5170,5121 5558,5094 6176,5086 6416,5114 6720,5091 6900,5143 7019,5130 7125,5154 7340,5070 7886,5070 7947,5020 7903,4966 5995,4995 5776,4955 5570,4987 5174,4992 5131,5019\"/>\n",
357
+ " <TextEquiv>\n",
358
+ " <Unicode>ifrån Moan, som upprinner på gränsen emel¬</Unicode>\n",
359
+ " </TextEquiv>\n",
360
+ " </TextLine>\n",
361
+ " <TextLine id=\"line_3_27\" custom=\"readingOrder {index:27;}\">\n",
362
+ " <Coords points=\"7953,5184 7924,5144 7735,5095 7508,5116 7342,5083 7185,5141 7006,5148 6685,5115 6398,5144 6182,5110 5728,5145 5548,5113 5396,5149 5151,5147 5110,5200 5118,5274 5185,5274 5187,5249 7870,5239 7934,5227\"/>\n",
363
+ " <TextEquiv>\n",
364
+ " <Unicode>lan Skogs och Hauebo socknar, samt emottager</Unicode>\n",
365
+ " </TextEquiv>\n",
366
+ " </TextLine>\n",
367
+ " </TextRegion>\n",
368
+ " </Page>\n",
369
+ "</PcGts>\n",
370
+ "\n"
371
+ ]
372
+ }
373
+ ],
374
+ "source": [
375
+ "from gradio_client import Client\n",
376
+ "\n",
377
+ "client = Client(\"http://127.0.0.1:7860/\")\n",
378
+ "job = client.submit(\n",
379
+ " \"./helper/examples/images/1861_R0000277_00153.jpg\", # str (filepath or URL to image) in 'Image to run HTR-pipeline on' Image component\n",
380
+ " \"test_api\", # str in 'parameter_22' Textbox component\n",
381
+ " api_name=\"/predict\",\n",
382
+ ")\n",
383
+ "\n",
384
+ "print(job.result())\n"
385
+ ]
386
+ },
387
+ {
388
+ "cell_type": "code",
389
+ "execution_count": 9,
390
+ "metadata": {},
391
+ "outputs": [
392
+ {
393
+ "name": "stdout",
394
+ "output_type": "stream",
395
+ "text": [
396
+ "Running on local URL: http://127.0.0.1:7861\n",
397
+ "\n",
398
+ "To create a public link, set `share=True` in `launch()`.\n"
399
+ ]
400
+ },
401
+ {
402
+ "data": {
403
+ "text/html": [
404
+ "<div><iframe src=\"http://127.0.0.1:7861/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
405
+ ],
406
+ "text/plain": [
407
+ "<IPython.core.display.HTML object>"
408
+ ]
409
+ },
410
+ "metadata": {},
411
+ "output_type": "display_data"
412
+ },
413
+ {
414
+ "data": {
415
+ "text/plain": []
416
+ },
417
+ "execution_count": 9,
418
+ "metadata": {},
419
+ "output_type": "execute_result"
420
+ }
421
+ ],
422
+ "source": [
423
+ "import gradio as gr\n",
424
+ "\n",
425
+ "def create_object(arg):\n",
426
+ " return gr.Textbox(value=arg)\n",
427
+ "\n",
428
+ "my_objects = {}\n",
429
+ "\n",
430
+ "test_list =[\"first\", \"second\"] \n",
431
+ "for i in test_list:\n",
432
+ " object_name = f\"object_{i}\"\n",
433
+ " new_object = create_object(i)\n",
434
+ " my_objects[object_name] = new_object\n",
435
+ "\n",
436
+ "# Accessing objects by their assigned names\n",
437
+ "first_object = my_objects[\"object_first\"]\n",
438
+ "second_object = my_objects[\"object_second\"]\n",
439
+ "\n",
440
+ "with gr.Blocks() as test:\n",
441
+ " with gr.Row():\n",
442
+ " first_object.render()\n",
443
+ " with gr.Row():\n",
444
+ " second_object.render()\n",
445
+ "\n",
446
+ "test.launch()\n"
447
+ ]
448
+ },
449
+ {
450
+ "cell_type": "code",
451
+ "execution_count": null,
452
+ "metadata": {},
453
+ "outputs": [],
454
+ "source": []
455
+ }
456
+ ],
457
+ "metadata": {
458
+ "kernelspec": {
459
+ "display_name": "venv",
460
+ "language": "python",
461
+ "name": "python3"
462
+ },
463
+ "language_info": {
464
+ "codemirror_mode": {
465
+ "name": "ipython",
466
+ "version": 3
467
+ },
468
+ "file_extension": ".py",
469
+ "mimetype": "text/x-python",
470
+ "name": "python",
471
+ "nbconvert_exporter": "python",
472
+ "pygments_lexer": "ipython3",
473
+ "version": "3.10.9"
474
+ },
475
+ "orig_nbformat": 4
476
+ },
477
+ "nbformat": 4,
478
+ "nbformat_minor": 2
479
+ }