Correct license to match Dawsonia

#1
.gitignore CHANGED
@@ -11,5 +11,3 @@ wheels/
11
  dawsonia.log
12
  output/*/
13
  .gradio_cache
14
- data/
15
- dawsonia.log.*
 
11
  dawsonia.log
12
  output/*/
13
  .gradio_cache
 
 
Dockerfile CHANGED
@@ -26,7 +26,6 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
26
  curl \
27
  git \
28
  ffmpeg \
29
- poppler-utils \
30
  libsm6 \
31
  libxext6 \
32
  libgl1 \
@@ -55,9 +54,9 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh
55
  SHELL ["/bin/bash", "-c"]
56
 
57
  # Copy dependency files and install dependencies
58
- COPY --chown=appuser pyproject.toml dawsonia.toml uv.lock LICENSE.txt LICENSE-RA.txt README.md ./
59
 
60
- RUN /home/appuser/.local/bin/uv sync -p 3.10 --frozen --no-cache --no-dev \
61
  && chown -R appuser:appuser /home/appuser/app/.venv \
62
  && rm -rf /home/appuser/.cache
63
 
@@ -70,4 +69,4 @@ COPY --chown=appuser data data
70
 
71
 
72
  # Command to run the application
73
- CMD ["/home/appuser/.local/bin/uv", "run", "--no-dev", "app/main.py"]
 
26
  curl \
27
  git \
28
  ffmpeg \
 
29
  libsm6 \
30
  libxext6 \
31
  libgl1 \
 
54
  SHELL ["/bin/bash", "-c"]
55
 
56
  # Copy dependency files and install dependencies
57
+ COPY --chown=appuser pyproject.toml dawsonia.toml uv.lock LICENSE README.md ./
58
 
59
+ RUN /home/appuser/.local/bin/uv sync -p 3.10 --frozen --no-cache \
60
  && chown -R appuser:appuser /home/appuser/app/.venv \
61
  && rm -rf /home/appuser/.cache
62
 
 
69
 
70
 
71
  # Command to run the application
