pylelemmatize 0.1.0__py3-none-any.whl

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.
@@ -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)})"
@@ -0,0 +1,282 @@
1
+ from typing import Dict, List
2
+
3
+
4
+ from .charset_iso import get_encoding_dicts as get_iso_encoding_dicts
5
+ from .charset_mes import get_encoding_dicts as get_mes_encoding_dicts
6
+ from .charset_ascii import get_encoding_dicts as get_ascii_encoding_dicts
7
+ from .charset_mufi import get_encoding_dicts as get_mufi_encoding_dicts
8
+
9
+
10
+
11
+ from .abstract_mapper import fast_cer, GenericLemmatizer
12
+ from .fast_mapper import LemmatizerBMP
13
+
14
+
15
+ class Charsets:
16
+ """A class that provides access to various character sets as unicode strings.
17
+
18
+ This class is a singleton and is instantiated as `charset` in the `pylelemmatize` package.
19
+
20
+ Usage::
21
+ from pylelemmatize import Charsets
22
+ charsets = Charsets()
23
+ print(charsets.mes1) # prints the MES1 character set as a string
24
+ """
25
+ def __init__(self):
26
+ self.lib_cache__ = {}
27
+ self.alphabets = ["mes1", "mes2", "mes3a", "mes3b",
28
+ "iso_8859_1", "iso_8859_2", "iso_8859_3", "iso_8859_4",
29
+ "iso_8859_5", "iso_8859_6", "iso_8859_7", "iso_8859_8",
30
+ "iso_8859_9", "iso_8859_10", "iso_8859_11", "iso_8859_12", "iso_8859_13",
31
+ "iso_8859_14", "iso_8859_15", "iso_8859_16",
32
+ "ascii_lowercase", "ascii_uppercase", "ascii_letters", "digits",
33
+ "hexdigits", "octdigits", "punctuation",
34
+ "bmp_mufi", "bmp_pua_mufi", "nonbmp_mufi",
35
+ "nonbmp_pua_mufi"]
36
+
37
+ @property
38
+ def mes_charsets(self):
39
+ if self.lib_cache__.get("mes") is None:
40
+ self.lib_cache__["mes"] = get_mes_encoding_dicts()
41
+ return self.lib_cache__["mes"]
42
+ @property
43
+ def mes1(self):
44
+ return self.mes_charsets["mes1"]
45
+ @property
46
+ def mes2(self):
47
+ return self.mes_charsets["mes2"]
48
+ @property
49
+ def mes3a(self):
50
+ return self.mes_charsets["mes3a"]
51
+ @property
52
+ def mes3b(self):
53
+ return self.mes_charsets["mes3b"]
54
+
55
+ @property
56
+ def iso_charsets(self):
57
+ if self.lib_cache__.get("iso") is None:
58
+ self.lib_cache__["iso"] = get_iso_encoding_dicts()
59
+ return self.lib_cache__["iso"]
60
+ @property
61
+ def iso_8859_2(self):
62
+ return self.iso_charsets["iso8859_2"]
63
+ @property
64
+ def iso_8859_3(self):
65
+ return self.iso_charsets["iso8859_3"]
66
+ @property
67
+ def iso_8859_4(self):
68
+ return self.iso_charsets["iso8859_4"]
69
+ @property
70
+ def iso_8859_5(self):
71
+ return self.iso_charsets["iso8859_5"]
72
+ @property
73
+ def iso_8859_6(self):
74
+ return self.iso_charsets["iso8859_6"]
75
+ @property
76
+ def iso_8859_7(self):
77
+ return self.iso_charsets["iso8859_7"]
78
+ @property
79
+ def iso_8859_8(self):
80
+ return self.iso_charsets["iso8859_8"]
81
+ @property
82
+ def iso_8859_9(self):
83
+ return self.iso_charsets["iso8859_9"]
84
+ @property
85
+ def iso_8859_10(self):
86
+ return self.iso_charsets["iso8859_10"]
87
+ @property
88
+ def iso_8859_11(self):
89
+ return self.iso_charsets["iso8859_11"]
90
+ @property
91
+ def iso_8859_12(self):
92
+ return self.iso_charsets["iso8859_12"]
93
+ @property
94
+ def iso_8859_13(self):
95
+ return self.iso_charsets["iso8859_13"]
96
+ @property
97
+ def iso_8859_14(self):
98
+ return self.iso_charsets["iso8859_14"]
99
+ @property
100
+ def iso_8859_15(self):
101
+ return self.iso_charsets["iso8859_15"]
102
+ @property
103
+ def iso_8859_16(self):
104
+ return self.iso_charsets["iso8859_16"]
105
+
106
+ @property
107
+ def ascii_charsets(self):
108
+ if self.lib_cache__.get("ascii") is None:
109
+ self.lib_cache__["ascii"] = get_ascii_encoding_dicts()
110
+ return self.lib_cache__["ascii"]
111
+ @property
112
+ def ascii(self):
113
+ return self.ascii_charsets["ascii"]
114
+ @property
115
+ def ascii_lowercase(self):
116
+ return self.ascii_charsets["ascii_lowercase"]
117
+ @property
118
+ def ascii_uppercase(self):
119
+ return self.ascii_charsets["ascii_uppercase"]
120
+ @property
121
+ def ascii_letters(self):
122
+ return self.ascii_charsets["ascii_letters"]
123
+ @property
124
+ def digits(self):
125
+ return self.ascii_charsets["digits"]
126
+ @property
127
+ def hexdigits(self):
128
+ return self.ascii_charsets["hexdigits"]
129
+ @property
130
+ def octdigits(self):
131
+ return self.ascii_charsets["octdigits"]
132
+ @property
133
+ def punctuation(self):
134
+ return self.ascii_charsets["punctuation"]
135
+
136
+ @property
137
+ def mufibmp_charsets(self):
138
+ if self.lib_cache__.get("mufibmp") is None:
139
+ self.lib_cache__["mufibmp"], self.lib_cache__["mufinonbmp"] = get_mufi_encoding_dicts()
140
+ return self.lib_cache__["mufibmp"]
141
+ @property
142
+ def mufinonbmp_charsets(self):
143
+ if self.lib_cache__.get("mufinonbmp") is None:
144
+ self.lib_cache__["mufibmp"], self.lib_cache__["mufinonbmp"] = get_mufi_encoding_dicts()
145
+ return self.lib_cache__["mufinonbmp"]
146
+ @property
147
+ def mufibmp(self):
148
+ return self.mufibmp_charsets["bmp_mufi"]
149
+ @property
150
+ def mufibmp_pua(self):
151
+ return self.mufibmp_charsets["bmp_pua_mufi"]
152
+ @property
153
+ def mufinonbmp(self):
154
+ return self.mufinonbmp_charsets["nonbmp_mufi"]
155
+ @property
156
+ def mufinonbmp_pua(self):
157
+ return self.mufinonbmp_charsets["nonbmp_pua_mufi"]
158
+
159
+ def __getitem__(self, item):
160
+ return getattr(self, item)
161
+
162
+ def __iter__(self):
163
+ return iter(self.alphabets)
164
+
165
+ def __len__(self):
166
+ return len(self.alphabets)
167
+
168
+ def __contains__(self, item):
169
+ return item in self.alphabets
170
+
171
+ def keys(self):
172
+ return self.alphabets
173
+
174
+ def items(self):
175
+ return [(k, getattr(self, k)) for k in self.alphabets]
176
+
177
+
178
+ def main_map_test_corpus_on_alphabets():
179
+ import fargv, glob, time, sys, tqdm
180
+ from matplotlib import pyplot as plt
181
+ from .util import generate_corpus
182
+
183
+ def plot_covverage(corpus_str, encoding_alphabet_strings, alphabet_to_cer: Dict[str, float], alphabet_to_missing: Dict[str, str], alphabet_to_unfound: Dict[str, str], save_plot_path: str, show_plot: bool):
184
+ fig, ax = plt.subplots(3, 1, figsize=(15, 10))
185
+ alphabet_to_missing_ratio = {k: 100*len(v)/len(corpus_str) for k, v in alphabet_to_missing.items() if len(v) > 0}
186
+ alphabet_to_unfound_ratio = {k: len(v) for k, v in alphabet_to_unfound.items() if len(v) > 0}
187
+ ax[0].bar(alphabet_to_cer.keys(), [100*v for v in alphabet_to_cer.values()])
188
+ ax[0].set_xticklabels(alphabet_to_cer.keys(), rotation=45)
189
+ ax[0].set_title("CER %")
190
+
191
+ ax[1].bar(alphabet_to_missing_ratio.keys(), alphabet_to_missing_ratio.values())
192
+ ax[1].set_xticklabels(alphabet_to_missing_ratio.keys(), rotation=45)
193
+ ax[1].set_title("Character Recall %")
194
+
195
+ ax[2].bar(alphabet_to_unfound_ratio.keys(), alphabet_to_unfound_ratio.values())
196
+ ax[2].set_xticklabels(alphabet_to_unfound_ratio.keys(), rotation=45)
197
+ ax[2].set_yscale('log')
198
+ ax[2].set_title("Charset uncovered chars # (waste)")
199
+
200
+ plt.subplots_adjust(hspace=.6)
201
+ if save_plot_path != "":
202
+ plt.savefig(save_plot_path)
203
+ if show_plot:
204
+ plt.show()
205
+
206
+ t = time.time()
207
+ p = {
208
+ "alphabets": ["ascii", f"A comma separated listof encodings must be a subset of {repr(allbmp_encoding_alphabet_strings.keys())}. Or 'all'"],
209
+ "corpus_glob": "",
210
+ "corpus_files": set([]),
211
+ "verbose": False,
212
+ "strip_xml": True,
213
+ "all_is_xml": False,
214
+ "output_tsv": "",
215
+ "hide_plot": False,
216
+ "save_plot_path": ""
217
+ }
218
+ args, _ = fargv.fargv(p)
219
+
220
+ if args.alphabets == "all":
221
+ args.alphabets = list(allbmp_encoding_alphabet_strings.keys())
222
+ else:
223
+ args.alphabets = [a.strip() for a in args.alphabets.split(',')]
224
+
225
+ if args.corpus_glob != "":
226
+ glob_files = set(glob.glob(args.corpus_glob))
227
+ else:
228
+ glob_files = set([])
229
+ if len(args.corpus_files) > 0:
230
+ corpus_files = args.corpus_files
231
+ else:
232
+ corpus_files = set([])
233
+
234
+ all_corpus_strs = []
235
+ for file_contnets in generate_corpus(glob_files | corpus_files, verbose=args.verbose,
236
+ strip_xml=args.strip_xml, treat_all_file_as_xml=args.all_is_xml):
237
+ all_corpus_strs.append(file_contnets)
238
+ found_corpus_str = ''.join(all_corpus_strs)
239
+
240
+ if args.verbose:
241
+ print(f"{time.time() - t :.2f}: Loaded corpus", file=sys.stderr)
242
+
243
+ if args.verbose:
244
+ alphabet_names = tqdm.tqdm(args.alphabets)
245
+ else:
246
+ alphabet_names = args.alphabets
247
+
248
+ alphabet_to_cer = {}
249
+ alphabet_to_missing = {}
250
+ alphabet_to_unfound = {}
251
+ for alphabet_name in alphabet_names:
252
+ if alphabet_name not in allbmp_encoding_alphabet_strings:
253
+ raise ValueError(f"Alphabet {alphabet_name} not in {allbmp_encoding_alphabet_strings.keys()}")
254
+
255
+ alphabet_str = allbmp_encoding_alphabet_strings[alphabet_name]
256
+ if max(alphabet_str) > '\uffff':
257
+ #alphabet = Alphabet(alphabet_str=alphabet_str, vectorized_mapper_sz=max(256**2, 1 + ord(max(alphabet_str)))) # This is a hack to ensure that the vectorized mapper is large enough for all characters in MUFI
258
+ alphabet = GenericLemmatizer.from_alphabet_mapping(alphabet_str, mapping_dict=None)
259
+ else:
260
+ #alphabet = AlphabetBMP(alphabet_str=alphabet_str) # This is a hack to ensure that the vectorized mapper is large enough for all characters in MUFI
261
+ alphabet = LemmatizerBMP.from_alphabet_mapping(alphabet_str)
262
+
263
+ mapped_corpus_str = alphabet(found_corpus_str)
264
+
265
+ if len(found_corpus_str) > 0:
266
+ alphabet_to_cer[alphabet_name] = fast_cer(found_corpus_str, mapped_corpus_str)
267
+ alphabet_to_missing[alphabet_name] = ''.join(sorted(set(found_corpus_str) - set(alphabet_str)))
268
+ alphabet_to_unfound[alphabet_name] = ''.join(sorted(set(alphabet_str) - set(found_corpus_str)))
269
+
270
+ else:
271
+ alphabet_to_cer[alphabet_name] = 0
272
+ alphabet_to_missing[alphabet_name] = ""
273
+ alphabet_to_unfound[alphabet_name] = alphabet_str
274
+
275
+ if args.verbose:
276
+ for k, v in alphabet_to_cer.items():
277
+ print(f"{k}: CER {100*v:.2f}% Missing: {len(alphabet_to_missing[k])}, Redundant: {len(alphabet_to_unfound[k])} {repr(alphabet_to_missing[k])}", file=sys.stderr)
278
+ print(f"Computed all for {len(found_corpus_str)} characters in {time.time() -t :.2f}", file=sys.stderr)
279
+
280
+ if not args.hide_plot or args.save_plot_path != "":
281
+ found_corpus_alphabet_str = ''.join(sorted(set(found_corpus_str)))
282
+ plot_covverage(found_corpus_alphabet_str, allbmp_encoding_alphabet_strings, alphabet_to_cer, alphabet_to_missing, alphabet_to_unfound, args.save_plot_path, not args.hide_plot)