pylelemmatize 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. pylelemmatize-0.1.0/PKG-INFO +107 -0
  2. pylelemmatize-0.1.0/README.md +77 -0
  3. pylelemmatize-0.1.0/pyproject.toml +3 -0
  4. pylelemmatize-0.1.0/setup.cfg +4 -0
  5. pylelemmatize-0.1.0/setup.py +48 -0
  6. pylelemmatize-0.1.0/src/pylelemmatize/__init__.py +52 -0
  7. pylelemmatize-0.1.0/src/pylelemmatize/abstract_mapper.py +345 -0
  8. pylelemmatize-0.1.0/src/pylelemmatize/all_charsets.py +282 -0
  9. pylelemmatize-0.1.0/src/pylelemmatize/char_distance.py +108 -0
  10. pylelemmatize-0.1.0/src/pylelemmatize/charset_ascii.py +21 -0
  11. pylelemmatize-0.1.0/src/pylelemmatize/charset_greek.py +133 -0
  12. pylelemmatize-0.1.0/src/pylelemmatize/charset_iso.py +43 -0
  13. pylelemmatize-0.1.0/src/pylelemmatize/charset_mes.py +145 -0
  14. pylelemmatize-0.1.0/src/pylelemmatize/charset_mufi.py +101 -0
  15. pylelemmatize-0.1.0/src/pylelemmatize/demapper_lstm.py +475 -0
  16. pylelemmatize-0.1.0/src/pylelemmatize/demapper_lstm_ctc.py +314 -0
  17. pylelemmatize-0.1.0/src/pylelemmatize/fast_mapper.py +123 -0
  18. pylelemmatize-0.1.0/src/pylelemmatize/htr_postcorrection.py +263 -0
  19. pylelemmatize-0.1.0/src/pylelemmatize/main_scripts.py +137 -0
  20. pylelemmatize-0.1.0/src/pylelemmatize/many_to_more.py +321 -0
  21. pylelemmatize-0.1.0/src/pylelemmatize/mapper_ds.py +236 -0
  22. pylelemmatize-0.1.0/src/pylelemmatize/philogeny.py +156 -0
  23. pylelemmatize-0.1.0/src/pylelemmatize/pylelemmatize_torch.py +33 -0
  24. pylelemmatize-0.1.0/src/pylelemmatize/substitution_augmenter.py +342 -0
  25. pylelemmatize-0.1.0/src/pylelemmatize/util.py +164 -0
  26. pylelemmatize-0.1.0/src/pylelemmatize/version.py +1 -0
  27. pylelemmatize-0.1.0/src/pylelemmatize.egg-info/PKG-INFO +107 -0
  28. pylelemmatize-0.1.0/src/pylelemmatize.egg-info/SOURCES.txt +31 -0
  29. pylelemmatize-0.1.0/src/pylelemmatize.egg-info/dependency_links.txt +1 -0
  30. pylelemmatize-0.1.0/src/pylelemmatize.egg-info/entry_points.txt +16 -0
  31. pylelemmatize-0.1.0/src/pylelemmatize.egg-info/requires.txt +8 -0
  32. pylelemmatize-0.1.0/src/pylelemmatize.egg-info/top_level.txt +2 -0
  33. pylelemmatize-0.1.0/src/pylelemmatize_torch/__init__.py +6 -0
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: pylelemmatize
3
+ Version: 0.1.0
4
+ Summary: A set utilities for hadling alphabets of corpora and OCR/HTR datasets
5
+ Home-page: https://github.com/anguelos/pylelemmatize
6
+ Author: Anguelos Nicolaou
7
+ Author-email: anguelos.nicolaou@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: numpy
14
+ Requires-Dist: unidecode
15
+ Requires-Dist: fargv
16
+ Requires-Dist: matplotlib
17
+ Requires-Dist: scipy
18
+ Requires-Dist: tqdm
19
+ Requires-Dist: networkx
20
+ Requires-Dist: lxml
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
30
+
31
+ # PyLeLemmatize
32
+
33
+ [![PyPI](https://img.shields.io/pypi/v/pylelemmatize.svg)](https://pypi.org/project/pylelemmatize/)
34
+ [![Python](https://img.shields.io/pypi/pyversions/pylelemmatize.svg)](https://pypi.org/project/pylelemmatize/)
35
+ [![Build](https://github.com/anguelos/pylelemmatize/actions/workflows/tests.yml/badge.svg)](https://github.com/anguelos/pylelemmatize/actions/workflows/tests.yml)
36
+ [![Docs](https://readthedocs.org/projects/pylelemmatize/badge/?version=latest)](https://pylelemmatize.readthedocs.io/en/latest/)
37
+ [![License](https://img.shields.io/github/license/anguelos/pylelemmatize.svg)](https://github.com/anguelos/pylelemmatize/blob/main/LICENSE)
38
+
39
+ A fast, modular lemmatization toolkit for Python.
40
+
41
+
42
+
43
+ PyLeLemmatize is a Python package for lemmatizing text. It provides a simple and efficient way to reduce large characters to simpler ones.
44
+
45
+ ## Installation
46
+
47
+ ### Install from GitHub with pip
48
+
49
+ To install PyLemmatize directly from GitHub using pip, run the following command:
50
+
51
+ ```sh
52
+ pip install git+https://github.com/yourusername/pylelemmatize.git
53
+ ```
54
+
55
+ ### Install from GitHub with code
56
+
57
+ To install PyLemmatize from the source code, follow these steps:
58
+
59
+ 1. Clone the repository:
60
+ 2. Navigate to the project directory:
61
+ 3. Install the package
62
+
63
+ ```sh
64
+ git clone https://github.com/yourusername/pylelemmatize.git
65
+ cd pylelemmatize
66
+ pip install -e ./
67
+ # If you dont want a development install, do pip install ./
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ ### Command Line Invocation
73
+
74
+ #### Evaluate Merges
75
+
76
+ ```sh
77
+ ll_evaluate_merges -h # get help string with the cli interface
78
+ ll_evaluate_merges -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*'
79
+ ```
80
+
81
+ Attention the merge CER is not symetric at all!
82
+ ```
83
+ # The following gives a CER of 0.0591
84
+ ll_evaluate_merges -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*' -merges '[("I", "J"), ("i", "j")]'
85
+ # While the following gives a CER of 0.0007
86
+ ll_evaluate_merges -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*' -merges '[("J", "I"), ("j", "i")]'
87
+ ```
88
+
89
+ #### Extract corpus alphabet
90
+ ```sh
91
+ ll_extract_corpus_alphabet -h # get help string with the cli interface
92
+ ll_extract_corpus_alphabet -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*'
93
+ ```
94
+
95
+ #### Test corpus on alphabets
96
+ ```sh
97
+ ll_test_corpus_on_alphabets -h # get help string with the cli interface
98
+ ll_test_corpus_on_alphabets -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*' -alphabets 'bmp_mufi,ascii,mes1,iso8859_2' -verbose
99
+ ```
100
+
101
+ ## Contributing
102
+
103
+ Contributions are welcome!
104
+
105
+ ## License
106
+
107
+ This project is licensed under the MIT License.
@@ -0,0 +1,77 @@
1
+ # PyLeLemmatize
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/pylelemmatize.svg)](https://pypi.org/project/pylelemmatize/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/pylelemmatize.svg)](https://pypi.org/project/pylelemmatize/)
5
+ [![Build](https://github.com/anguelos/pylelemmatize/actions/workflows/tests.yml/badge.svg)](https://github.com/anguelos/pylelemmatize/actions/workflows/tests.yml)
6
+ [![Docs](https://readthedocs.org/projects/pylelemmatize/badge/?version=latest)](https://pylelemmatize.readthedocs.io/en/latest/)
7
+ [![License](https://img.shields.io/github/license/anguelos/pylelemmatize.svg)](https://github.com/anguelos/pylelemmatize/blob/main/LICENSE)
8
+
9
+ A fast, modular lemmatization toolkit for Python.
10
+
11
+
12
+
13
+ PyLeLemmatize is a Python package for lemmatizing text. It provides a simple and efficient way to reduce large characters to simpler ones.
14
+
15
+ ## Installation
16
+
17
+ ### Install from GitHub with pip
18
+
19
+ To install PyLemmatize directly from GitHub using pip, run the following command:
20
+
21
+ ```sh
22
+ pip install git+https://github.com/yourusername/pylelemmatize.git
23
+ ```
24
+
25
+ ### Install from GitHub with code
26
+
27
+ To install PyLemmatize from the source code, follow these steps:
28
+
29
+ 1. Clone the repository:
30
+ 2. Navigate to the project directory:
31
+ 3. Install the package
32
+
33
+ ```sh
34
+ git clone https://github.com/yourusername/pylelemmatize.git
35
+ cd pylelemmatize
36
+ pip install -e ./
37
+ # If you dont want a development install, do pip install ./
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ### Command Line Invocation
43
+
44
+ #### Evaluate Merges
45
+
46
+ ```sh
47
+ ll_evaluate_merges -h # get help string with the cli interface
48
+ ll_evaluate_merges -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*'
49
+ ```
50
+
51
+ Attention the merge CER is not symetric at all!
52
+ ```
53
+ # The following gives a CER of 0.0591
54
+ ll_evaluate_merges -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*' -merges '[("I", "J"), ("i", "j")]'
55
+ # While the following gives a CER of 0.0007
56
+ ll_evaluate_merges -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*' -merges '[("J", "I"), ("j", "i")]'
57
+ ```
58
+
59
+ #### Extract corpus alphabet
60
+ ```sh
61
+ ll_extract_corpus_alphabet -h # get help string with the cli interface
62
+ ll_extract_corpus_alphabet -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*'
63
+ ```
64
+
65
+ #### Test corpus on alphabets
66
+ ```sh
67
+ ll_test_corpus_on_alphabets -h # get help string with the cli interface
68
+ ll_test_corpus_on_alphabets -corpus_glob './sample_data/wienocist_charter_1/wienocist_charter_1*' -alphabets 'bmp_mufi,ascii,mes1,iso8859_2' -verbose
69
+ ```
70
+
71
+ ## Contributing
72
+
73
+ Contributions are welcome!
74
+
75
+ ## License
76
+
77
+ This project is licensed under the MIT License.
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,48 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ version = open('src/pylelemmatize/version.py').read().split('=')[1].strip().strip('"')
4
+
5
+
6
+ setup(
7
+ name='pylelemmatize',
8
+ version=version,
9
+ package_dir={"": "src"},
10
+ packages=find_packages(where="src"),
11
+ install_requires=[
12
+ 'numpy', 'unidecode', 'fargv', 'matplotlib', 'scipy', 'tqdm', 'networkx', 'lxml'
13
+ ],
14
+ author='Anguelos Nicolaou',
15
+ author_email='anguelos.nicolaou@gmail.com',
16
+ description='A set utilities for hadling alphabets of corpora and OCR/HTR datasets',
17
+ long_description=open('README.md', encoding='utf-8').read(),
18
+ long_description_content_type='text/markdown',
19
+ url='https://github.com/anguelos/pylelemmatize',
20
+ classifiers=[
21
+ 'Programming Language :: Python :: 3',
22
+ 'License :: OSI Approved :: MIT License',
23
+ 'Operating System :: OS Independent',
24
+ ],
25
+ license_files=["LICENSE*", "LICENSES/*"],
26
+ python_requires='>=3.6',
27
+ entry_points={
28
+ 'console_scripts': [
29
+ 'll_remap_alphabet=pylelemmatize:main_remap_alphabet',
30
+ 'll_render_char_similarity_tree=pylelemmatize:main_char_similarity_tree',
31
+ 'll_infer_one2one=pylelemmatize:main_infer_one2one',
32
+ 'll_train_one2one=pylelemmatize:main_train_one2one',
33
+ 'll_train_one2one_report=pylelemmatize:main_report_demapper',
34
+ 'll_extract_corpus_alphabet=pylelemmatize:main_alphabet_extract_corpus_alphabet',
35
+ 'll_test_corpus_on_alphabets=pylelemmatize:main_map_test_corpus_on_alphabets',
36
+ 'll_evaluate_merges=pylelemmatize:main_alphabet_evaluate_merges',
37
+ 'll_extract_transcription_from_page_xml=pylelemmatize.util:main_extract_transcription_from_page_xml',
38
+ 'll_many_to_more=pylelemmatize.many_to_more:many_to_more_main',
39
+ 'll_many_to_more_evaluate=pylelemmatize.many_to_more:many_to_more_evaluate_main',
40
+
41
+ 'll_create_postcorrection_tsv=pylelemmatize.htr_postcorrection:main_create_postcorrection_tsv',
42
+
43
+ 'll_textline_full_cer=pylelemmatize.substitution_augmenter:main_textline_full_cer',
44
+ 'll_postcorrection=pylelemmatize.htr_postcorrection:main_postcorrection_infer',
45
+ 'll_postcorrection_train=pylelemmatize.htr_postcorrection:main_train_substitution_only_postcorrection',
46
+ ],
47
+ },
48
+ )
@@ -0,0 +1,52 @@
1
+ from typing import Dict, Literal, Optional
2
+ from .char_distance import char_similarity
3
+ from .main_scripts import main_alphabet_extract_corpus_alphabet, main_alphabet_evaluate_merges
4
+ from .abstract_mapper import AbstractLemmatizer, GenericLemmatizer, fast_cer, fast_numpy_to_str, fast_str_to_numpy
5
+ from .fast_mapper import LemmatizerBMP
6
+ #from .charset import allbmp_encoding_alphabet_strings, allnonbmp_encoding_alphabet_strings, main_map_test_corpus_on_alphabets
7
+
8
+ #from .charset import chr_iso, chr_mes, chr_ascii, chr_mufi_bmp, chr_mufi_nonbmp
9
+ from .all_charsets import Charsets
10
+ charsets = Charsets()
11
+
12
+ from .util import extract_transcription_from_page_xml, main_extract_transcription_from_page_xml, print_err
13
+ import sys
14
+ from .philogeny import main_char_similarity_tree
15
+ from .version import version
16
+ from .substitution_augmenter import CharConfusionMatrix
17
+ __version__ = version
18
+
19
+
20
+ default_unknown_chr = "�"
21
+
22
+ def create_lemmatizer(src_alphabet_str: str, dst_alphabet_str: Optional[str]=None,
23
+ mapper_type: Literal["fast", "generic", "guess"] = "guess", unknown_chr: str = default_unknown_chr,
24
+ override_map: Optional[Dict[str, str]] = None, min_similarity: float = .3) -> GenericLemmatizer:
25
+ if mapper_type == "guess":
26
+ if LemmatizerBMP.alphabet_in_bmp(src_alphabet_str) and LemmatizerBMP.alphabet_in_bmp(dst_alphabet_str):
27
+ mapper_type = "fast"
28
+ else:
29
+ mapper_type = "generic"
30
+ if mapper_type == "fast":
31
+ return LemmatizerBMP.from_alphabet_mapping(src_alphabet_str, dst_alphabet_str, unknown_chr=unknown_chr,
32
+ override_map=override_map, min_similarity=min_similarity)
33
+ elif mapper_type == "generic":
34
+ return GenericLemmatizer.from_alphabet_mapping(src_alphabet_str, dst_alphabet_str, unknown_chr=unknown_chr,
35
+ override_map=override_map, min_similarity=min_similarity)
36
+ else:
37
+ raise ValueError(f"Unknown mapper type: {mapper_type}")
38
+
39
+
40
+ __all__ = ["create_lemmatizer", "AbstractLemmatizer", "GenericLemmatizer", "LemmatizerBMP",
41
+ "char_similarity", "fast_cer", "fast_numpy_to_str", "fast_str_to_numpy",
42
+ "Charsets", "charset",
43
+ "extract_transcription_from_page_xml", "print_err",
44
+ "CharConfusionMatrix", "__version__"]
45
+
46
+ if "torch" in sys.modules or "sphinx" in sys.modules: # to allow doc generation without torch
47
+ from .mapper_ds import Seq2SeqDs
48
+ from .demapper_lstm import DemapperLSTM, main_train_one2one, main_infer_one2one, main_report_demapper
49
+ __all__.extend(["Seq2SeqDs", "DemapperLSTM", "main_train_one2one", "main_infer_one2one", "main_report_demapper"])
50
+ else:
51
+ print("Warning: Torch is not loaded. Seq2SeqDs will not be available.", file=sys.stderr)
52
+
@@ -0,0 +1,345 @@
1
+ from difflib import SequenceMatcher
2
+ import sys
3
+
4
+ from typing import Any, Dict, Generator, Literal, Optional, Set, Tuple, Union
5
+ from collections import defaultdict
6
+ import numpy as np
7
+ from abc import ABC, abstractmethod
8
+ import unicodedata
9
+ from .char_distance import char_similarity
10
+
11
+
12
+ def fast_str_to_numpy(s: str, dtype=np.uint16) -> np.ndarray:
13
+ if dtype == np.uint16:
14
+ return np.frombuffer(s.encode('utf-16le'), dtype=dtype)
15
+ elif dtype == np.uint32:
16
+ return np.frombuffer(s.encode('utf-32le'), dtype=dtype)
17
+ elif dtype == np.uint64:
18
+ return np.frombuffer(s.encode('utf-64le'), dtype=dtype)
19
+ elif dtype == np.uint8:
20
+ return np.frombuffer(s.encode('utf-8'), dtype=dtype)
21
+ else:
22
+ raise ValueError(f"Unsupported dtype: {dtype}")
23
+
24
+
25
+ def fast_numpy_to_str(np_arr: np.ndarray) -> str:
26
+ if np_arr.dtype == np.uint16:
27
+ return np_arr.tobytes().decode('utf-16le')
28
+ elif np_arr.dtype == np.uint32:
29
+ return np_arr.tobytes().decode('utf-32le')
30
+ elif np_arr.dtype == np.uint64:
31
+ return np_arr.tobytes().decode('utf-64le')
32
+ elif np_arr.dtype == np.uint8:
33
+ return np_arr.tobytes().decode('utf-8')
34
+ else:
35
+ raise ValueError(f"Unsupported dtype: {np_arr.dtype}")
36
+
37
+
38
+ def fast_cer(pred: str, true: str) -> float:
39
+ np_pred = fast_str_to_numpy(pred)
40
+ np_true = fast_str_to_numpy(true)
41
+ return np.mean(np_pred != np_true)
42
+
43
+
44
+
45
+
46
+ class AbstractLemmatizer(ABC):
47
+ """Abstract base class for lemmatizers that map characters from a source alphabet to a destination alphabet.
48
+
49
+ Attributes:
50
+ src_alphabet_str (str): The source alphabet string.
51
+ dst_alphabet_str (str): The destination alphabet string.
52
+ unknown_chr (str): The character used for unknown mappings. Default is "�".
53
+ normalize_unicode (Callable[[str], str]): Function to normalize Unicode strings.
54
+ """
55
+ def _densify_unicode(self: Any, text: str) -> str:
56
+ """
57
+ Convert a Unicode string to its composed (dense) form using NFC normalization.
58
+
59
+ Args:
60
+ text (str): The input Unicode string.
61
+
62
+ Returns:
63
+ str: The composed (dense) Unicode string.
64
+ """
65
+ return unicodedata.normalize('NFC', text)
66
+
67
+ def _decompose_unicode(self: Any, text: str) -> str:
68
+ """
69
+ Convert a Unicode string to its decomposed (sparse) form using NFD normalization.
70
+
71
+ Args:
72
+ text (str): The input Unicode string.
73
+
74
+ Returns:
75
+ str: The decomposed (sparse) Unicode string.
76
+ """
77
+ return unicodedata.normalize('NFD', text)
78
+
79
+
80
+ def _null_unicode(self: Any, text: str) -> str:
81
+ """
82
+ Normalize a Unicode string to its composed (dense) form using NFC normalization.
83
+
84
+ Args:
85
+ text (str): The input Unicode string.
86
+
87
+ Returns:
88
+ str: The normalized Unicode string.
89
+ """
90
+ return text
91
+
92
+ @classmethod
93
+ def __create_mappers(cls, mapping_dict, unknown_chr) -> Tuple[defaultdict, np.ndarray, Dict[int, str], Dict[str, int], np.ndarray, np.ndarray, np.ndarray]:
94
+ max_ord = max(ord(c) for c in mapping_dict.keys() | mapping_dict.values() | {unknown_chr})
95
+ assert max_ord < 256**3, f"All characters must be whithin resoble size (256^3). found {max_ord}."
96
+
97
+ if unknown_chr in mapping_dict:
98
+ assert mapping_dict[unknown_chr] == unknown_chr, "unknown_chr must map to itself in the mapping_dict."
99
+ del mapping_dict[unknown_chr] # Remove the unknown character from the mapping to avoid confusion
100
+
101
+ src_alphabet_str = ''.join(sorted(mapping_dict.keys()))
102
+ dst_alphabet_str = ''.join(sorted(set(mapping_dict.values())))
103
+
104
+ chr2chr = defaultdict(lambda: unknown_chr)
105
+ chr2chr.update(mapping_dict)
106
+
107
+ dense2src_dst = [(n + 1, (c, chr2chr[c])) for n, c in enumerate(src_alphabet_str)]
108
+
109
+ src_str = [(s,s) for _, (s, _) in dense2src_dst]
110
+ src_str = ''.join([s for _, s in sorted(src_str)])
111
+
112
+ dst_str = [(s, d) for _, (s, d) in dense2src_dst]
113
+ dst_str = [(s, d) for s, d in dst_str if d != unknown_chr] # Remove unknown characters from the destination string
114
+ dst_str = ''.join([d for _, d in sorted(dst_str)])
115
+
116
+ src_full_str = unknown_chr + src_str
117
+
118
+ srcchr2dense = {s: n for n, s in enumerate(src_full_str)}
119
+
120
+ np_chrord2dense = np.zeros(max_ord, dtype=np.uint16)
121
+ np_dense2chrord = np.zeros(max_ord, dtype=np.uint16)
122
+ for c, n in srcchr2dense.items():
123
+ np_chrord2dense[ord(c)] = n
124
+ np_dense2chrord[n] = ord(chr2chr[c])
125
+ return src_alphabet_str, dst_alphabet_str, np_chrord2dense, np_dense2chrord
126
+
127
+ @classmethod
128
+ def fast_alphabet_extraction(cls, text: str) -> str:
129
+ np_text = fast_str_to_numpy(text)
130
+ uniq = np.unique(np_text)
131
+ return fast_numpy_to_str(uniq)
132
+
133
+ def __set_unicode_normalization(self, normalize_unicode: Literal["Dense", "Composite", None] = "Dense"):
134
+ """
135
+ np_text = fast_str_to_numpy(text)
136
+ Set the Unicode normalization method.
137
+
138
+ Args:
139
+ normalize_unicode (Literal["Dense", "Composite", None]): The normalization type.
140
+ - "Dense" for NFC normalization (composed form).
141
+ - "Composite" for NFD normalization (decomposed form).
142
+ - None for no normalization.
143
+ """
144
+ self.__unicode_normalization = normalize_unicode
145
+ if normalize_unicode.lower() == "dense":
146
+ self.normalize_unicode = self._densify_unicode
147
+ elif normalize_unicode == "composite":
148
+ self.normalize_unicode = self._decompose_unicode
149
+ elif normalize_unicode is None:
150
+ self.normalize_unicode = self._null_unicode
151
+ else:
152
+ raise ValueError(f"Unknown normalization type: {normalize_unicode}")
153
+
154
+ def __init__(self, unicode_normalization: Literal["Dense", "Composite", None] = "Dense", unknown_chr: str = "�"):
155
+ super().__init__()
156
+ self.__set_unicode_normalization(unicode_normalization)
157
+ self.__unknown_chr = unknown_chr
158
+
159
+ @property
160
+ def unicode_normalization(self) -> Literal["Dense", "Composite", None]:
161
+ return self.__unicode_normalization
162
+
163
+ @abstractmethod
164
+ def __call__(self, text: str) -> str:
165
+ """Convert text to the alphabet representation."""
166
+ pass
167
+
168
+ @property
169
+ @abstractmethod
170
+ def src_alphabet_str(self) -> str:
171
+ pass
172
+
173
+ @property
174
+ @abstractmethod
175
+ def dst_alphabet_str(self) -> str:
176
+ pass
177
+
178
+ @property
179
+ def unknown_chr(self) -> str:
180
+ return self.__unknown_chr
181
+
182
+ @abstractmethod
183
+ def __repr__(self):
184
+ raise NotImplementedError("Subclasses must implement __repr__ method.")
185
+
186
+ @property
187
+ def alphabet_tsv(self) -> str:
188
+ title = " #\tUnicode Number\tUnicode x10\tUnicode x16\tPython String"
189
+ lines = [title]
190
+ for n, c in enumerate(self.unknown_chr + self.dst_alphabet_str):
191
+ lines.append(f"{n}\t{unicodedata.name(c)}\t{ord(c)}\t{ord(c):x}\t{repr(c)}")
192
+ return "\n".join(lines)
193
+
194
+ @property
195
+ def mapping_tsv(self) -> str:
196
+ title = "Source Character\tDestination Character"
197
+ lines = [title]
198
+ for src, dst in sorted(self.mapping_dict.items()):
199
+ lines.append(f"{repr(src)}\t{repr(dst)}")
200
+ return "\n".join(lines)
201
+
202
+ def __ne__(self, other: Any) -> bool:
203
+ if not isinstance(other, AbstractLemmatizer):
204
+ return True
205
+ else:
206
+ return self.mapping_tsv != other.mapping_tsv
207
+
208
+ def __eq__(self, other: Any) -> bool:
209
+ if not isinstance(other, AbstractLemmatizer):
210
+ return False
211
+ else:
212
+ return self.mapping_tsv == other.mapping_tsv
213
+
214
+ def __len__(self) -> int:
215
+ """Return the size of the destination alphabet."""
216
+ return len(self.dst_alphabet_str) + 1
217
+
218
+ def get_unigram(self, text: str) -> Tuple[np.ndarray, np.ndarray, Dict[int, str]]:
219
+ # adding all characters atleast once to make np.unique count zero counts
220
+ #src_alphabet_str, _, __np_chrord2dense, __np_dense2chrord = self.__create_mappers(self.mapping_dict, self.unknown_chr)
221
+ np_text = fast_str_to_numpy(self.unknown_chr + self.src_alphabet_str + text)
222
+ mapped_np_text = self.__np_chrord2dense[np_text]
223
+ values, counts = np.unique(mapped_np_text, return_counts=True)
224
+ counts = counts - 1 # removing the counts of the added characters
225
+ labels = np.array([c for c in fast_numpy_to_str(self.__np_dense2chrord[values])], dtype=np.str_)
226
+ return values, counts, labels
227
+
228
+ def get_cer(self, pred: str, true: str) -> float:
229
+ np_pred = fast_str_to_numpy(pred)
230
+ np_true = fast_str_to_numpy(true)
231
+ mapped_np_pred = self.__npint2int[np_pred]
232
+ mapped_np_true = self.__npint2int[np_true]
233
+ return np.mean(mapped_np_pred != mapped_np_true)
234
+
235
+ def get_encoding_information_loss(self, text: str) -> float:
236
+ np_text = fast_str_to_numpy(text)
237
+ mapped_np_text = self.__npint2int[np_text]
238
+ return np.mean(np_text != mapped_np_text)
239
+
240
+ # def get_unigram(self, text: str) -> Tuple[np.ndarray, np.ndarray, Dict[int, str]]:
241
+ # # adding all characters atleast once to make np.unique count zero counts
242
+ # src_alphabet_str, _, __np_chrord2dense, __np_dense2chrord = AbstractLemmatizer.__create_mappers(self.mapping_dict, self.unknown_chr)
243
+ # np_text = fast_str_to_numpy(self.unknown_chr + src_alphabet_str + text)
244
+ # mapped_np_text = __np_chrord2dense[np_text]
245
+ # values, counts = np.unique(mapped_np_text, return_counts=True)
246
+ # counts = counts - 1 # removing the counts of the added characters
247
+ # labels = np.array([c for c in fast_numpy_to_str(__np_dense2chrord[values])], dtype=np.str_)
248
+ # labels[0] = self.unknown_chr # Ensure the unknown character is included
249
+ # print("\n\nLABELS:\n", labels, "\n\n\n")
250
+ # return values, counts, labels
251
+
252
+
253
+ class GenericLemmatizer(AbstractLemmatizer):
254
+ @classmethod
255
+ def from_alphabet_mapping(cls, src_alphabet_str: str, dst_alphabet_str: Optional[str] = None,
256
+ unknown_chr: str = "�", override_map: Optional[Dict[str, str]] = None,
257
+ min_similarity: float = .25, verbose: int = 0) -> 'GenericLemmatizer':
258
+ if dst_alphabet_str is None:
259
+ mapping_dict = {c:c for c in src_alphabet_str}
260
+ if unknown_chr not in mapping_dict:
261
+ mapping_dict[unknown_chr] = unknown_chr
262
+ return cls(mapping_dict=mapping_dict, unknown_chr=unknown_chr)
263
+ mapping_dict = defaultdict(lambda: unknown_chr) # Default to unknown character
264
+ if override_map is not None:
265
+ mapping_dict.update(override_map) # Map destination characters to themselves
266
+ remain_sources = []
267
+ for c in src_alphabet_str:
268
+ if c not in override_map:
269
+ remain_sources.append(c)
270
+ else:
271
+ remain_sources = list(src_alphabet_str)
272
+ if unknown_chr not in dst_alphabet_str:
273
+ dst_alphabet_str = unknown_chr + dst_alphabet_str
274
+ s_map = np.zeros((len(remain_sources), len(dst_alphabet_str)))
275
+ unknown_chr_dst_idx = dst_alphabet_str.index(unknown_chr)
276
+ for src_n, src_c in enumerate(remain_sources):
277
+ for dst_n, dst_c in enumerate(dst_alphabet_str):
278
+ s_map[src_n, dst_n] = char_similarity(src_c, dst_c)
279
+
280
+ too_small = s_map < min_similarity
281
+ s_map[too_small] = 0 # Apply minimum similarity threshold
282
+ s_map[:, unknown_chr_dst_idx]+= .00001 # Set similarity to unknown character to 0
283
+
284
+ if verbose:
285
+ print(f"Similarity matrix:\n{s_map}\n", file=sys.stderr)
286
+
287
+ dst_symbol_array = np.array(list(dst_alphabet_str), dtype=np.str_)
288
+ src_symbol_array = np.array(list(remain_sources), dtype=np.str_)
289
+ best_dst_idx = np.argmax(s_map, axis=1)
290
+
291
+ if verbose:
292
+ print(f"Best destination indices: {best_dst_idx}\n", file=sys.stderr)
293
+ print(f"Source symbols: {src_symbol_array}\n", file=sys.stderr)
294
+ print(f"Destination symbols: {dst_symbol_array}\n", file=sys.stderr)
295
+ if verbose > 1:
296
+ for n in range(s_map.shape[0]):
297
+ print(f"Source: {repr(src_symbol_array[n])} -> Best Destination: {repr(dst_symbol_array[best_dst_idx[n]])} with similarity {s_map[n, best_dst_idx[n]]:.4f}", file=sys.stderr)
298
+ mapping_dict.update({src_symbol_array[n]: dst_symbol_array[best_dst_idx[n]] for n in range(len(remain_sources))})
299
+ mapping_dict = {k: v for k, v in sorted(mapping_dict.items())}
300
+ if verbose:
301
+ print(f"Mapping dictionary: {mapping_dict}\n", file=sys.stderr)
302
+ if unknown_chr not in mapping_dict:
303
+ mapping_dict[unknown_chr] = unknown_chr
304
+ else:
305
+ assert mapping_dict[unknown_chr] == unknown_chr, f"unknown_chr must map to itself in the mapping_dict. Found {repr(mapping_dict[unknown_chr])} instead of {repr(unknown_chr)}."
306
+ mapping_dict = {str(k): str(v) for k, v in sorted(mapping_dict.items())}
307
+ return cls(mapping_dict=mapping_dict, unknown_chr=unknown_chr)
308
+
309
+ def copy_removing_unused_inputs(self, txt: str) -> Any:
310
+ txt = self.normalize_unicode(txt)
311
+ txt = self.fast_alphabet_extraction(txt)
312
+ mapping_dict = self.mapping_dict.copy()
313
+ reduced_mapping_dict = {}
314
+ for k in txt:
315
+ if k in mapping_dict:
316
+ reduced_mapping_dict[k] = mapping_dict[k]
317
+ return self.__class__(mapping_dict=reduced_mapping_dict, unknown_chr=self.unknown_chr, unicode_normalization=self.unicode_normalization)
318
+
319
+
320
+ def __init__(self, mapping_dict = {}, unknown_chr: str = "�", unicode_normalization: Literal["Dense", "Composed", None] = "Dense"):
321
+ super().__init__(unicode_normalization=unicode_normalization, unknown_chr=unknown_chr)
322
+ #print(f"Creating GenericLemmatizer with mapping_dict={repr(mapping_dict)} and unknown_chr={repr(unknown_chr)}")
323
+ self.mapping_dict = mapping_dict.copy()
324
+
325
+ def len(self) -> int:
326
+ """Return the size of the destination alphabet."""
327
+ return len(self.dst_alphabet_str) + 1
328
+
329
+ def __call__(self, text: str) -> str:
330
+ text = self.normalize_unicode(text) # This might be the bottleneck in a fast implementation.
331
+ return ''.join(self.mapping_dict.get(c, self.unknown_chr) for c in text)
332
+
333
+ @property
334
+ def src_alphabet_str(self) -> str:
335
+ return ''.join(sorted(self.mapping_dict.keys()))
336
+
337
+ @property
338
+ def dst_alphabet_str(self) -> str:
339
+ res = ''.join(sorted(set(self.mapping_dict.values())))
340
+ if self.unknown_chr not in res:
341
+ res += self.unknown_chr
342
+ return res
343
+
344
+ def __repr__(self):
345
+ return f"GenericLemmatizer(mapping_dict={repr(self.mapping_dict)}, unknown_chr={repr(self.unknown_chr)})"