72
+ CMD ["/home/appuser/.local/bin/uv", "run", "app/main.py"]
LICENSE-RA.txt → LICENSE RENAMED
File without changes
LICENSE.txt DELETED
@@ -1,661 +0,0 @@
1
- GNU AFFERO GENERAL PUBLIC LICENSE
2
- Version 3, 19 November 2007
3
-
4
- Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
- Everyone is permitted to copy and distribute verbatim copies
6
- of this license document, but changing it is not allowed.
7
-
8
- Preamble
9
-
10
- The GNU Affero General Public License is a free, copyleft license for
11
- software and other kinds of works, specifically designed to ensure
12
- cooperation with the community in the case of network server software.
13
-
14
- The licenses for most software and other practical works are designed
15
- to take away your freedom to share and change the works. By contrast,
16
- our General Public Licenses are intended to guarantee your freedom to
17
- share and change all versions of a program--to make sure it remains free
18
- software for all its users.
19
-
20
- When we speak of free software, we are referring to freedom, not
21
- price. Our General Public Licenses are designed to make sure that you
22
- have the freedom to distribute copies of free software (and charge for
23
- them if you wish), that you receive source code or can get it if you
24
- want it, that you can change the software or use pieces of it in new
25
- free programs, and that you know you can do these things.
26
-
27
- Developers that use our General Public Licenses protect your rights
28
- with two steps: (1) assert copyright on the software, and (2) offer
29
- you this License which gives you legal permission to copy, distribute
30
- and/or modify the software.
31
-
32
- A secondary benefit of defending all users' freedom is that
33
- improvements made in alternate versions of the program, if they
34
- receive widespread use, become available for other developers to
35
- incorporate. Many developers of free software are heartened and
36
- encouraged by the resulting cooperation. However, in the case of
37
- software used on network servers, this result may fail to come about.
38
- The GNU General Public License permits making a modified version and
39
- letting the public access it on a server without ever releasing its
40
- source code to the public.
41
-
42
- The GNU Affero General Public License is designed specifically to
43
- ensure that, in such cases, the modified source code becomes available
44
- to the community. It requires the operator of a network server to
45
- provide the source code of the modified version running there to the
46
- users of that server. Therefore, public use of a modified version, on
47
- a publicly accessible server, gives the public access to the source
48
- code of the modified version.
49
-
50
- An older license, called the Affero General Public License and
51
- published by Affero, was designed to accomplish similar goals. This is
52
- a different license, not a version of the Affero GPL, but Affero has
53
- released a new version of the Affero GPL which permits relicensing under
54
- this license.
55
-
56
- The precise terms and conditions for copying, distribution and
57
- modification follow.
58
-
59
- TERMS AND CONDITIONS
60
-
61
- 0. Definitions.
62
-
63
- "This License" refers to version 3 of the GNU Affero General Public License.
64
-
65
- "Copyright" also means copyright-like laws that apply to other kinds of
66
- works, such as semiconductor masks.
67
-
68
- "The Program" refers to any copyrightable work licensed under this
69
- License. Each licensee is addressed as "you". "Licensees" and
70
- "recipients" may be individuals or organizations.
71
-
72
- To "modify" a work means to copy from or adapt all or part of the work
73
- in a fashion requiring copyright permission, other than the making of an
74
- exact copy. The resulting work is called a "modified version" of the
75
- earlier work or a work "based on" the earlier work.
76
-
77
- A "covered work" means either the unmodified Program or a work based
78
- on the Program.
79
-
80
- To "propagate" a work means to do anything with it that, without
81
- permission, would make you directly or secondarily liable for
82
- infringement under applicable copyright law, except executing it on a
83
- computer or modifying a private copy. Propagation includes copying,
84
- distribution (with or without modification), making available to the
85
- public, and in some countries other activities as well.
86
-
87
- To "convey" a work means any kind of propagation that enables other
88
- parties to make or receive copies. Mere interaction with a user through
89
- a computer network, with no transfer of a copy, is not conveying.
90
-
91
- An interactive user interface displays "Appropriate Legal Notices"
92
- to the extent that it includes a convenient and prominently visible
93
- feature that (1) displays an appropriate copyright notice, and (2)
94
- tells the user that there is no warranty for the work (except to the
95
- extent that warranties are provided), that licensees may convey the
96
- work under this License, and how to view a copy of this License. If
97
- the interface presents a list of user commands or options, such as a
98
- menu, a prominent item in the list meets this criterion.
99
-
100
- 1. Source Code.
101
-
102
- The "source code" for a work means the preferred form of the work
103
- for making modifications to it. "Object code" means any non-source
104
- form of a work.
105
-
106
- A "Standard Interface" means an interface that either is an official
107
- standard defined by a recognized standards body, or, in the case of
108
- interfaces specified for a particular programming language, one that
109
- is widely used among developers working in that language.
110
-
111
- The "System Libraries" of an executable work include anything, other
112
- than the work as a whole, that (a) is included in the normal form of
113
- packaging a Major Component, but which is not part of that Major
114
- Component, and (b) serves only to enable use of the work with that
115
- Major Component, or to implement a Standard Interface for which an
116
- implementation is available to the public in source code form. A
117
- "Major Component", in this context, means a major essential component
118
- (kernel, window system, and so on) of the specific operating system
119
- (if any) on which the executable work runs, or a compiler used to
120
- produce the work, or an object code interpreter used to run it.
121
-
122
- The "Corresponding Source" for a work in object code form means all
123
- the source code needed to generate, install, and (for an executable
124
- work) run the object code and to modify the work, including scripts to
125
- control those activities. However, it does not include the work's
126
- System Libraries, or general-purpose tools or generally available free
127
- programs which are used unmodified in performing those activities but
128
- which are not part of the work. For example, Corresponding Source
129
- includes interface definition files associated with source files for
130
- the work, and the source code for shared libraries and dynamically
131
- linked subprograms that the work is specifically designed to require,
132
- such as by intimate data communication or control flow between those
133
- subprograms and other parts of the work.
134
-
135
- The Corresponding Source need not include anything that users
136
- can regenerate automatically from other parts of the Corresponding
137
- Source.
138
-
139
- The Corresponding Source for a work in source code form is that
140
- same work.
141
-
142
- 2. Basic Permissions.
143
-
144
- All rights granted under this License are granted for the term of
145
- copyright on the Program, and are irrevocable provided the stated
146
- conditions are met. This License explicitly affirms your unlimited
147
- permission to run the unmodified Program. The output from running a
148
- covered work is covered by this License only if the output, given its
149
- content, constitutes a covered work. This License acknowledges your
150
- rights of fair use or other equivalent, as provided by copyright law.
151
-
152
- You may make, run and propagate covered works that you do not
153
- convey, without conditions so long as your license otherwise remains
154
- in force. You may convey covered works to others for the sole purpose
155
- of having them make modifications exclusively for you, or provide you
156
- with facilities for running those works, provided that you comply with
157
- the terms of this License in conveying all material for which you do
158
- not control copyright. Those thus making or running the covered works
159
- for you must do so exclusively on your behalf, under your direction
160
- and control, on terms that prohibit them from making any copies of
161
- your copyrighted material outside their relationship with you.
162
-
163
- Conveying under any other circumstances is permitted solely under
164
- the conditions stated below. Sublicensing is not allowed; section 10
165
- makes it unnecessary.
166
-
167
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168
-
169
- No covered work shall be deemed part of an effective technological
170
- measure under any applicable law fulfilling obligations under article
171
- 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172
- similar laws prohibiting or restricting circumvention of such
173
- measures.
174
-
175
- When you convey a covered work, you waive any legal power to forbid
176
- circumvention of technological measures to the extent such circumvention
177
- is effected by exercising rights under this License with respect to
178
- the covered work, and you disclaim any intention to limit operation or
179
- modification of the work as a means of enforcing, against the work's
180
- users, your or third parties' legal rights to forbid circumvention of
181
- technological measures.
182
-
183
- 4. Conveying Verbatim Copies.
184
-
185
- You may convey verbatim copies of the Program's source code as you
186
- receive it, in any medium, provided that you conspicuously and
187
- appropriately publish on each copy an appropriate copyright notice;
188
- keep intact all notices stating that this License and any
189
- non-permissive terms added in accord with section 7 apply to the code;
190
- keep intact all notices of the absence of any warranty; and give all
191
- recipients a copy of this License along with the Program.
192
-
193
- You may charge any price or no price for each copy that you convey,
194
- and you may offer support or warranty protection for a fee.
195
-
196
- 5. Conveying Modified Source Versions.
197
-
198
- You may convey a work based on the Program, or the modifications to
199
- produce it from the Program, in the form of source code under the
200
- terms of section 4, provided that you also meet all of these conditions:
201
-
202
- a) The work must carry prominent notices stating that you modified
203
- it, and giving a relevant date.
204
-
205
- b) The work must carry prominent notices stating that it is
206
- released under this License and any conditions added under section
207
- 7. This requirement modifies the requirement in section 4 to
208
- "keep intact all notices".
209
-
210
- c) You must license the entire work, as a whole, under this
211
- License to anyone who comes into possession of a copy. This
212
- License will therefore apply, along with any applicable section 7
213
- additional terms, to the whole of the work, and all its parts,
214
- regardless of how they are packaged. This License gives no
215
- permission to license the work in any other way, but it does not
216
- invalidate such permission if you have separately received it.
217
-
218
- d) If the work has interactive user interfaces, each must display
219
- Appropriate Legal Notices; however, if the Program has interactive
220
- interfaces that do not display Appropriate Legal Notices, your
221
- work need not make them do so.
222
-
223
- A compilation of a covered work with other separate and independent
224
- works, which are not by their nature extensions of the covered work,
225
- and which are not combined with it such as to form a larger program,
226
- in or on a volume of a storage or distribution medium, is called an
227
- "aggregate" if the compilation and its resulting copyright are not
228
- used to limit the access or legal rights of the compilation's users
229
- beyond what the individual works permit. Inclusion of a covered work
230
- in an aggregate does not cause this License to apply to the other
231
- parts of the aggregate.
232
-
233
- 6. Conveying Non-Source Forms.
234
-
235
- You may convey a covered work in object code form under the terms
236
- of sections 4 and 5, provided that you also convey the
237
- machine-readable Corresponding Source under the terms of this License,
238
- in one of these ways:
239
-
240
- a) Convey the object code in, or embodied in, a physical product
241
- (including a physical distribution medium), accompanied by the
242
- Corresponding Source fixed on a durable physical medium
243
- customarily used for software interchange.
244
-
245
- b) Convey the object code in, or embodied in, a physical product
246
- (including a physical distribution medium), accompanied by a
247
- written offer, valid for at least three years and valid for as
248
- long as you offer spare parts or customer support for that product
249
- model, to give anyone who possesses the object code either (1) a
250
- copy of the Corresponding Source for all the software in the
251
- product that is covered by this License, on a durable physical
252
- medium customarily used for software interchange, for a price no
253
- more than your reasonable cost of physically performing this
254
- conveying of source, or (2) access to copy the
255
- Corresponding Source from a network server at no charge.
256
-
257
- c) Convey individual copies of the object code with a copy of the
258
- written offer to provide the Corresponding Source. This
259
- alternative is allowed only occasionally and noncommercially, and
260
- only if you received the object code with such an offer, in accord
261
- with subsection 6b.
262
-
263
- d) Convey the object code by offering access from a designated
264
- place (gratis or for a charge), and offer equivalent access to the
265
- Corresponding Source in the same way through the same place at no
266
- further charge. You need not require recipients to copy the
267
- Corresponding Source along with the object code. If the place to
268
- copy the object code is a network server, the Corresponding Source
269
- may be on a different server (operated by you or a third party)
270
- that supports equivalent copying facilities, provided you maintain
271
- clear directions next to the object code saying where to find the
272
- Corresponding Source. Regardless of what server hosts the
273
- Corresponding Source, you remain obligated to ensure that it is
274
- available for as long as needed to satisfy these requirements.
275
-
276
- e) Convey the object code using peer-to-peer transmission, provided
277
- you inform other peers where the object code and Corresponding
278
- Source of the work are being offered to the general public at no
279
- charge under subsection 6d.
280
-
281
- A separable portion of the object code, whose source code is excluded
282
- from the Corresponding Source as a System Library, need not be
283
- included in conveying the object code work.
284
-
285
- A "User Product" is either (1) a "consumer product", which means any
286
- tangible personal property which is normally used for personal, family,
287
- or household purposes, or (2) anything designed or sold for incorporation
288
- into a dwelling. In determining whether a product is a consumer product,
289
- doubtful cases shall be resolved in favor of coverage. For a particular
290
- product received by a particular user, "normally used" refers to a
291
- typical or common use of that class of product, regardless of the status
292
- of the particular user or of the way in which the particular user
293
- actually uses, or expects or is expected to use, the product. A product
294
- is a consumer product regardless of whether the product has substantial
295
- commercial, industrial or non-consumer uses, unless such uses represent
296
- the only significant mode of use of the product.
297
-
298
- "Installation Information" for a User Product means any methods,
299
- procedures, authorization keys, or other information required to install
300
- and execute modified versions of a covered work in that User Product from
301
- a modified version of its Corresponding Source. The information must
302
- suffice to ensure that the continued functioning of the modified object
303
- code is in no case prevented or interfered with solely because
304
- modification has been made.
305
-
306
- If you convey an object code work under this section in, or with, or
307
- specifically for use in, a User Product, and the conveying occurs as
308
- part of a transaction in which the right of possession and use of the
309
- User Product is transferred to the recipient in perpetuity or for a
310
- fixed term (regardless of how the transaction is characterized), the
311
- Corresponding Source conveyed under this section must be accompanied
312
- by the Installation Information. But this requirement does not apply
313
- if neither you nor any third party retains the ability to install
314
- modified object code on the User Product (for example, the work has
315
- been installed in ROM).
316
-
317
- The requirement to provide Installation Information does not include a
318
- requirement to continue to provide support service, warranty, or updates
319
- for a work that has been modified or installed by the recipient, or for
320
- the User Product in which it has been modified or installed. Access to a
321
- network may be denied when the modification itself materially and
322
- adversely affects the operation of the network or violates the rules and
323
- protocols for communication across the network.
324
-
325
- Corresponding Source conveyed, and Installation Information provided,
326
- in accord with this section must be in a format that is publicly
327
- documented (and with an implementation available to the public in
328
- source code form), and must require no special password or key for
329
- unpacking, reading or copying.
330
-
331
- 7. Additional Terms.
332
-
333
- "Additional permissions" are terms that supplement the terms of this
334
- License by making exceptions from one or more of its conditions.
335
- Additional permissions that are applicable to the entire Program shall
336
- be treated as though they were included in this License, to the extent
337
- that they are valid under applicable law. If additional permissions
338
- apply only to part of the Program, that part may be used separately
339
- under those permissions, but the entire Program remains governed by
340
- this License without regard to the additional permissions.
341
-
342
- When you convey a copy of a covered work, you may at your option
343
- remove any additional permissions from that copy, or from any part of
344
- it. (Additional permissions may be written to require their own
345
- removal in certain cases when you modify the work.) You may place
346
- additional permissions on material, added by you to a covered work,
347
- for which you have or can give appropriate copyright permission.
348
-
349
- Notwithstanding any other provision of this License, for material you
350
- add to a covered work, you may (if authorized by the copyright holders of
351
- that material) supplement the terms of this License with terms:
352
-
353
- a) Disclaiming warranty or limiting liability differently from the
354
- terms of sections 15 and 16 of this License; or
355
-
356
- b) Requiring preservation of specified reasonable legal notices or
357
- author attributions in that material or in the Appropriate Legal
358
- Notices displayed by works containing it; or
359
-
360
- c) Prohibiting misrepresentation of the origin of that material, or
361
- requiring that modified versions of such material be marked in
362
- reasonable ways as different from the original version; or
363
-
364
- d) Limiting the use for publicity purposes of names of licensors or
365
- authors of the material; or
366
-
367
- e) Declining to grant rights under trademark law for use of some
368
- trade names, trademarks, or service marks; or
369
-
370
- f) Requiring indemnification of licensors and authors of that
371
- material by anyone who conveys the material (or modified versions of
372
- it) with contractual assumptions of liability to the recipient, for
373
- any liability that these contractual assumptions directly impose on
374
- those licensors and authors.
375
-
376
- All other non-permissive additional terms are considered "further
377
- restrictions" within the meaning of section 10. If the Program as you
378
- received it, or any part of it, contains a notice stating that it is
379
- governed by this License along with a term that is a further
380
- restriction, you may remove that term. If a license document contains
381
- a further restriction but permits relicensing or conveying under this
382
- License, you may add to a covered work material governed by the terms
383
- of that license document, provided that the further restriction does
384
- not survive such relicensing or conveying.
385
-
386
- If you add terms to a covered work in accord with this section, you
387
- must place, in the relevant source files, a statement of the
388
- additional terms that apply to those files, or a notice indicating
389
- where to find the applicable terms.
390
-
391
- Additional terms, permissive or non-permissive, may be stated in the
392
- form of a separately written license, or stated as exceptions;
393
- the above requirements apply either way.
394
-
395
- 8. Termination.
396
-
397
- You may not propagate or modify a covered work except as expressly
398
- provided under this License. Any attempt otherwise to propagate or
399
- modify it is void, and will automatically terminate your rights under
400
- this License (including any patent licenses granted under the third
401
- paragraph of section 11).
402
-
403
- However, if you cease all violation of this License, then your
404
- license from a particular copyright holder is reinstated (a)
405
- provisionally, unless and until the copyright holder explicitly and
406
- finally terminates your license, and (b) permanently, if the copyright
407
- holder fails to notify you of the violation by some reasonable means
408
- prior to 60 days after the cessation.
409
-
410
- Moreover, your license from a particular copyright holder is
411
- reinstated permanently if the copyright holder notifies you of the
412
- violation by some reasonable means, this is the first time you have
413
- received notice of violation of this License (for any work) from that
414
- copyright holder, and you cure the violation prior to 30 days after
415
- your receipt of the notice.
416
-
417
- Termination of your rights under this section does not terminate the
418
- licenses of parties who have received copies or rights from you under
419
- this License. If your rights have been terminated and not permanently
420
- reinstated, you do not qualify to receive new licenses for the same
421
- material under section 10.
422
-
423
- 9. Acceptance Not Required for Having Copies.
424
-
425
- You are not required to accept this License in order to receive or
426
- run a copy of the Program. Ancillary propagation of a covered work
427
- occurring solely as a consequence of using peer-to-peer transmission
428
- to receive a copy likewise does not require acceptance. However,
429
- nothing other than this License grants you permission to propagate or
430
- modify any covered work. These actions infringe copyright if you do
431
- not accept this License. Therefore, by modifying or propagating a
432
- covered work, you indicate your acceptance of this License to do so.
433
-
434
- 10. Automatic Licensing of Downstream Recipients.
435
-
436
- Each time you convey a covered work, the recipient automatically
437
- receives a license from the original licensors, to run, modify and
438
- propagate that work, subject to this License. You are not responsible
439
- for enforcing compliance by third parties with this License.
440
-
441
- An "entity transaction" is a transaction transferring control of an
442
- organization, or substantially all assets of one, or subdividing an
443
- organization, or merging organizations. If propagation of a covered
444
- work results from an entity transaction, each party to that
445
- transaction who receives a copy of the work also receives whatever
446
- licenses to the work the party's predecessor in interest had or could
447
- give under the previous paragraph, plus a right to possession of the
448
- Corresponding Source of the work from the predecessor in interest, if
449
- the predecessor has it or can get it with reasonable efforts.
450
-
451
- You may not impose any further restrictions on the exercise of the
452
- rights granted or affirmed under this License. For example, you may
453
- not impose a license fee, royalty, or other charge for exercise of
454
- rights granted under this License, and you may not initiate litigation
455
- (including a cross-claim or counterclaim in a lawsuit) alleging that
456
- any patent claim is infringed by making, using, selling, offering for
457
- sale, or importing the Program or any portion of it.
458
-
459
- 11. Patents.
460
-
461
- A "contributor" is a copyright holder who authorizes use under this
462
- License of the Program or a work on which the Program is based. The
463
- work thus licensed is called the contributor's "contributor version".
464
-
465
- A contributor's "essential patent claims" are all patent claims
466
- owned or controlled by the contributor, whether already acquired or
467
- hereafter acquired, that would be infringed by some manner, permitted
468
- by this License, of making, using, or selling its contributor version,
469
- but do not include claims that would be infringed only as a
470
- consequence of further modification of the contributor version. For
471
- purposes of this definition, "control" includes the right to grant
472
- patent sublicenses in a manner consistent with the requirements of
473
- this License.
474
-
475
- Each contributor grants you a non-exclusive, worldwide, royalty-free
476
- patent license under the contributor's essential patent claims, to
477
- make, use, sell, offer for sale, import and otherwise run, modify and
478
- propagate the contents of its contributor version.
479
-
480
- In the following three paragraphs, a "patent license" is any express
481
- agreement or commitment, however denominated, not to enforce a patent
482
- (such as an express permission to practice a patent or covenant not to
483
- sue for patent infringement). To "grant" such a patent license to a
484
- party means to make such an agreement or commitment not to enforce a
485
- patent against the party.
486
-
487
- If you convey a covered work, knowingly relying on a patent license,
488
- and the Corresponding Source of the work is not available for anyone
489
- to copy, free of charge and under the terms of this License, through a
490
- publicly available network server or other readily accessible means,
491
- then you must either (1) cause the Corresponding Source to be so
492
- available, or (2) arrange to deprive yourself of the benefit of the
493
- patent license for this particular work, or (3) arrange, in a manner
494
- consistent with the requirements of this License, to extend the patent
495
- license to downstream recipients. "Knowingly relying" means you have
496
- actual knowledge that, but for the patent license, your conveying the
497
- covered work in a country, or your recipient's use of the covered work
498
- in a country, would infringe one or more identifiable patents in that
499
- country that you have reason to believe are valid.
500
-
501
- If, pursuant to or in connection with a single transaction or
502
- arrangement, you convey, or propagate by procuring conveyance of, a
503
- covered work, and grant a patent license to some of the parties
504
- receiving the covered work authorizing them to use, propagate, modify
505
- or convey a specific copy of the covered work, then the patent license
506
- you grant is automatically extended to all recipients of the covered
507
- work and works based on it.
508
-
509
- A patent license is "discriminatory" if it does not include within
510
- the scope of its coverage, prohibits the exercise of, or is
511
- conditioned on the non-exercise of one or more of the rights that are
512
- specifically granted under this License. You may not convey a covered
513
- work if you are a party to an arrangement with a third party that is
514
- in the business of distributing software, under which you make payment
515
- to the third party based on the extent of your activity of conveying
516
- the work, and under which the third party grants, to any of the
517
- parties who would receive the covered work from you, a discriminatory
518
- patent license (a) in connection with copies of the covered work
519
- conveyed by you (or copies made from those copies), or (b) primarily
520
- for and in connection with specific products or compilations that
521
- contain the covered work, unless you entered into that arrangement,
522
- or that patent license was granted, prior to 28 March 2007.
523
-
524
- Nothing in this License shall be construed as excluding or limiting
525
- any implied license or other defenses to infringement that may
526
- otherwise be available to you under applicable patent law.
527
-
528
- 12. No Surrender of Others' Freedom.
529
-
530
- If conditions are imposed on you (whether by court order, agreement or
531
- otherwise) that contradict the conditions of this License, they do not
532
- excuse you from the conditions of this License. If you cannot convey a
533
- covered work so as to satisfy simultaneously your obligations under this
534
- License and any other pertinent obligations, then as a consequence you may
535
- not convey it at all. For example, if you agree to terms that obligate you
536
- to collect a royalty for further conveying from those to whom you convey
537
- the Program, the only way you could satisfy both those terms and this
538
- License would be to refrain entirely from conveying the Program.
539
-
540
- 13. Remote Network Interaction; Use with the GNU General Public License.
541
-
542
- Notwithstanding any other provision of this License, if you modify the
543
- Program, your modified version must prominently offer all users
544
- interacting with it remotely through a computer network (if your version
545
- supports such interaction) an opportunity to receive the Corresponding
546
- Source of your version by providing access to the Corresponding Source
547
- from a network server at no charge, through some standard or customary
548
- means of facilitating copying of software. This Corresponding Source
549
- shall include the Corresponding Source for any work covered by version 3
550
- of the GNU General Public License that is incorporated pursuant to the
551
- following paragraph.
552
-
553
- Notwithstanding any other provision of this License, you have
554
- permission to link or combine any covered work with a work licensed
555
- under version 3 of the GNU General Public License into a single
556
- combined work, and to convey the resulting work. The terms of this
557
- License will continue to apply to the part which is the covered work,
558
- but the work with which it is combined will remain governed by version
559
- 3 of the GNU General Public License.
560
-
561
- 14. Revised Versions of this License.
562
-
563
- The Free Software Foundation may publish revised and/or new versions of
564
- the GNU Affero General Public License from time to time. Such new versions
565
- will be similar in spirit to the present version, but may differ in detail to
566
- address new problems or concerns.
567
-
568
- Each version is given a distinguishing version number. If the
569
- Program specifies that a certain numbered version of the GNU Affero General
570
- Public License "or any later version" applies to it, you have the
571
- option of following the terms and conditions either of that numbered
572
- version or of any later version published by the Free Software
573
- Foundation. If the Program does not specify a version number of the
574
- GNU Affero General Public License, you may choose any version ever published
575
- by the Free Software Foundation.
576
-
577
- If the Program specifies that a proxy can decide which future
578
- versions of the GNU Affero General Public License can be used, that proxy's
579
- public statement of acceptance of a version permanently authorizes you
580
- to choose that version for the Program.
581
-
582
- Later license versions may give you additional or different
583
- permissions. However, no additional obligations are imposed on any
584
- author or copyright holder as a result of your choosing to follow a
585
- later version.
586
-
587
- 15. Disclaimer of Warranty.
588
-
589
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590
- APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591
- HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592
- OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593
- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594
- PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595
- IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596
- ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597
-
598
- 16. Limitation of Liability.
599
-
600
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601
- WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602
- THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603
- GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604
- USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605
- DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606
- PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607
- EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608
- SUCH DAMAGES.
609
-
610
- 17. Interpretation of Sections 15 and 16.
611
-
612
- If the disclaimer of warranty and limitation of liability provided
613
- above cannot be given local legal effect according to their terms,
614
- reviewing courts shall apply local law that most closely approximates
615
- an absolute waiver of all civil liability in connection with the
616
- Program, unless a warranty or assumption of liability accompanies a
617
- copy of the Program in return for a fee.
618
-
619
- END OF TERMS AND CONDITIONS
620
-
621
- How to Apply These Terms to Your New Programs
622
-
623
- If you develop a new program, and you want it to be of the greatest
624
- possible use to the public, the best way to achieve this is to make it
625
- free software which everyone can redistribute and change under these terms.
626
-
627
- To do so, attach the following notices to the program. It is safest
628
- to attach them to the start of each source file to most effectively
629
- state the exclusion of warranty; and each file should have at least
630
- the "copyright" line and a pointer to where the full notice is found.
631
-
632
- Dawsonia
633
- Copyright (C) 2023 AiForObs
634
-
635
- This program is free software: you can redistribute it and/or modify
636
- it under the terms of the GNU Affero General Public License as published
637
- by the Free Software Foundation, either version 3 of the License, or
638
- (at your option) any later version.
639
-
640
- This program is distributed in the hope that it will be useful,
641
- but WITHOUT ANY WARRANTY; without even the implied warranty of
642
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643
- GNU Affero General Public License for more details.
644
-
645
- You should have received a copy of the GNU Affero General Public License
646
- along with this program. If not, see <https://www.gnu.org/licenses/>.
647
-
648
- Also add information on how to contact you by electronic and paper mail.
649
-
650
- If your software can interact with users remotely through a computer
651
- network, you should also make sure that it provides a way for users to
652
- get its source. For example, if your program is a web application, its
653
- interface could display a "Source" link that leads users to an archive
654
- of the code. There are many ways you could offer source, and different
655
- solutions will be better for different programs; see section 13 for the
656
- specific requirements.
657
-
658
- You should also get your employer (if you work as a programmer) or school,
659
- if any, to sign a "copyright disclaimer" for the program, if necessary.
660
- For more information on this, and how to apply and follow the GNU AGPL, see
661
- <https://www.gnu.org/licenses/>.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/content/sidebar.md CHANGED
@@ -5,11 +5,8 @@ handwritten observations in weather journals
5
 
