Spaces:
Running
Running
File size: 37,053 Bytes
e95961a cd8a388 e95961a 2e00505 1cc581b e95961a 1cc581b e95961a 1cc581b e95961a 1cc581b e95961a cb955d0 1cc581b e95961a 1cc581b e95961a 1cc581b e95961a 731dce5 e95961a 2e00505 cd8a388 e95961a 731dce5 cb955d0 731dce5 e95961a cb955d0 e95961a 2e00505 cd8a388 cb955d0 cd8a388 cb955d0 e95961a 731dce5 e95961a 731dce5 cb955d0 731dce5 e95961a cb955d0 e95961a 1cc581b cb955d0 d2e30a1 e95961a 2e00505 e95961a 2e00505 e95961a 2e00505 e95961a 2e00505 e95961a 2e00505 e95961a 2e00505 e95961a 2c16fe6 e95961a cafd450 e95961a cafd450 e95961a cafd450 e95961a 2c16fe6 e95961a 8dbb4c9 e95961a 2e00505 e95961a 2e00505 e95961a 21d0900 2e00505 21d0900 2e00505 21d0900 e95961a 9511015 864b2fc 9511015 e95961a cb955d0 e95961a cb955d0 e95961a cb955d0 4683d94 cb955d0 e95961a 047434d 2e00505 047434d 2e00505 047434d e95961a 047434d e95961a 047434d e95961a 047434d 2e00505 e95961a 047434d e95961a 047434d 2e00505 047434d e95961a 047434d e95961a 047434d e95961a 047434d e95961a 047434d e95961a 9511015 e95961a 9511015 14db0f1 1cc581b 14db0f1 9511015 e95961a 047434d e95961a 047434d e95961a 047434d e95961a 047434d e95961a 047434d 14db0f1 047434d 1cc581b 047434d 14db0f1 047434d 7f87f17 047434d e95961a 047434d e95961a 047434d e95961a 047434d e95961a 2e00505 e95961a 047434d e95961a 047434d 2e00505 047434d e95961a 047434d e95961a 047434d e95961a 047434d e95961a 047434d e95961a 047434d e95961a 047434d e95961a cb955d0 fb91a82 e95961a |
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 |
import streamlit as st
import spacy
from spacy import displacy
import pandas as pd
from io import StringIO, BytesIO
from lxml import etree
from bs4 import BeautifulSoup
import html
from streamlit_extras.stylable_container import stylable_container
import subprocess
import importlib.util
# This app was inspired by Lucas Terriel's NER4Archives Visualizer App (2022-2023), https://huggingface.co/spaces/ner4archives/ner4archives-NEL-vizualizer-app/tree/main
# Check out the NER4Archives project (INRIA-ALMAnaCH/Archives nationales) : https://github.com/NER4Archives-project
# ===== SOME SETTING UP =====
# Setting up the app's page
st.set_page_config(page_title="Arches Demo", page_icon="🏺")
# Path to the statics directory
statics = "./static"
# Making the radio widgets' titles bigger
# Source : arnaud, https://discuss.streamlit.io/t/how-to-change-font-size-of-streamlit-radio-widget-title/35945/2
st.markdown(
"""<style>
div[class*="stRadio"] > label > div[data-testid="stMarkdownContainer"] > p {
font-size: 17px;
}
</style>
""", unsafe_allow_html=True)
# Hiding the possibility to display pictures fullscreen
# Source : AvratanuBiswas, https://discuss.streamlit.io/t/hide-fullscreen-option-when-displaying-images-using-st-image/19792/2
st.markdown(
"""<style>
button[title="View fullscreen"]{
visibility: hidden;
}
</style>
""", unsafe_allow_html=True)
# Setting up the colors of the entity tags for displacy
ENTITIES_COLORS = {
"CHRONOLOGIE": "#ffb627",
"MOBILIER": "#6b7fd7",
"MATERIAU": "#d36582",
"STRUCTURE": "#00b2ca",
"TECHNIQUE_STYLE": "#ED6A5A",
"ESPECE": "#96C7FF",
"EDIFICE": "#9F86C0",
"ID": "#f65bff",
"LIEUDIT_SITE": "#d8e446",
"PERSONNE": "#D3B4B4",
"PEUPLE_CULTURE": "#d20000",
"LOC": "#81db72",
"DECOR": "#fff46a",
"ORG": "#887575",
"GPE": "#00a878"
}
OPTIONS = {
"ents":
[
"CHRONOLOGIE",
"MOBILIER",
"MATERIAU",
"STRUCTURE",
"TECHNIQUE_STYLE",
"ESPECE",
"EDIFICE",
"ID",
"LIEUDIT_SITE",
"PERSONNE",
"PEUPLE_CULTURE",
"LOC",
"DECOR",
"ORG",
"GPE"
],
"colors": ENTITIES_COLORS}
# ===== SIDEBAR =====
st.sidebar.title("ARCHES - Étude, composition et processus pour une édition structurée des rapports d’opérations archéologiques préventives")
st.sidebar.markdown("Avec ses 2200 collaborateurs, l’[Inrap](https://www.inrap.fr/) représente la plus importante structure publique de recherche archéologique française. De fait, chaque année, près de 2000 chantiers (diagnostics archéologiques et fouilles) sont réalisés en partenariat avec les aménageurs publics et privés, en France métropolitaine et dans les départements d’outre-mer. Les missions de l’Institut intégrant l’exploitation scientifique des résultats et la diffusion de la connaissance archéologique auprès du public, plus de 2000 rapports d’opération archéologique sont ainsi rédigés annuellement.")
st.sidebar.markdown("Financé avec le soutien du [Fonds National pour la Science Ouverte](https://www.ouvrirlascience.fr/accueil/) et réalisé en collaboration avec l’infrastructure de recherche [Métopes](http://www.metopes.fr/) ([Université de Caen Normandie](https://www.unicaen.fr/) - [CNRS](https://www.cnrs.fr/fr)), [ARCHES](https://www.inrap.fr/arches-etude-composition-et-processus-pour-une-edition-structuree-des-rapports-d-17145) vise à explorer l’amélioration de la diffusion et de l’exploitation des rapports d’opération à l’aide du format de balisage XML-TEI, permettant d’encoder tant la structuration formelle que le contenu sémantique d’un document. Dans cette optique, vingt-et-un rapports de fouilles de l’Inrap ont été annotés pour entraîner un modèle de reconnaissance des entités nommées (représentant plus de 80 000 entités annotées). Cette application vise à tester la manipulation du modèle, tant avec des fichiers XML que texte brut.")
st.sidebar.markdown("Le corpus a été annoté à l'aide d'[INCEpTION](https://inception-project.github.io/), tandis que les modèles de [segmentation](https://huggingface.co/a-menu/fr_arches_sentencizer) et de reconnaissance des entités nommées ([avec](https://huggingface.co/a-menu/fr_arches_ner_trf) et [sans](https://huggingface.co/a-menu/fr_arches_ner) architecture transformer) ont été entraînés et évalués avec [spaCy](https://spacy.io/). Les modalités de [citation](https://huggingface.co/spaces/a-menu/arches_demo/blob/main/CITATION.cff) de l'application peuvent être retrouvées dans le [dépôt](https://huggingface.co/spaces/a-menu/arches_demo/tree/main) de celle-ci.")
st.sidebar.write("")
st.sidebar.markdown("*ARCHES (Inrap), janvier 2024*")
st.sidebar.write("")
st.sidebar.write("")
st.sidebar.header("Partenaires")
st.sidebar.write("")
# Display logos
col1, col2, col3 = st.sidebar.columns(3)
col1.image(f"{statics}/logo_inrap.png", width=100)
col2.write("")
col2.image(f"{statics}/logo_ouvrir_la_science.png", width=100)
col3.image(f"{statics}/logo_mesr.png", width=100)
col1.image(f"{statics}/logo_ir_metopes.png", width=100)
col2.write("")
col2.write("")
col2.image(f"{statics}/logo_mrsh.jpg", width=100)
col3.image(f"{statics}/logo_unicaen.png", width=100)
col1.image(f"{statics}/logo_cnrs.png", width=80)
# ===== SOME FUNCTIONS =====
# Cached to prevent computation on every rerun
@st.cache_resource
def download_sentencizer():
"""
Downloads the fr_arches_sentencizer model.
:returns: None
"""
# Check if the model is already installed
# If not, install it
# Source : ice.nicer & Arthur, https://stackoverflow.com/a/41815890
check_senter = importlib.util.find_spec("fr_arches_sentencizer")
if check_senter is None:
subprocess.run(["pip", "install", "https://huggingface.co/a-menu/fr_arches_sentencizer/resolve/main/fr_arches_sentencizer-any-py3-none-any.whl"])
# Cached to prevent computation on every rerun
@st.cache_resource
def download_ner_trf():
"""
Downloads the fr_arches_ner_trf TRF NER model.
:returns: None
"""
# Check if the model is already installed
# If not, install it
# Source : ice.nicer & Arthur, https://stackoverflow.com/a/41815890
check_ner_trf = importlib.util.find_spec("fr_arches_ner_trf")
if check_ner_trf is None:
subprocess.run(["pip", "install", "https://huggingface.co/a-menu/fr_arches_ner_trf/resolve/main/fr_arches_ner_trf-any-py3-none-any.whl"])
# Cached to prevent computation on every rerun
@st.cache_resource
def download_ner():
"""
Downloads the fr_arches_ner NER model.
:returns: None
"""
# Check if the model is already installed
# If not, install it
# Source : ice.nicer & Arthur, https://stackoverflow.com/a/41815890
check_ner = importlib.util.find_spec("fr_arches_ner")
if check_ner is None:
subprocess.run(["pip", "install", "https://huggingface.co/a-menu/fr_arches_ner/resolve/main/fr_arches_ner-any-py3-none-any.whl"])
# Cached to prevent computation on every rerun
@st.cache_resource
def load_sentencizer():
"""
Loads our custom sentence segmentation model.
:returns: loaded fr_arches_sentencizer model
:rtype: spacy.lang.fr.French
"""
senter = spacy.load("fr_arches_sentencizer")
return senter
# Cached to prevent computation on every rerun
@st.cache_resource
def load_ner_trf():
"""
Loads our custom fr_arches_ner_trf trf ner model.
:returns: loaded fr_arches model
:rtype: spacy.lang.fr.French
"""
ner = spacy.load("fr_arches_ner_trf")
# To try to reduce memory usage
config = {"attrs": {"tensor": None}}
ner.add_pipe("doc_cleaner", config=config)
return ner
# Cached to prevent computation on every rerun
@st.cache_resource
def load_ner():
"""
Loads our custom fr_arches_ner ner model.
:returns: loaded fr_arches model
:rtype: spacy.lang.fr.French
"""
ner = spacy.load("fr_arches_ner")
# To try to reduce memory usage
config = {"attrs": {"tensor": None}}
ner.add_pipe("doc_cleaner", config=config)
return ner
def apply_senter(senter, data):
"""
Applies our custom sentence segmentation model on data.
:param senter: sentence segmentation model
:type senter: spacy.lang.fr.French
:param data: text to be segmented
:type data: str
:returns: sentencized text
:rtype: str
"""
mes_phrases = senter(data)
sentencized_text = ""
for sent in mes_phrases.sents:
sentencized_text += str(sent) + "\n"
return sentencized_text
def get_doc(ner, data):
"""
Applies our custom ner model on data.
:param ner: ner model
:type ner: spacy.lang.fr.French
:param data: text to be analyzed
:type data: str
:returns: spacy doc
:rtype: spacy.tokens.doc.Doc
"""
# Replace the non-breaking spaces (NBSP) with regular spaces before applying our model on the text. To do so:
# Create a list to store their position
list_nbsp = []
# Iterate over each character and save the position of the non-breaking spaces
for i, char in enumerate(data):
if char == "\u00A0":
list_nbsp.append(i)
# Once we have memorized the NBSP's positions, we replace them with regular spaces
data = data.replace("\u00A0", " ")
# Apply the NER model on our data
doc = ner(data)
return doc, list_nbsp
def get_entities(doc, list_nbsp):
"""
Extracts the named entities from the doc.
:param doc: spacy doc
:type doc: spacy.tokens.doc.Doc
:returns: list of named entities
:rtype: list
"""
# Put back the NBSP
characters_with_nbsp = [char if i not in list_nbsp else "\u00A0" for i, char in enumerate(doc.text)]
# Convert the list back to a string
nbsp_text = "".join(characters_with_nbsp)
entities = []
for ent in doc.ents:
# We collect :
# The named entity (using its position since the tokenizer would sometimes add unwanted spaces, for instance before a comma)
# Its label
# Its position
entities.append((nbsp_text[ent.start_char:ent.end_char].strip(), ent.label_, ent.start_char, ent.end_char))
return entities, nbsp_text
def create_displacy(text, entities):
"""
Render named entities using displacy.
:param text: input text
:type text: str
:param entities: list of named entities with start and end character positions
:type entities: list
:returns: showcase of entities with displacy
:rtype: str
"""
# Prepare data for displacy
entity_data = [{"start": ent[2], "end": ent[3], "label": ent[1]} for ent in entities]
# Render using displacy
my_displacy = displacy.render([{"text": text, "ents": entity_data}], style="ent", options=OPTIONS, manual=True)
return my_displacy
def create_df(entities):
"""
Creates a dataframe to display the named entities found in text.
:param entities: named entities
:type entities: list
:returns: dataframe
:rtype: pd.DataFrame
"""
df = pd.DataFrame(entities, columns=["ENTITE",
"LABEL",
"DEBUT",
"FIN"
])
return df
def df_to_csv(df_to_convert):
"""
Converts df to csv.
:param df_to_convert: dataframe to be converted to csv
:type df_to_convert: pd.DataFrame
:returns: csv
:rtype: csv
"""
return df_to_convert.to_csv(encoding="utf-8")
def doc_to_conll(doc, updated_name=False):
"""
Converts a doc and its entities to a conll2002 file.
:param doc: spacy doc
:type doc: spacy.tokens.doc.Doc
:param updated_name: should the name of the downloaded file be updated?
:type updated_name: bool
:returns: button to download the conll2002 file
:rtype: streamlit.components.v1.components.download_button.DownloadButtonMixin
"""
# Writing to a BytesIO object to get the byte content
with BytesIO() as sortie_buffer:
for tok in doc:
# Convert a named entity to conll2002
if tok.ent_type and tok.text != "\n":
sortie_buffer.write(f"{tok.text} {tok.ent_iob_}-{tok.ent_type_}\n".encode("utf-8"))
# Convert a token without a named entity to conll2002
else:
if tok.text != "\n" and tok.ent_iob_:
sortie_buffer.write(f"{tok.text} {tok.ent_iob_}\n".encode("utf-8"))
# Write a single empty line for each new line in the original text
else:
sortie_buffer.write(b"\n")
# Move the buffer position to the beginning for reading
sortie_buffer.seek(0)
# Check if the buffer has a line only consisting of "O\n" and delete it
buffer_content = sortie_buffer.getvalue().decode("utf-8")
lines = buffer_content.split("\n")
modified_lines = [line for line in lines if line.strip() != "O"]
modified_buffer_content = "\n".join(modified_lines)
# Write the modified content back to the buffer
sortie_buffer.seek(0)
sortie_buffer.write(modified_buffer_content.encode("utf-8"))
# Move the buffer position to the beginning for reading
sortie_buffer.seek(0)
# If we have an uploaded file: update the name of the exported file.
if updated_name:
my_button = st.download_button(
label="Télécharger le fichier CoNLL2002",
data=sortie_buffer,
file_name=updated_name + ".conll"
)
# If we have no uploaded file ('example on the go' mode): use a default name for the exported file.
else:
my_button = st.download_button(
label="Télécharger le fichier CoNLL2002",
data=sortie_buffer,
file_name="prediction_arches.conll"
)
return my_button
def get_body_text(xml_input):
"""
Parses an xml file and returns its <body>.
:param xml_input: xml file to be parsed
:type xml_input: str
:returns: the <body> if successful, None otherwise
:rtype: str or None
"""
try:
# Parse XML content
parser = etree.XMLParser(recover=True)
root = etree.fromstring(xml_input, parser=parser)
# Find <body> element in the XML namespace
body = root.xpath("//tei:body", namespaces={"tei": "http://www.tei-c.org/ns/1.0"})
if body:
body_element = body[0]
if len(body_element) > 0:
# Extract the text content
body_soup = BeautifulSoup(etree.tostring(body_element), "html.parser")
body_text = body_soup.get_text(separator=" ", strip=True)
return body_text
else:
st.warning("L'élément <body> est vide.")
return None
else:
st.warning("Aucun élément <body> n'a été détecté dans le fichier XML.")
return None
except etree.XMLSyntaxError:
st.warning("Format XML incorrect. Veuillez importer un fichier XML valide.")
return None
def xml_mapping(entity, label):
"""
Create an XML element based on an entity's given label.
:param entity: entity text
:type entity: str
:param label: entity label
:type label: str
:returns: custom XML element if successful, default <name> element if not
:rtype: etree.Element
"""
element_mapping = {
"CHRONOLOGIE": {"tag": "date"},
"DECOR": {"tag": "name", "attrib": {"type": "decor"}},
"EDIFICE": {"tag": "placeName", "attrib": {"type": "edifice"}},
"ESPECE": {"tag": "name", "attrib": {"type": "espece"}},
"GPE": {"tag": "placeName"},
"ID": {"tag": "idno", "attrib": {"type": "entite"}},
"LIEUDIT_SITE": {"tag": "placeName", "attrib": {"type": "lieudit_site"}},
"LOC": {"tag": "geogName"},
"MATERIAU": {"tag": "material"},
"MOBILIER": {"tag": "objectType"},
"ORG": {"tag": "orgName"},
"PERSONNE": {"tag": "persName"},
"PEUPLE_CULTURE": {"tag": "orgName", "attrib": {"type": "peuple_culture"}},
"STRUCTURE": {"tag": "name", "attrib": {"type": "structure"}},
"TECHNIQUE_STYLE": {"tag": "name", "attrib": {"type": "technique_style"}},
}
mapping = element_mapping.get(label)
if mapping:
xml_tag = etree.Element(mapping["tag"], attrib=mapping.get("attrib", {}))
xml_tag.text = entity
return xml_tag
# If the mapping is impossible, encode the entity with a default <name type="generique">
else:
st.warning(f"Mapping introuvable pour le label : {label}. Entité encodée par conséquence comme : <name type=\"generique\">.")
return etree.Element("name", attrib={"type": "generique"})
def entities_to_xml(xml_content, ner):
"""
Process XML content by replacing identified entities with XML elements.
:param xml_content: original xml content
:type xml_content: str
:param ner: ner model
:type ner: spacy.lang.fr.French
:returns: modified XML content if successful, None otherwise
:rtype: str or None
"""
try:
# Parse XML content
parser = etree.XMLParser(recover=True)
root = etree.fromstring(xml_content, parser=parser)
# Find <body> element in the XML namespace
body = root.xpath("//tei:body", namespaces={"tei": "http://www.tei-c.org/ns/1.0"})
if body:
body_element = body[0]
if len(body_element) > 0:
# Strip the <body> of the <hi> tags
etree.strip_tags(body_element, "{http://www.tei-c.org/ns/1.0}hi")
# Get the <body>'s descendants
descendants = body_element.xpath("descendant::*")
# Iterate through all descendants in the <body>
for descendant in descendants:
# Apply the ner model on the text of the descendant
if descendant.text:
doc = get_doc(ner, descendant.text)[0]
list_nbsp = get_doc(ner, descendant.text)[1]
entities = get_entities(doc, list_nbsp)[0]
entities.sort(key=lambda ent: ent[2], reverse=True)
for ent in entities:
xml_tag = xml_mapping(ent[0], ent[1])
start_index = ent[2]
end_index = ent[3]
descendant.text = (
descendant.text[:start_index]
+ etree.tostring(xml_tag, encoding="unicode")
+ descendant.text[end_index:]
)
# Apply the ner model on the tail of the descendant
if descendant.tail:
doc_tail = get_doc(ner, descendant.tail)[0]
list_nbsp_tail = get_doc(ner, descendant.tail)[1]
entities_tail = get_entities(doc_tail, list_nbsp_tail)[0]
entities_tail.sort(key=lambda ent: ent[2], reverse=True)
for ent_tail in entities_tail:
xml_tag_tail = xml_mapping(ent_tail[0], ent_tail[1])
start_index_tail = ent_tail[2]
end_index_tail = ent_tail[3]
descendant.tail = (
descendant.tail[:start_index_tail]
+ etree.tostring(xml_tag_tail, encoding="unicode")
+ descendant.tail[end_index_tail:]
)
# Export modified XML content
modified_xml = etree.tostring(root, xml_declaration=True, pretty_print=True, encoding="utf-8").decode("utf-8")
return modified_xml
else:
st.warning("L'élément <body> est vide.")
return None
else:
st.warning("Aucun élément <body> n'a été détecté dans le fichier XML.")
return None
except etree.XMLSyntaxError:
st.error("Format XML incorrect. Veuillez importer un fichier XML valide.")
return None
# ===== BODY OF THE PAGE =====
st.title("La reconnaissance des entités nommées dans le projet ARCHES")
st.header("Visualisation & extraction")
st.write("")
st.write("")
# Check GPU presence
gpu = spacy.prefer_gpu()
if gpu:
st.success("GPU détecté avec succès")
else:
st.warning("Aucun GPU détecté, l'application du modèle pourra nécessiter un certain temps. Considérez une [installation locale du dépôt](https://huggingface.co/spaces/a-menu/arches_demo/blob/main/guide_installation_locale_et_gpu.md) si besoin.")
st.write("")
st.write("")
def main():
# Download and load our models
# Sentencizer
download_sentencizer()
senter = load_sentencizer()
# NER
# Choose which NER model you want
pick_model = st.radio("Quel modèle de reconnaissance d'entités nommées souhaitez-vous utiliser ?", ("fr_arches_ner (plus léger en ressources mais moins efficace)", "fr_arches_ner_trf (plus lourd en ressources mais plus efficace, GPU conseillé)"))
st.write("")
st.write("")
if pick_model == "fr_arches_ner (plus léger en ressources mais moins efficace)":
download_ner()
ner = load_ner()
if pick_model == "fr_arches_ner_trf (plus lourd en ressources mais plus efficace, GPU conseillé)":
download_ner_trf()
ner = load_ner_trf()
with st.expander("Au sujet des entités nommées recherchées"):
st.markdown("**Les différents types d'entités sont :** \n\n- **CHRONOLOGIE :** utilisé pour les références chronologiques (\"Antiquité\", \"XIIe siècle\", \"200 av. n. ère\", etc.). \n- **MOBILIER :** utilisé pour le mobilier (\"os\", \"pot\", \"tuile\", etc.). \n- **STRUCTURE :** utilisé pour les structures archéologiques (\"fosse\", \"mur\", \"fossé\", \"foyer\", etc.). \n- **MATERIAU :** utilisé pour les matériaux (\"bronze\", \"dolérite\", \"terre cuite\", etc.). \n- **ID :** utilisé pour les identifiants de vestiges (\"4\" pour \"le fossé 4\" par exemple). \n- **TECHNIQUE_STYLE :** utilisé pour les mentions de techniques et styles de fabrication ou construction (\"taillé\", \"glaçuré\", \"en petit appareil\", etc.). \n- **DECOR :** utilisé pour les éléments de décor. \n- **ESPECE :** utilisé pour signaler les taxons et noms vernaculaires rencontrés dans le texte. \n- **EDIFICE :** utilisé pour les édifices et monuments nommés (\"église Saint-Paul\", \"pont du Gard\", etc.). \n- **PEUPLE_CULTURE :** utilisé pour les cultures et peuples évoqués (tribus gauloises, cultures préhistoriques, etc.). \n- **PERSONNE :** utilisé pour les noms de personnes (historiques, fictives, équipe scientifique, etc.). \n- **ORG :** utilisé pour les institutions, sociétés, laboratoires, universités, musées, archives, etc. \n- **GPE :** utilisé pour les entités géopolitiques (villes, départements, États, etc.). \n- **LOC :** utilisé pour les lieux non-GPE (lieux naturels par exemple). \n- **LIEUDIT_SITE :** utilisé pour les lieux-dits et noms de sites archéologiques.")
st.write("")
# Select input type
use_type = st.radio("Veuillez choisir le type de données à analyser :", ("Taper un exemple", "Importer un fichier texte", "Importer un fichier xml-tei"))
st.write("")
# ===== MODE: EXAMPLE ON THE GO =====
if use_type == "Taper un exemple":
# Checkbox to apply our custom sentence segmentation model
bouton_phraseur = st.checkbox("Cochez cette case pour resegmenter les phrases de votre document selon notre modèle entraîné sur des rapports d'opération")
st.write("")
st.write("")
# Create a text area
raw_text = st.text_area("Veuillez saisir votre exemple dans le bloc ci-dessous (max. 5000 caractères)", "La fosse 34 a livré des restes de pinces en bronze et quelques grains d'orge.", max_chars=5000)
st.write("")
# Launch prediction
if st.button("Lancer la prédiction"):
if len(raw_text) > 0:
with st.spinner("Application du modèle.."):
# If requested, apply the sentence segmentation model
if bouton_phraseur:
raw_text = apply_senter(senter, raw_text)
# Apply the ner model
doc = get_doc(ner, raw_text)[0]
list_nbsp = get_doc(ner, raw_text)[1]
entities = get_entities(doc, list_nbsp)[0]
nbsp_text = get_entities(doc, list_nbsp)[1]
st.write("")
st.subheader("Résultats :")
st.write("")
st.write("")
# Display the entities with displacy
my_displacy = create_displacy(nbsp_text, entities)
st.markdown(my_displacy, unsafe_allow_html=True)
st.write("")
# Download results as a conll2002 file
doc_to_conll(doc)
st.write("")
df = create_df(entities)
st.write("")
# Display the entities as a table
st.markdown("**Tableau regroupant les entités détectées**")
st.write("")
st.dataframe(df, use_container_width=True)
csv = df_to_csv(df)
st.write("")
# Download results as a csv file
st.download_button(
label="Télécharger le fichier CSV",
data=csv,
file_name="prediction_arches.csv",
mime="text/csv",
)
else:
st.warning("Veuillez saisir un exemple.")
# ===== MODE: LOAD A PLAIN TEXT FILE =====
if use_type == "Importer un fichier texte":
# Checkbox to apply our custom sentence segmentation model
bouton_phraseur = st.checkbox("Cochez cette case pour resegmenter les phrases de votre document selon notre modèle entraîné sur des rapports d'opération")
st.write("")
st.write("")
# Upload a plain text file
uploaded_file = st.file_uploader("Importez un fichier texte (.txt)", type="txt")
if uploaded_file is not None:
# Collect the name of the uploaded file (for the future export)
updated_name = uploaded_file.name[:-4]
stringio = StringIO(uploaded_file.getvalue().decode("utf-8"))
file_contents = stringio.read()
# Launch prediction
if st.button("Lancer la prédiction"):
if len(file_contents) > 0:
with st.spinner("Application du modèle.."):
# If requested, apply the sentence segmentation model
if bouton_phraseur:
file_contents = apply_senter(senter, file_contents)
# Apply the ner model
doc = get_doc(ner, file_contents)[0]
list_nbsp = get_doc(ner, file_contents)[1]
entities = get_entities(doc, list_nbsp)[0]
nbsp_text = get_entities(doc, list_nbsp)[1]
st.write("")
st.subheader("Résultats :")
st.write("")
st.write("")
# Display the entities with displacy
with st.expander("Voir les entités dans le texte"):
my_displacy = create_displacy(nbsp_text, entities)
st.markdown(my_displacy, unsafe_allow_html=True)
st.write("")
# Download the results as a conll2002 file
doc_to_conll(doc, updated_name)
st.write("")
df = create_df(entities)
st.write("")
# Display the entities as a table
with st.expander("Voir les entités sous forme de tableau"):
st.write("")
st.dataframe(df, use_container_width=True)
csv = df_to_csv(df)
st.write("")
# Download the results as a csv file
st.download_button(
label="Télécharger le fichier CSV",
data=csv,
file_name=updated_name + ".csv",
mime="text/csv",
)
else:
st.warning("Le fichier importé est vide.")
# ===== MODE: LOAD AN XML FILE =====
if use_type == "Importer un fichier xml-tei":
# User chooses between xml or conll2002 & csv export
choix_xml = st.radio("Comment souhaitez vous appliquer le modèle sur le <body> ?", ("Conserver les balises (export xml de l'intégralité* du fichier importé)", "Ne pas conserver les balises (export conll2002 ou csv du <body> uniquement)"))
# ===== MODE: XML EXPORT =====
if choix_xml == "Conserver les balises (export xml de l'intégralité* du fichier importé)":
st.write("")
st.error("\* À l'exception des balises <hi> du body.")
st.write("")
with st.expander("Au sujet du mapping XML des entités nommées"):
st.markdown(
"**Les entités nommées ont été converties comme suit :** \n\n- **CHRONOLOGIE :** ```<date>``` \n- **MOBILIER :** ```<objectType>``` \n- **STRUCTURE :** ```<name type=\"structure\">``` \n- **MATERIAU :** ```<material>``` \n- **ID :** ```<idno type=\"entite\">``` \n- **TECHNIQUE_STYLE :** ```<name type=\"technique_style\">``` \n- **DECOR :** ```<name type=\"decor\">``` \n- **ESPECE :** ```<name type=\"espece\">``` \n- **EDIFICE :** ```<placeName type=\"edifice\">``` \n- **PEUPLE_CULTURE :** ```<orgName type=\"peuple_culture\">``` \n- **PERSONNE :** ```<persName>``` \n- **ORG :** ```<orgName>``` \n- **GPE :** ```<placeName>``` \n- **LOC :** ```<geogName>``` \n- **LIEUDIT_SITE :** ```<placeName type=\"lieudit_site\">```\n- **Entité inconnue :** ```<name type=\"generique\">```")
st.write("")
st.write("")
# Upload an xml file
uploaded_file = st.file_uploader("Importez un fichier XML (.xml)", type="xml")
if uploaded_file is not None:
# Collect the name of the uploaded file (for the export later)
updated_name = uploaded_file.name[:-4]
file_contents = uploaded_file.read()
# Launch prediction
if st.button("Lancer la prédiction"):
if len(file_contents) > 0:
with st.spinner("Application du modèle.."):
# Apply the ner model to an xml file
modified_xml = entities_to_xml(file_contents, ner)
if modified_xml is not None:
# Convert HTML entities back to characters
modified_xml = html.unescape(modified_xml)
st.write("")
st.subheader("Résultats :")
st.write("")
st.write("")
# Display the modified XML
with st.expander("Contenu XML modifié"):
# Wrap the code
# Source : blackary, https://discuss.streamlit.io/t/st-code-on-multiple-lines/50511/8
with stylable_container(
"codeblock",
"""
code {
white-space: pre-wrap !important;
}
""",
):
st.code(modified_xml, language="xml")
st.write("")
# Download the modified XML
st.download_button(
label="Télécharger le fichier xml modifié",
data=modified_xml,
file_name=updated_name + ".xml",
mime="xml",
)
# ===== MODE: CONLL2002 & CSV EXPORT =====
if choix_xml == "Ne pas conserver les balises (export conll2002 ou csv du <body> uniquement)":
st.write("")
# Checkbox to apply our custom sentence segmentation model
bouton_phraseur = st.checkbox(
"Cochez cette case pour resegmenter les phrases de votre document selon notre modèle entraîné sur des rapports d'opération")
st.write("")
st.write("")
# Upload an xml file
uploaded_file = st.file_uploader("Importez un fichier XML (.xml)", type="xml")
if uploaded_file is not None:
# Collect the name of the file (for the export later)
updated_name = uploaded_file.name[:-4]
file_contents = uploaded_file.read()
# Launch prediction
if st.button("Lancer la prédiction"):
if len(file_contents) > 0:
with st.spinner("Application du modèle.."):
st.write("")
# Strip the <body> of its tags
body_text = get_body_text(file_contents)
if body_text is not None:
# If requested, apply the sentence segmentation model
if bouton_phraseur:
body_text = apply_senter(senter, body_text)
# Apply the ner model
doc = get_doc(ner, body_text)[0]
list_nbsp = get_doc(ner, body_text)[1]
entities = get_entities(doc, list_nbsp)[0]
nbsp_text = get_entities(doc, list_nbsp)[1]
st.write("")
st.subheader("Résultats :")
st.write("")
st.write("")
# Display the entities with displacy
with st.expander("Voir les entités dans le texte"):
my_displacy = create_displacy(nbsp_text, entities)
st.markdown(my_displacy, unsafe_allow_html=True)
st.write("")
# Download the results as a conll2002 file
doc_to_conll(doc, updated_name)
st.write("")
df = create_df(entities)
st.write("")
# Display the entities as a table
with st.expander("Voir les entités sous forme de tableau"):
st.write("")
st.dataframe(df, use_container_width=True)
csv = df_to_csv(df)
st.write("")
# Download the results as a csv file
st.download_button(
label="Télécharger le fichier CSV",
data=csv,
file_name=updated_name + ".csv",
mime="text/csv",
)
# Add a "footer"
st.markdown("# ")
st.markdown("# ")
if __name__ == "__main__":
main()
|