6
  ### How It Works
7
 
8
- 1. **Upload** tab: Either,
9
- **1a.** select from the **examples** on the right side bar, or
10
- **1b.** submit your own PDF / Zarr.zip file and **upload**.
11
- 2. **Digitize**: click on the blue button to digitize the data previewed above.
12
- 3. **Results** tab: View the digitized text generated by the system.
13
  <!-- 3. **Export:** Choose your preferred format and download your results. -->
14
 
15
  > Note: This demo application is for demonstration purposes only and is not intended for production use.
 
5
 
6
  ### How It Works
7
 
8
+ 1. **Upload:** Select from examples on the right or submit your own image file (WIP!) to run through Dawsonia.
9
+ 2. **Results:** View the digitized text generated by the system.
 
 
 
10
  <!-- 3. **Export:** Choose your preferred format and download your results. -->
11
 
12
  > Note: This demo application is for demonstration purposes only and is not intended for production use.
app/main.py CHANGED
@@ -11,7 +11,6 @@ from app.gradio_config import css, theme
11
  # from app.tabs.export import export
12
  from app.tabs.submit import collection_submit_state, submit
13
 
14
- from app.tabs.submit_functions import GRADIO_CACHE
15
  from app.tabs.visualizer import collection as collection_viz_state
16
  from app.tabs.visualizer import visualizer
17
 
@@ -95,5 +94,5 @@ if __name__ == "__main__":
95
  server_port=7860,
96
  enable_monitoring=True,
97
  show_api=False,
98
- allowed_paths=["output", os.path.join(GRADIO_CACHE, "data/output")],
99
  )
 
11
  # from app.tabs.export import export
12
  from app.tabs.submit import collection_submit_state, submit
13
 
 
14
  from app.tabs.visualizer import collection as collection_viz_state
15
  from app.tabs.visualizer import visualizer
16
 
 
94
  server_port=7860,
95
  enable_monitoring=True,
96
  show_api=False,
97
+ allowed_paths=["output"],
98
  )
app/tabs/submit.py CHANGED
@@ -1,17 +1,192 @@
 
1
  import logging
 
2
  from pathlib import Path
 
 
3
 
 
 
 
 
4
  import gradio as gr
5
  from gradio_modal import Modal
 
 
 
 
 
6
 
7
- from .submit_functions import all_example_images, get_selected_example_image, move_uploaded_file, get_uploaded_image, run_dawsonia, overwrite_table_format_file
8
 
9
  logger = logging.getLogger(__name__)
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  with gr.Blocks() as submit:
 
13
  gr.Markdown(
14
- "🛈 Select or upload the image you want to transcribe. You can upload up to five images at a time."
15
  )
16
 
17
  batch_book_state = gr.State()
@@ -22,41 +197,25 @@ with gr.Blocks() as submit:
22
  with gr.Row(equal_height=True):
23
  with gr.Column(scale=5):
24
  batch_image_gallery = gr.Gallery(
25
- # file_types=[".pdf", ".zarr.zip"],
26
- label="Preview",
27
- interactive=False,
28
- object_fit="contain",
29
- # scale=0.8,
30
- )
31
-
32
- with gr.Column(scale=2):
33
- first_page = gr.Number(3, label="First page", precision=0,)
34
- last_page = gr.Number(5, label="Last page", precision=0,)
35
- table_fmt_filename = gr.Dropdown(
36
- [f.name for f in Path("table_formats").iterdir()],
37
  interactive=True,
38
- label="Select Table Format",
 
39
  )
40
 
 
 
 
41
  examples = gr.Gallery(
42
  all_example_images(),
43
- label="1a. Choose from the examples below, or",
44
  interactive=False,
45
  allow_preview=False,
46
  object_fit="scale-down",
47
  min_width=250,
48
- height=160,
49
- )
50
-
51
- upload_file = gr.File(
52
- label="1b. Upload a .pdf or .zarr.zip file",
53
- file_types=[".pdf", ".zarr.zip", ".zip"],
54
  )
55
 
56
- # upload_file_true_path = gr.Textbox(visible=False)
57
-
58
- upload_button = gr.Button(value="1b. Upload", min_width=200)
59
-
60
  with Modal(visible=False) as edit_table_fmt_modal:
61
  with gr.Column():
62
  gr.Markdown(
@@ -64,9 +223,7 @@ with gr.Blocks() as submit:
64
  "Write a custom table format, overriding the default one. "
65
  "Click on the **Save** button when you are done."
66
  )
67
- save_tf_button = gr.Button(
68
- "Save", variant="primary", scale=0, min_width=200
69
- )
70
  gr.HTML(
71
  (
72
  "<a href='https://dawsonia.readthedocs.io/en/latest/user_guide/misc.html#table-formats' target='_blank'>"
@@ -79,16 +236,7 @@ with gr.Blocks() as submit:
79
  table_fmt_config_override = gr.Code("", language="python")
80
 
81
  with gr.Row():
82
- prob_thresh = gr.Slider(
83
- minimum=0.0,
84
- maximum=1.0,
85
- value=0.75,
86
- step=0.05,
87
- label="Prediction probability threshold",
88
- )
89
-
90
- with gr.Row():
91
- run_button = gr.Button("2. Digitize", variant="primary", scale=0, min_width=200)
92
  edit_table_fmt_button = gr.Button(
93
  "Edit table format", variant="secondary", scale=0, min_width=200
94
  )
@@ -98,63 +246,30 @@ with gr.Blocks() as submit:
98
  examples.select(
99
  get_selected_example_image,
100
  (first_page, last_page),
101
- (
102
- batch_image_gallery,
103
- batch_book_state,
104
- batch_book_path_state,
105
- table_fmt_filename,
106
- table_fmt_config_override,
107
- ),
108
  trigger_mode="always_last",
109
  )
110
 
111
- upload_file.upload(move_uploaded_file, inputs=[upload_file, table_fmt_filename], outputs=batch_book_path_state)
112
-
113
- upload_button.click(
114
- get_uploaded_image,
115
- (first_page, last_page, table_fmt_filename, batch_book_path_state),
116
- (
117
- batch_image_gallery,
118
- batch_book_state,
119
- batch_book_path_state,
120
- table_fmt_config_override,
121
- ),
122
  )
123
-
124
- # @batch_image_gallery.upload(
125
- # inputs=batch_image_gallery,
126
- # outputs=[batch_image_gallery],
127
- # )
128
- # def validate_images(images):
129
- # print(images)
130
- # if len(images) > MAX_IMAGES:
131
- # gr.Warning(f"Maximum images you can upload is set to: {MAX_IMAGES}")
132
- # return gr.update(value=None)
133
-
134
- # gr.Warning(
135
- # "Digitizing uploaded images is not implemented yet! Work in progress!"
136
- # )
137
- # raise NotImplementedError("WIP")
138
- # return images
139
-
140
  run_button.click(
141
  fn=run_dawsonia,
142
- inputs=(
143
- table_fmt_config_override,
144
- first_page,
145
- last_page,
146
- prob_thresh,
147
- batch_book_state,
148
- batch_book_path_state,
149
- batch_image_gallery,
150
- ),
151
  outputs=(collection_submit_state, batch_image_gallery),
152
  )
153
 
154
  ## Table formats modal dialog box
155
  edit_table_fmt_button.click(lambda: Modal(visible=True), None, edit_table_fmt_modal)
156
  save_tf_button.click(
157
- overwrite_table_format_file,
158
- (batch_book_state, batch_book_path_state, table_fmt_config_override),
159
- (batch_book_state,),
160
- )
 
1
+ import json
2
  import logging
3
+ import os
4
  from pathlib import Path
5
+ import time
6
+ import warnings
7
 
8
+ from PIL import Image
9
+ from dawsonia import io
10
+ from dawsonia import digitize
11
+ from dawsonia.ml import ml
12
  import gradio as gr
13
  from gradio_modal import Modal
14
+ import numpy as np
15
+ from numpy.typing import NDArray
16
+ import pandas as pd
17
+ import pooch
18
+ import yaml
19
 
20
+ from .visualizer import Page, TableCell
21
 
22
  logger = logging.getLogger(__name__)
23
 
24
+ # Max number of images a user can upload at once
25
+ MAX_IMAGES = int(os.environ.get("MAX_IMAGES", 5))
26
+
27
+ # Setup the cache directory to point to the directory where the example images
28
+ # are located. The images must lay in the cache directory because otherwise they
29
+ # have to be reuploaded when drag-and-dropped to the input image widget.
30
+ GRADIO_CACHE = os.getenv("GRADIO_CACHE_DIR", ".gradio_cache")
31
+ DATA_CACHE = os.path.join(GRADIO_CACHE, "data")
32
+ EXAMPLES_DIRECTORY = os.path.join(os.getcwd(), "examples")
33
+
34
+ # Example books
35
+ PIPELINES: dict[str, dict[str, str]] = {
36
+ "bjuröklubb": dict(
37
+ url="https://git.smhi.se/ai-for-obs/data/-/raw/688c04f13e8e946962792fe4b4e0ded98800b154/raw_zarr/BJUR%C3%96KLUBB/DAGBOK_Bjur%C3%B6klubb_Station_Jan-Dec_1928.zarr.zip",
38
+ known_hash="sha256:6d87b7f79836ae6373cfab11260fe28787d93fe16199fefede6697ccd750f71a",
39
+ ),
40
+ "härnösand": dict(
41
+ url="https://git.smhi.se/ai-for-obs/data/-/raw/688c04f13e8e946962792fe4b4e0ded98800b154/raw_zarr/H%C3%84RN%C3%96SAND/DAGBOK_H%C3%A4rn%C3%B6sand_Station_1934.zarr.zip",
42
+ known_hash="sha256:a58fdb6521214d0bd569c9325ce78d696738de28ce6ec869cde0d46616b697f2",
43
+ )
44
+ }
45
+
46
+
47
+ def run_dawsonia(
48
+ table_fmt_config_override, first_page, last_page, book, gallery, progress=gr.Progress()
49
+ ):
50
+ if book is None:
51
+ raise ValueError("You need to select / upload the pages to digitize")
52
+
53
+ progress(0, desc="Dawsonia: starting")
54
+
55
+ model_path = Path("data/models/dawsonia/2024-07-02")
56
+ output_path = Path("output")
57
+
58
+ print("Dawsonia: digitizing", book)
59
+ table_fmt = book.table_format
60
+
61
+ output_path_book = output_path / book.station_name
62
+ output_path_book.mkdir(exist_ok=True, parents=True)
63
+ (output_path_book / "probablities").mkdir(exist_ok=True)
64
+
65
+ init_data: list[dict[str, NDArray]] = [
66
+ {
67
+ key: np.empty(len(table_fmt.rows), dtype="O")
68
+ for key in table_fmt.columns[table_idx]
69
+ }
70
+ for table_idx in table_fmt.preproc.idx_tables_size_verify
71
+ ]
72
+
73
+ collection = []
74
+ images = []
75
+
76
+ with warnings.catch_warnings():
77
+ warnings.simplefilter("ignore", FutureWarning)
78
+ for page_number, im_from_gallery in zip(range(first_page, last_page), gallery):
79
+ output_path_page = output_path_book / str(page_number)
80
+ gr.Info(f"Digitizing {page_number = }")
81
+
82
+ if not (output_path_book / str(page_number)).with_suffix(".parquet").exists():
83
+ digitize.digitize_page_and_write_output(
84
+ book,
85
+ init_data,
86
+ page_number=page_number,
87
+ date_str=f"0000-page-{page_number}",
88
+ model_path=model_path,
89
+ model_predict=ml.model_predict,
90
+ prob_thresh=0.5,
91
+ output_path_page=output_path_page,
92
+ output_text_fmt=False,
93
+ debug=False,
94
+ )
95
+ progress_value = (page_number - first_page) / max(1, last_page - first_page)
96
+
97
+ if results := read_page(output_path_book, str(page_number), progress, progress_value): # , im_from_gallery[0])
98
+ page, im = results
99
+ collection.append(page)
100
+ images.append(im)
101
+ else:
102
+ gr.Info(f"No tables detected in {page_number = }")
103
+
104
+
105
+ gr.Info("Pages were succesfully digitized ✨")
106
+
107
+ # yield collection, images
108
+ yield collection, gr.skip()
109
+
110
+
111
+ def read_page(output_path_book: Path, prefix: str, progress, progress_value, im_path_from_gallery: str = ""):
112
+ stats = digitize.Statistics.from_json((output_path_book / "statistics" / prefix).with_suffix(".json"))
113
+ print(stats)
114
+ progress(progress_value, desc=f"Dawsonia: {stats!s:.50}")
115
+ if stats.tables_detected > 0:
116
+ values_df = pd.read_parquet((output_path_book / prefix).with_suffix(".parquet"))
117
+ table_meta = json.loads(
118
+ (output_path_book / "table_meta" / prefix).with_suffix(".json").read_text()
119
+ )
120
+ with Image.open(
121
+ image_path:=(output_path_book / "pages" / prefix).with_suffix(".webp")
122
+ ) as im:
123
+ width = im.width
124
+ height = im.height
125
+
126
+ values_array = values_df.values.flatten()
127
+ bbox_array = np.hstack(table_meta["table_positions"]).reshape(
128
+ -1, 4
129
+ )
130
+ cells = [
131
+ make_cell(value, bbox) for value, bbox in zip(values_array, bbox_array)
132
+ ]
133
+
134
+ return Page(width, height, cells, im_path_from_gallery or str(image_path)), im
135
+
136
+
137
+ def make_cell(value: str, bbox: NDArray[np.int64]):
138
+ y, x, h, w = bbox
139
+ xmin, ymin = x-w//2, y-h//2
140
+ xmax, ymax = x+w//2, y+h//2
141
+ polygon = (xmin,ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax), (xmin,ymin)
142
+ return TableCell(polygon, text_x=x, text_y=y, text=value)
143
+
144
+
145
+ def all_example_images() -> list[str]:
146
+ """
147
+ Get paths to all example images.
148
+ """
149
+ examples = [
150
+ os.path.join(EXAMPLES_DIRECTORY, f"{pipeline}.png") for pipeline in PIPELINES
151
+ ]
152
+ return examples
153
+
154
+
155
+ def get_selected_example_image(
156
+ first_page, last_page, event: gr.SelectData
157
+ ) -> tuple[str, io.Book, str] | None:
158
+ """
159
+ Get the name of the pipeline that corresponds to the selected image.
160
+ """
161
+ # for name, details in PIPELINES.items():
162
+ name, _ext = event.value["image"]["orig_name"].split(".")
163
+
164
+ station_tf = Path("table_formats", name).with_suffix(".toml")
165
+
166
+ if (last_page - first_page) > MAX_IMAGES:
167
+ raise ValueError(f"Maximum images you can digitize is set to: {MAX_IMAGES}")
168
+
169
+ if name in PIPELINES:
170
+ book_path = pooch.retrieve(**PIPELINES[name], path=DATA_CACHE)
171
+ first, last, book = io.read_book(book_path)
172
+ book._name = name
173
+ book.size_cell = [1.0, 1.0, 1.0, 1.0]
174
+ return [book.read_image(pg) for pg in range(first_page, last_page)], book, book_path, station_tf.read_text()
175
+
176
+
177
+ def overwrite_table_format_file(book: io.Book, book_path, table_fmt: str):
178
+ name = book.station_name
179
+ table_fmt_dir = Path("table_formats")
180
+ (table_fmt_dir / name).with_suffix(".toml").write_text(table_fmt)
181
+ book.table_format = io.read_specific_table_format(table_fmt_dir, Path(book_path))
182
+ gr.Info(f"Overwritten table format file for {name}")
183
+ return book
184
+
185
 
186
  with gr.Blocks() as submit:
187
+ gr.Markdown("# Upload")
188
  gr.Markdown(
189
+ "Select or upload the image you want to transcribe. You can upload up to five images at a time."
190
  )
191
 
192
  batch_book_state = gr.State()
 
197
  with gr.Row(equal_height=True):
198
  with gr.Column(scale=5):
199
  batch_image_gallery = gr.Gallery(
200
+ file_types=["image"],
201
+ label="Image to digitize",
 
 
 
 
 
 
 
 
 
 
202
  interactive=True,
203
+ object_fit="scale-down",
204
+ scale=1,
205
  )
206
 
207
+ with gr.Column(scale=2):
208
+ first_page = gr.Number(3, label="First page of the book", precision=0)
209
+ last_page = gr.Number(5, label="Last page of the book", precision=0)
210
  examples = gr.Gallery(
211
  all_example_images(),
212
+ label="Examples",
213
  interactive=False,
214
  allow_preview=False,
215
  object_fit="scale-down",
216
  min_width=250,
 
 
 
 
 
 
217
  )
218
 
 
 
 
 
219
  with Modal(visible=False) as edit_table_fmt_modal:
220
  with gr.Column():
221
  gr.Markdown(
 
223
  "Write a custom table format, overriding the default one. "
224
  "Click on the **Save** button when you are done."
225
  )
226
+ save_tf_button = gr.Button("Save", variant="primary", scale=0, min_width=200)
 
 
227
  gr.HTML(
228
  (
229
  "<a href='https://dawsonia.readthedocs.io/en/latest/user_guide/misc.html#table-formats' target='_blank'>"
 
236
  table_fmt_config_override = gr.Code("", language="python")
237
 
238
  with gr.Row():
239
+ run_button = gr.Button("Digitize", variant="primary", scale=0, min_width=200)
 
 
 
 
 
 
 
 
 
240
  edit_table_fmt_button = gr.Button(
241
  "Edit table format", variant="secondary", scale=0, min_width=200
242
  )
 
246
  examples.select(
247
  get_selected_example_image,
248
  (first_page, last_page),
249
+ (batch_image_gallery, batch_book_state, batch_book_path_state, table_fmt_config_override),
 
 
 
 
 
 
250
  trigger_mode="always_last",
251
  )
252
 
253
+ @batch_image_gallery.upload(
254
+ inputs=batch_image_gallery,
255
+ outputs=[batch_image_gallery],
 
 
 
 
 
 
 
 
256
  )
257
+ def validate_images(images):
258
+ if len(images) > MAX_IMAGES:
259
+ gr.Warning(f"Maximum images you can upload is set to: {MAX_IMAGES}")
260
+ return gr.update(value=None)
261
+
262
+ gr.Warning("Digitizing uploaded images is not implemented yet! Work in progress!")
263
+ raise NotImplementedError("WIP")
264
+ return images
265
+
 
 
 
 
 
 
 
 
266
  run_button.click(
267
  fn=run_dawsonia,
268
+ inputs=(table_fmt_config_override, first_page, last_page, batch_book_state, batch_image_gallery),
 
 
 
 
 
 
 
 
269
  outputs=(collection_submit_state, batch_image_gallery),
270
  )
271
 
272
  ## Table formats modal dialog box
273
  edit_table_fmt_button.click(lambda: Modal(visible=True), None, edit_table_fmt_modal)
274
  save_tf_button.click(
275
+ overwrite_table_format_file, (batch_book_state, batch_book_path_state, table_fmt_config_override), (batch_book_state,))
 
 
 
app/tabs/submit_functions.py DELETED
@@ -1,304 +0,0 @@
1
- import json
2
- import os
3
- from pathlib import Path
4
- import shutil
5
- import warnings
6
-
7
- from PIL import Image
8
- from dawsonia import io
9
- from dawsonia import digitize
10
- from dawsonia.ml import ml
11
- import gradio as gr
12
- import numpy as np
13
- from numpy.typing import NDArray
14
- import pandas as pd
15
- import pooch
16
-
17
- from .visualizer_functions import Page, TableCell
18
-
19
- # Max number of images a user can upload at once
20
- MAX_IMAGES = int(os.environ.get("MAX_IMAGES", 5))
21
-
22
- # Setup the cache directory to point to the directory where the example images
23
- # are located. The images must lay in the cache directory because otherwise they
24
- # have to be reuploaded when drag-and-dropped to the input image widget.
25
- GRADIO_CACHE = os.getenv("GRADIO_CACHE_DIR", ".gradio_cache")
26
- DATA_CACHE = os.path.join(GRADIO_CACHE, "data")
27
- EXAMPLES_DIRECTORY = os.path.join(os.getcwd(), "examples")
28
-
29
- # Example books
30
- PIPELINES: dict[str, dict[str, str]] = {
31
- "bjuröklubb": dict(
32
- url="https://git.smhi.se/ai-for-obs/data/-/raw/688c04f13e8e946962792fe4b4e0ded98800b154/raw_zarr/BJUR%C3%96KLUBB/DAGBOK_Bjur%C3%B6klubb_Station_Jan-Dec_1928.zarr.zip",
33
- known_hash="sha256:6d87b7f79836ae6373cfab11260fe28787d93fe16199fefede6697ccd750f71a",
34
- ),
35
- "härnösand": dict(
36
- url="https://git.smhi.se/ai-for-obs/data/-/raw/688c04f13e8e946962792fe4b4e0ded98800b154/raw_zarr/H%C3%84RN%C3%96SAND/DAGBOK_H%C3%A4rn%C3%B6sand_Station_1934.zarr.zip",
37
- known_hash="sha256:a58fdb6521214d0bd569c9325ce78d696738de28ce6ec869cde0d46616b697f2",
38
- ),
39
- }
40
-
41
-
42
- def run_dawsonia(
43
- table_fmt_config_override,
44
- first_page,
45
- last_page,
46
- prob_thresh,
47
- book: io.Book,
48
- book_path,
49
- gallery,
50
- progress=gr.Progress(),
51
- ):
52
- if book is None:
53
- raise ValueError("You need to select / upload the pages to digitize")
54
-
55
- progress(0, desc="Dawsonia: starting")
56
-
57
- model_path = Path("data/models/dawsonia/2024-07-02")
58
- output_path = Path("output")
59
- output_path.mkdir(exist_ok=True)
60
-
61
- print("Dawsonia: digitizing", book)
62
- table_fmt = book.table_format
63
-
64
- final_output_path_book = output_path / book.station_name
65
- output_path_book = Path(book_path).parent / "output"
66
- output_path_book.mkdir(exist_ok=True, parents=True)
67
- (output_path_book / "probablities").mkdir(exist_ok=True)
68
-
69
- init_data: list[dict[str, NDArray]] = [
70
- {
71
- key: np.empty(len(table_fmt.rows), dtype="O")
72
- for key in table_fmt.columns[table_idx]
73
- }
74
- for table_idx in table_fmt.preproc.idx_tables_size_verify
75
- ]
76
-
77
- collection = []
78
- images = []
79
-
80
- with warnings.catch_warnings():
81
- warnings.simplefilter("ignore", FutureWarning)
82
- for page_number in range(first_page, last_page):
83
- output_path_page = output_path_book / str(page_number)
84
- gr.Info(f"Digitizing {page_number = }")
85
-
86
- if (
87
- not (output_path_book / str(page_number))
88
- .with_suffix(".parquet")
89
- .exists()
90
- ):
91
- digitize.digitize_page_and_write_output(
92
- book,
93
- init_data,
94
- page_number=page_number,
95
- date_str=f"0000-page-{page_number}",
96
- model_path=model_path,
97
- model_predict=ml.model_predict,
98
- prob_thresh=prob_thresh,
99
- output_path_page=output_path_page,
100
- output_text_fmt=False,
101
- debug=False,
102
- )
103
- _synctree(output_path_book, final_output_path_book)
104
-
105
- progress_value = (page_number - first_page) / max(1, last_page - first_page)
106
-
107
- # if final_output_path_book.exists():
108
- # shutil.rmtree(final_output_path_book)
109
-
110
- # shutil.copytree(output_path_book, final_output_path_book)
111
- for page_number, im_from_gallery in zip(range(first_page, last_page), gallery):
112
- if results := read_page(
113
- final_output_path_book,
114
- str(page_number),
115
- prob_thresh,
116
- progress,
117
- 1.0,
118
- table_fmt.preproc.idx_tables_size_verify,
119
- ): # , im_from_gallery[0])
120
- page, im = results
121
- collection.append(page)
122
- images.append(im)
123
- yield collection, gr.skip()
124
- else:
125
- gr.Info(f"No tables detected in {page_number = }")
126
-
127
- gr.Info("Pages were succesfully digitized ✨")
128
-
129
- # yield collection, images
130
- yield collection, gr.skip()
131
-
132
-
133
- def _synctree(source_dir, dest_dir):
134
- source_dir = Path(source_dir)
135
- dest_dir = Path(dest_dir)
136
- if not dest_dir.exists():
137
- dest_dir.mkdir(parents=True)
138
-
139
- for root, _, files in os.walk(source_dir):
140
- root = Path(root)
141
- relative_root = root.relative_to(source_dir)
142
-
143
- # Create subdirectories in the destination directory
144
- dest_subdir_path = dest_dir / relative_root
145
- if not dest_subdir_path.exists():
146
- dest_subdir_path.mkdir(parents=True, exist_ok=True)
147
-
148
- for file_ in files:
149
- source_file_path = root / file_
150
- dest_file_path = dest_subdir_path / file_
151
-
152
- # Copy only if the file does not already exist or is newer
153
- if (
154
- not dest_file_path.exists()
155
- or (source_file_path.stat().st_mtime - dest_file_path.stat().st_mtime) > 0
156
- ):
157
- shutil.copy2(source_file_path, dest_file_path)
158
-
159
-
160
- def read_page(
161
- output_path_book: Path,
162
- prefix: str,
163
- prob_thresh: float,
164
- progress,
165
- progress_value,
166
- idx_tables_size_verify: list[int],
167
- im_path_from_gallery: str = "",
168
- ):
169
- stats = digitize.Statistics.from_json(
170
- (output_path_book / "statistics" / prefix).with_suffix(".json")
171
- )
172
- print(stats)
173
- progress(progress_value, desc=f"Dawsonia: {stats!s:.50}")
174
- if stats.tables_detected > 0:
175
- values_df = pd.read_parquet((output_path_book / prefix).with_suffix(".parquet"))
176
- prob_df = pd.read_parquet(
177
- (output_path_book / "probablities" / prefix).with_suffix(".parquet")
178
- )
179
- table_meta = json.loads(
180
- (output_path_book / "table_meta" / prefix).with_suffix(".json").read_text()
181
- )
182
- with Image.open(
183
- image_path := (output_path_book / "pages" / prefix).with_suffix(".webp")
184
- ) as im:
185
- width = im.width
186
- height = im.height
187
-
188
- values_array = values_df.values.flatten()
189
- prob_array = prob_df.values.flatten()
190
- # FIXME: hardcoded to get upto 2 tables. Use idx_tables_size_verify and reconstruct bbox_array
191
- try:
192
- bbox_array = np.hstack(table_meta["table_positions"][:2]).reshape(-1, 4)
193
- except ValueError:
194
- bbox_array = np.reshape(table_meta["table_positions"][0], (-1, 4))
195
-
196
- cells = [
197
- make_cell(value, bbox)
198
- for value, prob, bbox in zip(values_array, prob_array, bbox_array)
199
- if prob > prob_thresh
200
- ]
201
-
202
- return Page(width, height, cells, im_path_from_gallery or str(image_path)), im
203
-
204
-
205
- def make_cell(value: str, bbox: NDArray[np.int64]):
206
- y, x, h, w = bbox
207
- xmin, ymin = x - w // 2, y - h // 2
208
- xmax, ymax = x + w // 2, y + h // 2
209
- polygon = (xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax), (xmin, ymin)
210
- return TableCell(polygon, text_x=x - w // 4, text_y=y, text=value)
211
-
212
-
213
- def all_example_images() -> list[str]:
214
- """
215
- Get paths to all example images.
216
- """
217
- examples = [
218
- os.path.join(EXAMPLES_DIRECTORY, f"{pipeline}.png") for pipeline in PIPELINES
219
- ]
220
- return examples
221
-
222
-
223
- def get_selected_example_image(
224
- first_page, last_page, event: gr.SelectData
225
- ) -> tuple[list[Image.Image], io.Book, str, str, str] | None:
226
- """
227
- Get the name of the pipeline that corresponds to the selected image.
228
- """
229
- orig_name = event.value["image"]["orig_name"]
230
- # for name, details in PIPELINES.items():
231
- orig_path = Path(orig_name)
232
- name = orig_path.name
233
- for suffix in orig_path.suffixes[::-1]:
234
- name = name.removesuffix(suffix)
235
-
236
- station_tf = Path("table_formats", name).with_suffix(".toml")
237
-
238
- if (last_page - first_page) > MAX_IMAGES:
239
- error = f"Maximum images you can digitize is set to: {MAX_IMAGES}"
240
- gr.Warning(error)
241
- raise ValueError(error)
242
-
243
- if name in PIPELINES:
244
- book_path = pooch.retrieve(**PIPELINES[name], path=DATA_CACHE)
245
- first, last, book = io.read_book(book_path)
246
- book._name = name
247
- book.size_cell = [1.0, 1.0, 1.0, 1.0]
248
- return (
249
- [book.read_image(pg) for pg in range(first_page, last_page)],
250
- book,
251
- book_path,
252
- station_tf.name,
253
- station_tf.read_text(),
254
- )
255
-
256
-
257
- def move_uploaded_file(uploaded, table_fmt_filename):
258
- current_directory = Path(uploaded).parent
259
-
260
- # Define the target directory where you want to save the uploaded files
261
- target_directory = current_directory / table_fmt_filename.removesuffix(".toml")
262
- os.makedirs(target_directory, exist_ok=True)
263
-
264
- # Move the uploaded file to the target directory
265
- true_path = Path(target_directory / Path(uploaded).name)
266
- # if true_path.exists():
267
- # true_path.unlink()
268
-
269
- shutil.copy2(uploaded, true_path)
270
- print(f"Copy created", true_path)
271
- return str(true_path)
272
-
273
-
274
- def get_uploaded_image(
275
- first_page: int, last_page: int, table_fmt_filename: str, filename: str
276
- ) -> tuple[list[NDArray], io.Book, str, str] | None:
277
-
278
- orig_path = Path(filename)
279
- name = orig_path.name
280
- for suffix in orig_path.suffixes[::-1]:
281
- name = name.removesuffix(suffix)
282
-
283
- station_tf = Path("table_formats", table_fmt_filename)
284
- if not station_tf.exists():
285
- station_tf = Path("table_formats", "bjuröklubb.toml")
286
-
287
- first, last, book = io.read_book(Path(filename))
288
- book._name = name
289
- book.size_cell = [1.0, 1.0, 1.0, 1.0]
290
- return (
291
- [book.read_page(pg) for pg in range(first_page, last_page)],
292
- book,
293
- filename,
294
- station_tf.read_text(),
295
- )
296
-
297
-
298
- def overwrite_table_format_file(book: io.Book, book_path, table_fmt: str):
299
- name = book.station_name
300
- table_fmt_dir = Path("table_formats")
301
- (table_fmt_dir / name).with_suffix(".toml").write_text(table_fmt)
302
- book.table_format = io.read_specific_table_format(table_fmt_dir, Path(book_path))
303
- gr.Info(f"Overwritten table format file for {name}")
304
- return book
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/tabs/visualizer.py CHANGED
@@ -1,15 +1,70 @@
 
1
  import gradio as gr
2
  from jinja2 import Environment, FileSystemLoader
 
3
 
4
  _ENV = Environment(loader=FileSystemLoader("app/assets/jinja-templates"))
5
  _IMAGE_TEMPLATE = _ENV.get_template("image.j2")
6
 
7
- from .visualizer_functions import render_image, toggle_navigation_button, activate_left_button, activate_right_button, right_button_click, left_button_click, update_image_caption
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
 
10
  with gr.Blocks() as visualizer:
 
11
  gr.Markdown(
12
- "🛈 The image to the below shows where Dawsonia found text in the image."
13
  )
14
 
15
  with gr.Row():
@@ -31,7 +86,7 @@ with gr.Blocks() as visualizer:
31
  "← Previous", visible=False, interactive=False, scale=0
32
  )
33
  right = gr.Button("Next →", visible=False, scale=0)
34
-
35
  collection = gr.State()
36
  current_page_index = gr.State(0)
37
 
@@ -40,7 +95,7 @@ with gr.Blocks() as visualizer:
40
  right.click(
41
  right_button_click, [collection, current_page_index], current_page_index
42
  )
43
-
44
  # Updates on collection change:
45
  # - update the view
46
  # - reset the page index (always start on page 0)
 
1
+ import os
2
  import gradio as gr
3
  from jinja2 import Environment, FileSystemLoader
4
+ from typing_extensions import TypeAlias
5
 
6
  _ENV = Environment(loader=FileSystemLoader("app/assets/jinja-templates"))
7
  _IMAGE_TEMPLATE = _ENV.get_template("image.j2")
8
 
9
+ from typing import NamedTuple, TypeAlias
10
+ from dawsonia.typing import BBoxTuple
11
+
12
+
13
+ class TableCell(NamedTuple):
14
+ polygon: tuple[tuple[int, int], ...]
15
+ text_x: int
16
+ text_y: int
17
+ text: str
18
+
19
+
20
+ class Page(NamedTuple):
21
+ width: int
22
+ height: int
23
+ cells: list[TableCell]
24
+ path: str
25
+
26
+
27
+ Collection: TypeAlias = list[Page]
28
+
29
+ def render_image(collection: Collection, current_page_index: int) -> str:
30
+ return _IMAGE_TEMPLATE.render(
31
+ page=collection[current_page_index],
32
+ )
33
+
34
+ def toggle_navigation_button(collection: Collection):
35
+ visible = len(collection) > 1
36
+ return gr.update(visible=visible)
37
+
38
+
39
+ def activate_left_button(current_page_index):
40
+ interactive = current_page_index > 0
41
+ return gr.update(interactive=interactive)
42
+
43
+
44
+ def activate_right_button(collection: Collection, current_page_index):
45
+ interactive = current_page_index + 1 < len(collection)
46
+ return gr.update(interactive=interactive)
47
+
48
+
49
+ def right_button_click(collection: Collection, current_page_index):
50
+ max_index = len(collection) - 1
51
+ return min(max_index, current_page_index + 1)
52
+
53
+
54
+ def left_button_click(current_page_index):
55
+ return max(0, current_page_index - 1)
56
+
57
+
58
+ def update_image_caption(collection: Collection, current_page_index):
59
+ n_pages = len(collection)
60
+ label = os.path.split(collection[current_page_index].path)[-1]
61
+ return f"image {current_page_index + 1} of {n_pages}: `{label}`"
62
 
63
 
64
  with gr.Blocks() as visualizer:
65
+ gr.Markdown("# Result")
66
  gr.Markdown(
67
+ "The image to the below shows where Dawsonia found text in the image."
68
  )
69
 
70
  with gr.Row():
 
86
  "← Previous", visible=False, interactive=False, scale=0
87
  )
88
  right = gr.Button("Next →", visible=False, scale=0)
89
+
90
  collection = gr.State()
91
  current_page_index = gr.State(0)
92
 
 
95
  right.click(
96
  right_button_click, [collection, current_page_index], current_page_index
97
  )
98
+
99
  # Updates on collection change:
100
  # - update the view
101
  # - reset the page index (always start on page 0)
app/tabs/visualizer_functions.py DELETED
@@ -1,62 +0,0 @@
1
- import os
2
- import gradio as gr
3
- from jinja2 import Environment, FileSystemLoader
4
- from typing_extensions import TypeAlias
5
-
6
- _ENV = Environment(loader=FileSystemLoader("app/assets/jinja-templates"))
7
- _IMAGE_TEMPLATE = _ENV.get_template("image.j2")
8
-
9
- from typing import NamedTuple, TypeAlias
10
-
11
-
12
- class TableCell(NamedTuple):
13
- polygon: tuple[tuple[int, int], ...]
14
- text_x: int
15
- text_y: int
16
- text: str
17
-
18
-
19
- class Page(NamedTuple):
20
- width: int
21
- height: int
22
- cells: list[TableCell]
23
- path: str
24
-
25
-
26
- Collection: TypeAlias = list[Page]
27
-
28
-
29
- def render_image(collection: Collection, current_page_index: int) -> str:
30
- return _IMAGE_TEMPLATE.render(
31
- page=collection[current_page_index],
32
- )
33
-
34
-
35
- def toggle_navigation_button(collection: Collection):
36
- visible = len(collection) > 1
37
- return gr.update(visible=visible)
38
-
39
-
40
- def activate_left_button(current_page_index):
41
- interactive = current_page_index > 0
42
- return gr.update(interactive=interactive)
43
-
44
-
45
- def activate_right_button(collection: Collection, current_page_index):
46
- interactive = current_page_index + 1 < len(collection)
47
- return gr.update(interactive=interactive)
48
-
49
-
50
- def right_button_click(collection: Collection, current_page_index):
51
- max_index = len(collection) - 1
52
- return min(max_index, current_page_index + 1)
53
-
54
-
55
- def left_button_click(current_page_index):
56
- return max(0, current_page_index - 1)
57
-
58
-
59
- def update_image_caption(collection: Collection, current_page_index):
60
- n_pages = len(collection)
61
- label = os.path.split(collection[current_page_index].path)[-1]
62
- return f"image {current_page_index + 1} of {n_pages}: `{label}`"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -16,17 +16,4 @@ dependencies = [
16
 
17
  [tool.uv.sources]
18
  # dawsonia = { url = "https://git.smhi.se/ai-for-obs/dawsonia/-/archive/v0.1.0b2/dawsonia-v0.1.0b2.tar.gz" }
19
- dawsonia = { url = "https://git.smhi.se/ai-for-obs/dawsonia/-/archive/1c252df1066e1d7bae9d95637b9493294537f7bf/dawsonia-1c252df1066e1d7bae9d95637b9493294537f7bf.zip" }
20
-
21
- [dependency-groups]
22
- dev = [
23
- "ipykernel>=6.29.5",
24
- "ipython>=8.32.0",
25
- "onnxruntime>=1.20.1",
26
- "pip>=25.1.1",
27
- "pyqt6>=6.8.1",
28
- "pytest>=8.3.5",
29
- ]
30
-
31
- [tool.pytest.ini_options]
32
- pythonpath="."
 
16
 
17
  [tool.uv.sources]
18
  # dawsonia = { url = "https://git.smhi.se/ai-for-obs/dawsonia/-/archive/v0.1.0b2/dawsonia-v0.1.0b2.tar.gz" }
19
+ dawsonia = { url = "https://git.smhi.se/ai-for-obs/dawsonia/-/archive/e3dce11d60d07729a8a5004c5343647e84722d52/dawsonia-e3dce11d60d07729a8a5004c5343647e84722d52.tar.gz" }
 
 
 
 
 
 
 
 
 
 
 
 
 
run.sh CHANGED
@@ -1,4 +1,3 @@
1
  #!/bin/bash
2
  source .venv/bin/activate
3
- export GRADIO_CACHE_DIR="$PWD/.gradio_cache"
4
- PYTHONPATH=$(pwd) gradio app/main.py
 
1
  #!/bin/bash
2
  source .venv/bin/activate
3
+ GRADIO_CACHE_DIR=.gradio_cache PYTHONPATH=$(pwd) gradio app/main.py
 
table_formats/haparanda.toml DELETED
@@ -1,128 +0,0 @@
1
- [1925]
2
- version = 1
3
-
4
- [1926]
5
- version = 1
6
-
7
- [1927]
8
- version = 1
9
-
10
- [1928]
11
- version = 1
12
-
13
- [1929]
14
- version = 1
15
-
16
- [1938-0]
17
- # Jan-Jun
18
- version = 0
19
-
20
- [1938-1]
21
- # Jul-Dec
22
- version = 2
23
-
24
- [default]
25
- version = 0
26
-
27
- [version.0]
28
- columns = [
29
- [
30
- "term_på_baro",
31
- "barom",
32
- "torra_term",
33
- "våta_term",
34
- "moln_slag_lägre",
35
- "moln_mängd_lägre",
36
- "moln_slag_medel",
37
- "moln_slag_högre"
38
- ],
39
- [
40
- "moln_het_sol_dimma_nederbörd_total",
41
- "vind_riktning",
42
- "vind_beaufort",
43
- "vind_m_sek",
44
- "sikt",
45
- "sjögang",
46
- "maximi_term",
47
- "minimi_term",
48
- "nederbörd_mängd",
49
- "nederbörd_slag"
50
- ]
51
- ]
52
- name_idx = "tid"
53
- rows = [2, 8, 14, 19, 21]
54
- tables = [
55
- [5, 8],
56
- [5, 10],
57
- [3, 1],
58
- [4, 2],
59
- [4, 5]
60
- ]
61
-
62
- [version.1]
63
- columns = [
64
- [
65
- "term_på_baro",
66
- "barom",
67
- "torra_term",
68
- "våta_term",
69
- "moln_slag_lägre",
70
- "moln_mängd_lägre",
71
- "moln_slag_högre",
72
- "moln_mängd_total"
73
- ],
74
- [
75
- "vind_riktning",
76
- "vind_beaufort",
77
- "vind_m_sek",
78
- "sikt",
79
- "sjögang",
80
- "maximi_term",
81
- "minimi_term",
82
- "nederbörd_mängd",
83
- "nederbörd_slag"
84
- ]
85
- ]
86
- rows = [2, 8, 14, 19, 21]
87
- tables = [
88
- [5, 8],
89
- [5, 9],
90
- [3, 1],
91
- [4, 2],
92
- [4, 5]
93
- ]
94
-
95
- [version.2]
96
- columns = [
97
- [
98
- "term_på_baro",
99
- "barom",
100
- "torra_term",
101
- "våta_term",
102
- "moln_slag_lägre",
103
- "moln_mängd_lägre",
104
- "moln_slag_medel",
105
- "moln_slag_högre",
106
- "moln_höjd"
107
- ],
108
- [
109
- "moln_het_sol_dimma_nederbörd_total",
110
- "vind_riktning",
111
- "vind_beaufort",
112
- "vind_m_sek",
113
- "sikt",
114
- "sjögang",
115
- "maximi_term",
116
- "minimi_term",
117
- "nederbörd_mängd",
118
- "nederbörd_slag"
119
- ]
120
- ]
121
- rows = [2, 5, 8, 11, 14, 17, 19, 21]
122
- tables = [
123
- [8, 9],
124
- [8, 10],
125
- [3, 1],
126
- [7, 3],
127
- [7, 5]
128
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
table_formats/hoburg.toml DELETED
@@ -1,128 +0,0 @@
1
- [1925]
2
- version = 1
3
-
4
- [1926]
5
- version = 1
6
-
7
- [1927]
8
- version = 1
9
-
10
- [1928]
11
- version = 1
12
-
13
- [1929]
14
- version = 1
15
-
16
- [1938-0]
17
- # Jan-Jun
18
- version = 0
19
-
20
- [1938-1]
21
- # Jul-Dec
22
- version = 2
23
-
24
- [default]
25
- version = 0
26
-
27
- [version.0]
28
- columns = [
29
- [
30
- "term_på_baro",
31
- "barom",
32
- "torra_term",
33
- "våta_term",
34
- "moln_slag_lägre",
35
- "moln_mängd_lägre",
36
- "moln_slag_medel",
37
- "moln_slag_högre"
38
- ],
39
- [
40
- "moln_het_sol_dimma_nederbörd_total",
41
- "vind_riktning",
42
- "vind_beaufort",
43
- "vind_m_sek",
44
- "sikt",
45
- "sjögang",
46
- "maximi_term",
47
- "minimi_term",
48
- "nederbörd_mängd",
49
- "nederbörd_slag"
50
- ]
51
- ]
52
- name_idx = "tid"
53
- rows = [2, 8, 14, 19, 21]
54
- tables = [
55
- [5, 8],
56
- [5, 10],
57
- [3, 1],
58
- [4, 2],
59
- [4, 5]
60
- ]
61
-
62
- [version.1]
63
- columns = [
64
- [
65
- "term_på_baro",
66
- "barom",
67
- "torra_term",
68
- "våta_term",
69
- "moln_slag_lägre",
70
- "moln_mängd_lägre",
71
- "moln_slag_högre",
72
- "moln_mängd_total"
73
- ],
74
- [
75
- "vind_riktning",
76
- "vind_beaufort",
77
- "vind_m_sek",
78
- "sikt",
79
- "sjögang",
80
- "maximi_term",
81
- "minimi_term",
82
- "nederbörd_mängd",
83
- "nederbörd_slag"
84
- ]
85
- ]
86
- rows = [2, 8, 14, 19, 21]
87
- tables = [
88
- [5, 8],
89
- [5, 9],
90
- [3, 1],
91
- [4, 2],
92
- [4, 5]
93
- ]
94
-
95
- [version.2]
96
- columns = [
97
- [
98
- "term_på_baro",
99
- "barom",
100
- "torra_term",
101
- "våta_term",
102
- "moln_slag_lägre",
103
- "moln_mängd_lägre",
104
- "moln_slag_medel",
105
- "moln_slag_högre",
106
- "moln_höjd"
107
- ],
108
- [
109
- "moln_het_sol_dimma_nederbörd_total",
110
- "vind_riktning",
111
- "vind_beaufort",
112
- "vind_m_sek",
113
- "sikt",
114
- "sjögang",
115
- "maximi_term",
116
- "minimi_term",
117
- "nederbörd_mängd",
118
- "nederbörd_slag"
119
- ]
120
- ]
121
- rows = [2, 5, 8, 11, 14, 17, 19, 21]
122
- tables = [
123
- [8, 9],
124
- [8, 10],
125
- [3, 1],
126
- [7, 3],
127
- [7, 5]
128
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
table_formats/holmögadd.toml DELETED
@@ -1,132 +0,0 @@
1
- [1925]
2
- version = 1
3
-
4
- [1926]
5
- version = 1
6
-
7
- [1927]
8
- version = 1
9
-
10
- [1928]
11
- version = 1
12
-
13
- [1929]
14
- version = 1
15
-
16
- [1930]
17
- # NOTE: starts with June and then Jan-May, some pages are crossed out (31-May)
18
- version = 0
19
-
20
- [1938-0]
21
- # Jan-Jun
22
- version = 0
23
-
24
- [1938-1]
25
- # Jul-Dec
26
- version = 2
27
-
28
- [default]
29
- version = 0
30
-
31
- [version.0]
32
- columns = [
33
- [
34
- "term_på_baro",
35
- "barom",
36
- "torra_term",
37
- "våta_term",
38
- "moln_slag_lägre",
39
- "moln_mängd_lägre",
40
- "moln_slag_medel",
41
- "moln_slag_högre"
42
- ],
43
- [
44
- "moln_het_sol_dimma_nederbörd_total",
45
- "vind_riktning",
46
- "vind_beaufort",
47
- "vind_m_sek",
48
- "sikt",
49
- "sjögang",
50
- "maximi_term",
51
- "minimi_term",
52
- "nederbörd_mängd",
53
- "nederbörd_slag"
54
- ]
55
- ]
56
- name_idx = "tid"
57
- rows = [2, 8, 14, 19, 21]
58
- tables = [
59
- [5, 8],
60
- [5, 10],
61
- [3, 1],
62
- [4, 2],
63
- [4, 5]
64
- ]
65
-
66
- [version.1]
67
- columns = [
68
- [
69
- "term_på_baro",
70
- "barom",
71
- "torra_term",
72
- "våta_term",
73
- "moln_slag_lägre",
74
- "moln_mängd_lägre",
75
- "moln_slag_högre",
76
- "moln_mängd_total"
77
- ],
78
- [
79
- "vind_riktning",
80
- "vind_beaufort",
81
- "vind_m_sek",
82
- "sikt",
83
- "sjögang",
84
- "maximi_term",
85
- "minimi_term",
86
- "nederbörd_mängd",
87
- "nederbörd_slag"
88
- ]
89
- ]
90
- rows = [2, 8, 14, 19, 21]
91
- tables = [
92
- [5, 8],
93
- [5, 9],
94
- [3, 1],
95
- [4, 2],
96
- [4, 5]
97
- ]
98
-
99
- [version.2]
100
- columns = [
101
- [
102
- "term_på_baro",
103
- "barom",
104
- "torra_term",
105
- "våta_term",
106
- "moln_slag_lägre",
107
- "moln_mängd_lägre",
108
- "moln_slag_medel",
109
- "moln_slag_högre",
110
- "moln_höjd"
111
- ],
112
- [
113
- "moln_het_sol_dimma_nederbörd_total",
114
- "vind_riktning",
115
- "vind_beaufort",
116
- "vind_m_sek",
117
- "sikt",
118
- "sjögang",
119
- "maximi_term",
120
- "minimi_term",
121
- "nederbörd_mängd",
122
- "nederbörd_slag"
123
- ]
124
- ]
125
- rows = [2, 5, 8, 11, 14, 17, 19, 21]
126
- tables = [
127
- [8, 9],
128
- [8, 10],
129
- [3, 1],
130
- [7, 3],
131
- [7, 5]
132
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
table_formats/landsort.toml DELETED
@@ -1,128 +0,0 @@
1
- [1925]
2
- version = 1
3
-
4
- [1926]
5
- version = 1
6
-
7
- [1927]
8
- version = 1
9
-
10
- [1928]
11
- version = 1
12
-
13
- [1929]
14
- version = 1
15
-
16
- [1938-0]
17
- # Jan-Jun
18
- version = 0
19
-
20
- [1938-1]
21
- # Jul-Dec
22
- version = 2
23
-
24
- [default]
25
- version = 0
26
-
27
- [version.0]
28
- columns = [
29
- [
30
- "term_på_baro",
31
- "barom",
32
- "torra_term",
33
- "våta_term",
34
- "moln_slag_lägre",
35
- "moln_mängd_lägre",
36
- "moln_slag_medel",
37
- "moln_slag_högre"
38
- ],
39
- [
40
- "moln_het_sol_dimma_nederbörd_total",
41
- "vind_riktning",
42
- "vind_beaufort",
43
- "vind_m_sek",
44
- "sikt",
45
- "sjögang",
46
- "maximi_term",
47
- "minimi_term",
48
- "nederbörd_mängd",
49
- "nederbörd_slag"
50
- ]
51
- ]
52
- name_idx = "tid"
53
- rows = [8, 11, 14, 19, 21]
54
- tables = [
55
- [5, 8],
56
- [5, 10],
57
- [3, 1],
58
- [4, 2],
59
- [4, 5]
60
- ]
61
-
62
- [version.1]
63
- columns = [
64
- [
65
- "term_på_baro",
66
- "barom",
67
- "torra_term",
68
- "våta_term",
69
- "moln_slag_lägre",
70
- "moln_mängd_lägre",
71
- "moln_slag_högre",
72
- "moln_mängd_total"
73
- ],
74
- [
75
- "vind_riktning",
76
- "vind_beaufort",
77
- "vind_m_sek",
78
- "sikt",
79
- "sjögang",
80
- "maximi_term",
81
- "minimi_term",
82
- "nederbörd_mängd",
83
- "nederbörd_slag"
84
- ]
85
- ]
86
- rows = [2, 8, 14, 19, 21]
87
- tables = [
88
- [5, 8],
89
- [5, 9],
90
- [3, 1],
91
- [4, 2],
92
- [4, 5]
93
- ]
94
-
95
- [version.2]
96
- columns = [
97
- [
98
- "term_på_baro",
99
- "barom",
100
- "torra_term",
101
- "våta_term",
102
- "moln_slag_lägre",
103
- "moln_mängd_lägre",
104
- "moln_slag_medel",
105
- "moln_slag_högre",
106
- "moln_höjd"
107
- ],
108
- [
109
- "moln_het_sol_dimma_nederbörd_total",
110
- "vind_riktning",
111
- "vind_beaufort",
112
- "vind_m_sek",
113
- "sikt",
114
- "sjögang",
115
- "maximi_term",
116
- "minimi_term",
117
- "nederbörd_mängd",
118
- "nederbörd_slag"
119
- ]
120
- ]
121
- rows = [2, 5, 8, 11, 14, 17, 19, 21]
122
- tables = [
123
- [8, 9],
124
- [8, 10],
125
- [3, 1],
126
- [7, 3],
127
- [7, 5]
128
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
table_formats/malmslätt.toml DELETED
@@ -1,88 +0,0 @@
1
- [1928]
2
- # row with time 2 and 19 actually stores some additional information with percentage
3
- # for the barometer and wet thermometer
4
- version = 1
5
-
6
- [1929]
7
- # row with time 2 and 19 actually stores some additional information with percentage
8
- # for the barometer and wet thermometer
9
- version = 1
10
-
11
- [1930-0]
12
- # rows change time frequently and inconsistently. For example 2 -> 8 and 8 -> 11
13
- version = 1
14
-
15
- [1930-1]
16
- version = 0
17
-
18
- [default]
19
- version = 0
20
-
21
- [version.0]
22
- columns = [
23
- [
24
- "term_på_baro",
25
- "barom",
26
- "torra_term",
27
- "våta_term",
28
- "moln_slag_lägre",
29
- "moln_mängd_lägre",
30
- "moln_slag_medel",
31
- "moln_slag_högre"
32
- ],
33
- [
34
- "moln_het_sol_dimma_nederbörd_total",
35
- "vind_riktning",
36
- "vind_beaufort",
37
- "vind_m_sek",
38
- "sikt",
39
- "sjögang",
40
- "maximi_term",
41
- "minimi_term",
42
- "nederbörd_mängd",
43
- "nederbörd_slag"
44
- ]
45
- ]
46
- name_idx = "tid"
47
- # Not exact, sometimes 6:30 -> 7 or 8 and 11 -> 10:30
48
- rows = [06:30:00, 8, 11, 14, 21]
49
- tables = [
50
- [5, 8],
51
- [5, 10],
52
- [3, 1],
53
- [4, 2],
54
- [4, 5]
55
- ]
56
-
57
- [version.1]
58
- columns = [
59
- [
60
- "term_på_baro",
61
- "barom",
62
- "torra_term",
63
- "våta_term",
64
- "moln_slag_lägre",
65
- "moln_mängd_lägre",
66
- "moln_slag_högre",
67
- "moln_mängd_total"
68
- ],
69
- [
70
- "vind_riktning",
71
- "vind_beaufort",
72
- "vind_m_sek",
73
- "sikt",
74
- "sjögang",
75
- "maximi_term",
76
- "minimi_term",
77
- "nederbörd_mängd",
78
- "nederbörd_slag"
79
- ]
80
- ]
81
- rows = [2, 8, 14, 19, 21]
82
- tables = [
83
- [5, 8],
84
- [5, 9],
85
- [3, 1],
86
- [4, 2],
87
- [4, 5]
88
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
table_formats/samplan.toml DELETED
@@ -1,54 +0,0 @@
1
- [default]
2
- version = 0
3
-
4
- [default.preproc]
5
- corr_rotate = false
6
- idx_tables_size_verify = [0]
7
- row_idx_unit = "DAYS"
8
- table_modif = false
9
-
10
- [version.0]
11
- columns = [
12
- "nederbörd_slag_0",
13
- "nederbörd_slag_1",
14
- "nederbörd_slag_2",
15
- "nederbörd_slag_3",
16
- "nederbörd_slag_4",
17
- "nederbörd_mängd_0",
18
- "nederbörd_mängd_1",
19
- "nederbörd_mängd_2",
20
- "nederbörd_mängd_3",
21
- "nederbörd_mängd_sym",
22
- "snötäcke_djup_0",
23
- "snötäcke_djup_1",
24
- "snötäcke_djup_2",
25
- "snötäcke_djup_sym_0",
26
- "snötäcke_djup_sym_1",
27
- "obs_0",
28
- "obs_1",
29
- "kl_07_0",
30
- "kl_07_1",
31
- "kl_07_2",
32
- "kl_07_3",
33
- "kl_13_0",
34
- "kl_13_1",
35
- "kl_13_2",
36
- "kl_13_3",
37
- "kl_19_0",
38
- "kl_19_1",
39
- "kl_19_2",
40
- "kl_19_3",
41
- "max_temp_0",
42
- "max_temp_1",
43
- "max_temp_2",
44
- "max_temp_3",
45
- "min_temp_0",
46
- "min_temp_1",
47
- "min_temp_2",
48
- "min_temp_3"
49
- ]
50
- name_idx = "datum"
51
- rows = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
52
- tables = [
53
- [32, 38]
54
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
table_formats/vinga.toml DELETED
@@ -1,173 +0,0 @@
1
- [1925]
2
- version = 1
3
-
4
- [1926]
5
- version = 1
6
-
7
- [1927]
8
- version = 1
9
-
10
- [1928]
11
- version = 1
12
-
13
- [1929]
14
- version = 1
15
-
16
- [1938-0]
17
- version = 0
18
-
19
- [1938-1]
20
- version = 2
21
-
22
- [1939]
23
- version = 2
24
-
25
- [1940]
26
- version = 2
27
-
28
- [1941]
29
- version = 2
30
-
31
- [1942]
32
- version = 3
33
-
34
- [default]
35
- version = 0
36
-
37
- [version.0]
38
- columns = [
39
- [
40
- "term_på_baro",
41
- "barom",
42
- "torra_term",
43
- "våta_term",
44
- "moln_slag_lägre",
45
- "moln_mängd_lägre",
46
- "moln_slag_medel",
47
- "moln_slag_högre"
48
- ],
49
- [
50
- "moln_het_sol_dimma_nederbörd_total",
51
- "vind_riktning",
52
- "vind_beaufort",
53
- "vind_m_sek",
54
- "sikt",
55
- "sjögang",
56
- "maximi_term",
57
- "minimi_term",
58
- "nederbörd_mängd",
59
- "nederbörd_slag"
60
- ]
61
- ]
62
- name_idx = "tid"
63
- rows = [2, 8, 14, 19, 21]
64
- tables = [
65
- [5, 8],
66
- [5, 10],
67
- [3, 1],
68
- [4, 2],
69
- [4, 5]
70
- ]
71
-
72
- [version.1]
73
- columns = [
74
- [
75
- "term_på_baro",
76
- "barom",
77
- "torra_term",
78
- "våta_term",
79
- "moln_slag_lägre",
80
- "moln_mängd_lägre",
81
- "moln_slag_högre",
82
- "moln_mängd_total"
83
- ],
84
- [
85
- "vind_riktning",
86
- "vind_beaufort",
87
- "vind_m_sek",
88
- "sikt",
89
- "sjögang",
90
- "maximi_term",
91
- "minimi_term",
92
- "nederbörd_mängd",
93
- "nederbörd_slag"
94
- ]
95
- ]
96
- rows = [2, 8, 14, 19, 21]
97
- tables = [
98
- [5, 8],
99
- [5, 9],
100
- [3, 1],
101
- [4, 2],
102
- [4, 5]
103
- ]
104
-
105
- [version.2]
106
- columns = [
107
- [
108
- "term_på_baro",
109
- "barom",
110
- "torra_term",
111
- "våta_term",
112
- "moln_slag_lägre",
113
- "moln_mängd_lägre",
114
- "moln_slag_medel",
115
- "moln_slag_högre",
116
- "moln_höjd"
117
- ],
118
- [
119
- "moln_het_sol_dimma_nederbörd_total",
120
- "vind_riktning",
121
- "vind_beaufort",
122
- "vind_m_sek",
123
- "sikt",
124
- "sjögang",
125
- "maximi_term",
126
- "minimi_term",
127
- "nederbörd_mängd",
128
- "nederbörd_slag"
129
- ]
130
- ]
131
- rows = [2, 5, 8, 11, 14, 17, 19, 21]
132
- tables = [
133
- [8, 9],
134
- [8, 10],
135
- [3, 1],
136
- [7, 3],
137
- [7, 5]
138
- ]
139
-
140
- [version.3]
141
- columns = [
142
- [
143
- "term_på_baro",
144
- "barom",
145
- "torra_term",
146
- "våta_term",
147
- "maximi_term",
148
- "minimi_term",
149
- "vind_riktning",
150
- "vind_beaufort",
151
- "vind_m_sek"
152
- ],
153
- [
154
- "moln_slag_lägre",
155
- "moln_mängd_lägre",
156
- "moln_slag_medel",
157
- "moln_slag_högre",
158
- "moln_höjd",
159
- "moln_het_sol_dimma_nederbörd_total",
160
- "sikt",
161
- "sjögang",
162
- "nederbörd_mängd",
163
- "nederbörd_slag"
164
- ]
165
- ]
166
- rows = [2, 5, 8, 11, 14, 17, 19, 23]
167
- tables = [
168
- [8, 9],
169
- [8, 10],
170
- [3, 1],
171
- [7, 3],
172
- [7, 5]
173
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
table_formats/visby.toml DELETED
@@ -1,165 +0,0 @@
1
- [1925]
2
- version = 1
3
-
4
- [1926]
5
- version = 1
6
-
7
- [1927]
8
- version = 1
9
-
10
- [1928]
11
- version = 1
12
-
13
- [1929]
14
- version = 1
15
-
16
- [1938-0]
17
- # Jan-Jun
18
- version = 0
19
-
20
- [1938-1]
21
- # Jul-Dec
22
- version = 2
23
-
24
- [1948]
25
- version = 4
26
-
27
- [default]
28
- version = 0
29
-
30
- [version.0]
31
- columns = [
32
- [
33
- "term_på_baro",
34
- "barom",
35
- "torra_term",
36
- "våta_term",
37
- "moln_slag_lägre",
38
- "moln_mängd_lägre",
39
- "moln_slag_medel",
40
- "moln_slag_högre"
41
- ],
42
- [
43
- "moln_het_sol_dimma_nederbörd_total",
44
- "vind_riktning",
45
- "vind_beaufort",
46
- "vind_m_sek",
47
- "sikt",
48
- "sjögang",
49
- "maximi_term",
50
- "minimi_term",
51
- "nederbörd_mängd",
52
- "nederbörd_slag"
53
- ]
54
- ]
55
- name_idx = "tid"
56
- rows = [2, 8, 14, 19, 21]
57
- tables = [
58
- [5, 8],
59
- [5, 10],
60
- [3, 1],
61
- [4, 2],
62
- [4, 5]
63
- ]
64
-
65
- [version.1]
66
- columns = [
67
- [
68
- "term_på_baro",
69
- "barom",
70
- "torra_term",
71
- "våta_term",
72
- "moln_slag_lägre",
73
- "moln_mängd_lägre",
74
- "moln_slag_högre",
75
- "moln_mängd_total"
76
- ],
77
- [
78
- "vind_riktning",
79
- "vind_beaufort",
80
- "vind_m_sek",
81
- "sikt",
82
- "sjögang",
83
- "maximi_term",
84
- "minimi_term",
85
- "nederbörd_mängd",
86
- "nederbörd_slag"
87
- ]
88
- ]
89
- rows = [2, 8, 14, 19, 21]
90
- tables = [
91
- [5, 8],
92
- [5, 9],
93
- [3, 1],
94
- [4, 2],
95
- [4, 5]
96
- ]
97
-
98
- [version.2]
99
- columns = [
100
- [
101
- "term_på_baro",
102
- "barom",
103
- "torra_term",
104
- "våta_term",
105
- "moln_slag_lägre",
106
- "moln_mängd_lägre",
107
- "moln_slag_medel",
108
- "moln_slag_högre",
109
- "moln_höjd"
110
- ],
111
- [
112
- "moln_het_sol_dimma_nederbörd_total",
113
- "vind_riktning",
114
- "vind_beaufort",
115
- "vind_m_sek",
116
- "sikt",
117
- "sjögang",
118
- "maximi_term",
119
- "minimi_term",
120
- "nederbörd_mängd",
121
- "nederbörd_slag"
122
- ]
123
- ]
124
- rows = [2, 5, 8, 11, 14, 17, 19, 21]
125
- tables = [
126
- [8, 9],
127
- [8, 10],
128
- [3, 1],
129
- [7, 2],
130
- [7, 5]
131
- ]
132
-
133
- [version.4]
134
- columns = [
135
- [
136
- "term_på_baro",
137
- "barom",
138
- "torra_term",
139
- "våta_term",
140
- "maximi_term",
141
- "minimi_term",
142
- "vind_riktning",
143
- "vind_beaufort",
144
- "vind_m_sek"
145
- ],
146
- [
147
- "moln_slag_lägre",
148
- "moln_mängd_lägre",
149
- "moln_slag_medel",
150
- "moln_slag_högre",
151
- "moln_höjd",
152
- "moln_het_sol_dimma_nederbörd_total",
153
- "sikt",
154
- "sjögang",
155
- "nederbörd_mängd",
156
- "nederbörd_slag"
157
- ]
158
- ]
159
- rows = [1, 4, 7, 10, 13, 16, 19, 22]
160
- tables = [
161
- [8, 9],
162
- [8, 10],
163
- [8, 5],
164
- [8, 5]
165
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
table_formats/öland.toml DELETED
@@ -1,128 +0,0 @@
1
- [1926]
2
- version = 1
3
-
4
- [1927]
5
- version = 1
6
-
7
- [1928]
8
- version = 1
9
-
10
- [1929]
11
- version = 1
12
-
13
- [1938-0]
14
- # Jan-Jun
15
- version = 0
16
-
17
- [1938-1]
18
- # Jul-Dec
19
- version = 2
20
-
21
- [default]
22
- version = 0
23
-
24
- [default.transforms]
25
- rotate = 90
26
-
27
- [version.0]
28
- columns = [
29
- [
30
- "term_på_baro",
31
- "barom",
32
- "torra_term",
33
- "våta_term",
34
- "moln_slag_lägre",
35
- "moln_mängd_lägre",
36
- "moln_slag_medel",
37
- "moln_slag_högre"
38
- ],
39
- [
40
- "moln_het_sol_dimma_nederbörd_total",
41
- "vind_riktning",
42
- "vind_beaufort",
43
- "vind_m_sek",
44
- "sikt",
45
- "sjögang",
46
- "maximi_term",
47
- "minimi_term",
48
- "nederbörd_mängd",
49
- "nederbörd_slag"
50
- ]
51
- ]
52
- name_idx = "tid"
53
- rows = [2, 8, 14, 19, 21]
54
- tables = [
55
- [5, 8],
56
- [5, 10],
57
- [3, 1],
58
- [4, 2],
59
- [4, 5]
60
- ]
61
-
62
- [version.1]
63
- columns = [
64
- [
65
- "term_på_baro",
66
- "barom",
67
- "torra_term",
68
- "våta_term",
69
- "moln_slag_lägre",
70
- "moln_mängd_lägre",
71
- "moln_slag_högre",
72
- "moln_mängd_total"
73
- ],
74
- [
75
- "vind_riktning",
76
- "vind_beaufort",
77
- "vind_m_sek",
78
- "sikt",
79
- "sjögang",
80
- "maximi_term",
81
- "minimi_term",
82
- "nederbörd_mängd",
83
- "nederbörd_slag"
84
- ]
85
- ]
86
- rows = [2, 8, 14, 19, 21]
87
- tables = [
88
- [5, 8],
89
- [5, 9],
90
- [3, 1],
91
- [4, 2],
92
- [4, 5]
93
- ]
94
-
95
- [version.2]
96
- columns = [
97
- [
98
- "term_på_baro",
99
- "barom",
100
- "torra_term",
101
- "våta_term",
102
- "moln_slag_lägre",
103
- "moln_mängd_lägre",
104
- "moln_slag_medel",
105
- "moln_slag_högre",
106
- "moln_höjd"
107
- ],
108
- [
109
- "moln_het_sol_dimma_nederbörd_total",
110
- "vind_riktning",
111
- "vind_beaufort",
112
- "vind_m_sek",
113
- "sikt",
114
- "sjögang",
115
- "maximi_term",
116
- "minimi_term",
117
- "nederbörd_mängd",
118
- "nederbörd_slag"
119
- ]
120
- ]
121
- rows = [2, 5, 8, 11, 14, 17, 19, 21]
122
- tables = [
123
- [8, 9],
124
- [8, 10],
125
- [3, 1],
126
- [7, 3],
127
- [7, 5]
128
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_submit_functions.py DELETED
@@ -1,36 +0,0 @@
1
- def test_make_cell():
2
- """
3
- Test the make_cell function.
4
- """
5
- import numpy as np
6
- from app.tabs.submit_functions import make_cell
7
-
8
- # Arrange
9
- value = "Hello, World!"
10
- bbox = [10, 20, 30, 40]
11
-
12
- # Act
13
- cell = make_cell(value, bbox)
14
-
15
- # Assert
16
- expected_polygon = ((0, -5), (40, -5), (40, 25), (0, 25), (0, -5))
17
- assert np.array_equal(cell.polygon, expected_polygon)
18
-
19
-
20
- def test_make_cell_for_text_position():
21
- """
22
- Test the make_cell function for text position.
23
- """
24
- import numpy as np
25
- from app.tabs.submit_functions import make_cell
26
-
27
- # Arrange
28
- value = "Hello, World!"
29
- bbox = [10, 20, 30, 40]
30
-
31
- # Act
32
- cell = make_cell(value, bbox)
33
-
34
- # Assert
35
- text_position = 10, 10
36
- assert (cell.text_x, cell.text_y) == text_position
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
uv.lock CHANGED
The diff for this file is too large to render. See raw diff